From f4433f28b509a9f67ca85d79000ccf9c2f4b7a24 Mon Sep 17 00:00:00 2001 From: Mauro Sardara Date: Fri, 21 Feb 2020 11:52:28 +0100 Subject: [HICN-534] Major rework on libtransport organization Change-Id: I361b83a18b4fd59be136d5f0817fc28e17e89884 Signed-off-by: Mauro Sardara --- libtransport/src/utils/CMakeLists.txt | 53 + libtransport/src/utils/content_store.cc | 122 +++ libtransport/src/utils/content_store.h | 76 ++ libtransport/src/utils/daemonizator.cc | 76 ++ libtransport/src/utils/deadline_timer.h | 114 ++ libtransport/src/utils/epoll_event_reactor.cc | 187 ++++ libtransport/src/utils/epoll_event_reactor.h | 80 ++ libtransport/src/utils/event_reactor.h | 37 + libtransport/src/utils/event_thread.h | 99 ++ libtransport/src/utils/fd_deadline_timer.h | 129 +++ libtransport/src/utils/log.cc | 1403 ++++++++++++++++++++++++ libtransport/src/utils/membuf.cc | 867 +++++++++++++++ libtransport/src/utils/memory_pool_allocator.h | 152 +++ libtransport/src/utils/min_filter.h | 56 + libtransport/src/utils/stream_buffer.h | 31 + libtransport/src/utils/string_tokenizer.cc | 47 + libtransport/src/utils/suffix_strategy.h | 165 +++ libtransport/src/utils/test.h | 46 + libtransport/src/utils/uri.cc | 122 +++ 19 files changed, 3862 insertions(+) create mode 100644 libtransport/src/utils/CMakeLists.txt create mode 100644 libtransport/src/utils/content_store.cc create mode 100644 libtransport/src/utils/content_store.h create mode 100644 libtransport/src/utils/daemonizator.cc create mode 100644 libtransport/src/utils/deadline_timer.h create mode 100644 libtransport/src/utils/epoll_event_reactor.cc create mode 100644 libtransport/src/utils/epoll_event_reactor.h create mode 100644 libtransport/src/utils/event_reactor.h create mode 100644 libtransport/src/utils/event_thread.h create mode 100644 libtransport/src/utils/fd_deadline_timer.h create mode 100644 libtransport/src/utils/log.cc create mode 100644 libtransport/src/utils/membuf.cc create mode 100644 libtransport/src/utils/memory_pool_allocator.h create mode 100644 libtransport/src/utils/min_filter.h create mode 100644 libtransport/src/utils/stream_buffer.h create mode 100644 libtransport/src/utils/string_tokenizer.cc create mode 100644 libtransport/src/utils/suffix_strategy.h create mode 100644 libtransport/src/utils/test.h create mode 100644 libtransport/src/utils/uri.cc (limited to 'libtransport/src/utils') diff --git a/libtransport/src/utils/CMakeLists.txt b/libtransport/src/utils/CMakeLists.txt new file mode 100644 index 000000000..88d451b6b --- /dev/null +++ b/libtransport/src/utils/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (c) 2017-2019 Cisco and/or its affiliates. +# 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. + +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +list(APPEND SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/string_tokenizer.cc + ${CMAKE_CURRENT_SOURCE_DIR}/uri.cc + ${CMAKE_CURRENT_SOURCE_DIR}/log.cc + ${CMAKE_CURRENT_SOURCE_DIR}/membuf.cc + ${CMAKE_CURRENT_SOURCE_DIR}/content_store.cc +) + + +list(APPEND HEADER_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/event_reactor.h + ${CMAKE_CURRENT_SOURCE_DIR}/min_filter.h + ${CMAKE_CURRENT_SOURCE_DIR}/stream_buffer.h + ${CMAKE_CURRENT_SOURCE_DIR}/suffix_strategy.h + ${CMAKE_CURRENT_SOURCE_DIR}/event_thread.h + ${CMAKE_CURRENT_SOURCE_DIR}/content_store.h + ${CMAKE_CURRENT_SOURCE_DIR}/deadline_timer.h +) + +if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + list(APPEND HEADER_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/epoll_event_reactor.h + ${CMAKE_CURRENT_SOURCE_DIR}/fd_deadline_timer.h + ) + + list(APPEND SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/epoll_event_reactor.cc + ) +endif() + +if(NOT WIN32) + list(APPEND SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/daemonizator.cc + ) +endif() + +set(SOURCE_FILES ${SOURCE_FILES} PARENT_SCOPE) +set(HEADER_FILES ${HEADER_FILES} PARENT_SCOPE) diff --git a/libtransport/src/utils/content_store.cc b/libtransport/src/utils/content_store.cc new file mode 100644 index 000000000..cb3db6d94 --- /dev/null +++ b/libtransport/src/utils/content_store.cc @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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 +#include +#include +#include + +#include + +namespace utils { + +ContentStore::ContentStore(std::size_t max_packets) + : max_content_store_size_(max_packets) {} + +ContentStore::~ContentStore() {} + +void ContentStore::insert( + const std::shared_ptr &content_object) { + if (max_content_store_size_ == 0) { + return; + } + + utils::SpinLock::Acquire locked(cs_mutex_); + + if (TRANSPORT_EXPECT_FALSE(content_store_hash_table_.size() != + fifo_list_.size())) { + TRANSPORT_LOGW("Inconsistent size!!!!"); + TRANSPORT_LOGW("Hash Table: %zu |||| FIFO List: %zu", + content_store_hash_table_.size(), fifo_list_.size()); + } + + if (content_store_hash_table_.size() >= max_content_store_size_) { + content_store_hash_table_.erase(fifo_list_.back()); + fifo_list_.pop_back(); + } + + // Insert new item + auto it = content_store_hash_table_.find(content_object->getName()); + if (it != content_store_hash_table_.end()) { + fifo_list_.erase(it->second.second); + content_store_hash_table_.erase(content_object->getName()); + } + + fifo_list_.push_front(std::cref(content_object->getName())); + auto pos = fifo_list_.begin(); + content_store_hash_table_[content_object->getName()] = ContentStoreEntry( + ObjectTimeEntry(content_object, std::chrono::steady_clock::now()), pos); +} + +const std::shared_ptr ContentStore::find( + const Interest &interest) { + utils::SpinLock::Acquire locked(cs_mutex_); + + std::shared_ptr ret = empty_reference_; + auto it = content_store_hash_table_.find(interest.getName()); + if (it != content_store_hash_table_.end()) { + auto content_lifetime = it->second.first.first->getLifetime(); + auto time_passed_since_creation = + std::chrono::duration_cast( + std::chrono::steady_clock::now() - it->second.first.second) + .count(); + + if (time_passed_since_creation > content_lifetime) { + fifo_list_.erase(it->second.second); + content_store_hash_table_.erase(it); + } else { + ret = it->second.first.first; + } + } + + return ret; +} + +void ContentStore::erase(const Name &exact_name) { + utils::SpinLock::Acquire locked(cs_mutex_); + auto it = content_store_hash_table_.find(exact_name); + fifo_list_.erase(it->second.second); + content_store_hash_table_.erase(exact_name); +} + +void ContentStore::setLimit(size_t max_packets) { + utils::SpinLock::Acquire locked(cs_mutex_); + max_content_store_size_ = max_packets; +} + +std::size_t ContentStore::getLimit() const { + utils::SpinLock::Acquire locked(cs_mutex_); + return max_content_store_size_; +} + +std::size_t ContentStore::size() const { + utils::SpinLock::Acquire locked(cs_mutex_); + return content_store_hash_table_.size(); +} + +void ContentStore::printContent() { + for (auto &item : content_store_hash_table_) { + if (item.second.first.first->getPayloadType() == + transport::core::PayloadType::MANIFEST) { + TRANSPORT_LOGI("Manifest: %s", + item.second.first.first->getName().toString().c_str()); + } else { + TRANSPORT_LOGI("Data Packet: %s", + item.second.first.first->getName().toString().c_str()); + } + } +} + +} // end namespace utils \ No newline at end of file diff --git a/libtransport/src/utils/content_store.h b/libtransport/src/utils/content_store.h new file mode 100644 index 000000000..03ce76f42 --- /dev/null +++ b/libtransport/src/utils/content_store.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include + +#include + +namespace transport { + +namespace core { +class Name; +class ContentObject; +class Interest; +} // namespace core + +} // namespace transport + +namespace utils { + +using Name = transport::core::Name; +using ContentObject = transport::core::ContentObject; +using Interest = transport::core::Interest; + +typedef std::pair, + std::chrono::steady_clock::time_point> + ObjectTimeEntry; +typedef std::pair>::iterator> + ContentStoreEntry; +typedef std::list> FIFOList; +typedef std::unordered_map ContentStoreHashTable; + +class ContentStore { + public: + explicit ContentStore(std::size_t max_packets = (1 << 16)); + + ~ContentStore(); + + void insert(const std::shared_ptr &content_object); + + const std::shared_ptr find(const Interest &interest); + + void erase(const Name &exact_name); + + void setLimit(size_t max_packets); + + size_t getLimit() const; + + size_t size() const; + + void printContent(); + + private: + ContentStoreHashTable content_store_hash_table_; + FIFOList fifo_list_; + std::shared_ptr empty_reference_; + // Must be atomic + std::atomic_size_t max_content_store_size_; + mutable utils::SpinLock cs_mutex_; +}; + +} // end namespace utils \ No newline at end of file diff --git a/libtransport/src/utils/daemonizator.cc b/libtransport/src/utils/daemonizator.cc new file mode 100644 index 000000000..c51a68d14 --- /dev/null +++ b/libtransport/src/utils/daemonizator.cc @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#ifndef _WIN32 +#include +#include +#include + +#include +#include + +namespace utils { + +void Daemonizator::daemonize(bool close_fds) { + pid_t process_id = 0; + pid_t sid = 0; + + // Create child process + process_id = fork(); + + // Indication of fork() failure + if (process_id < 0) { + throw errors::RuntimeException("Fork failed."); + } + + // PARENT PROCESS. Need to kill it. + if (process_id > 0) { + TRANSPORT_LOGE("Process id of child process %d", process_id); + // return success in exit status + exit(EXIT_SUCCESS); + } + + // unmask the file mode + umask(0); + + // set new session + sid = setsid(); + if (sid < 0) { + // Return failure + exit(EXIT_FAILURE); + } + + // Change the current working directory to root. + int ret = chdir("/"); + + if (ret < 0) { + throw errors::RuntimeException("Error changing working directory to root"); + } + + // Close stdin. Redirect stdout and stderr to file if possible + + if (close_fds) { + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + close(STDIN_FILENO); + + // Really start application +} + +} // namespace utils + +#endif diff --git a/libtransport/src/utils/deadline_timer.h b/libtransport/src/utils/deadline_timer.h new file mode 100644 index 000000000..5187754f0 --- /dev/null +++ b/libtransport/src/utils/deadline_timer.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace std { +namespace chrono { +namespace detail { + +template +struct posix_duration_cast; + +// chrono -> timespec caster +template +struct posix_duration_cast, + struct timespec> { + static struct timespec cast(std::chrono::duration const &d) { + struct timespec tv; + + std::chrono::seconds const sec = + std::chrono::duration_cast(d); + + tv.tv_sec = sec.count(); + tv.tv_nsec = + std::chrono::duration_cast(d - sec).count(); + + return tv; + } +}; + +// timespec -> chrono caster +template +struct posix_duration_cast> { + static std::chrono::duration cast(struct timespec const &tv) { + return std::chrono::duration_cast>( + std::chrono::seconds(tv.tv_sec) + std::chrono::nanoseconds(tv.tv_nsec)); + } +}; + +} // namespace detail + +// chrono -> timespec +template +auto duration_cast(std::chrono::duration const &d) -> + typename std::enable_if::value, + struct timespec>::type { + return detail::posix_duration_cast, + timespec>::cast(d); +} + +// timespec -> chrono +template +Duration duration_cast(struct timespec const &tv) { + return detail::posix_duration_cast::cast(tv); +} + +} // namespace chrono +} // namespace std + +namespace utils { + +template +class DeadlineTimer { + public: + virtual ~DeadlineTimer() = default; + + template + void asyncWait(WaitHandler &&callback) { + static_cast(this)->asyncWaitImpl( + std::forward(callback)); + } + + void wait() { static_cast(this)->waitImpl(); } + + template + void expiresFromNow(std::chrono::duration &&duration) { + static_cast(this)->expiresFromNowImpl( + std::forward>(duration)); + } + + template , + std::chrono::steady_clock::time_point>::value, + TimePoint>::type> + void expiresAt(TimePoint &&time_point) { + static_cast(this)->expiresAtImpl( + std::forward(time_point)); + } + + void cancel() { static_cast(this)->cancelImpl(); } +}; + +} // namespace utils diff --git a/libtransport/src/utils/epoll_event_reactor.cc b/libtransport/src/utils/epoll_event_reactor.cc new file mode 100644 index 000000000..0e6590d0e --- /dev/null +++ b/libtransport/src/utils/epoll_event_reactor.cc @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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 + +#include +#include + +#include +#include +#include + +namespace utils { + +EpollEventReactor::EpollEventReactor() + : epoll_fd_(epoll_create(20000)), run_event_loop_(true) {} + +EpollEventReactor::~EpollEventReactor() { close(epoll_fd_); } + +int EpollEventReactor::addFileDescriptor(int fd, uint32_t events) { + if (TRANSPORT_EXPECT_FALSE(fd < 0)) { + TRANSPORT_LOGE("invalid fd %d", fd); + return -1; + } + + struct epoll_event evt; + std::memset(&evt, 0, sizeof(evt)); + evt.events = events; + evt.data.fd = fd; + + if (TRANSPORT_EXPECT_FALSE(epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &evt) < + 0)) { + TRANSPORT_LOGE("epoll_ctl: %s fd %d", strerror(errno), fd); + return -1; + } + + return 0; +} + +int EpollEventReactor::modFileDescriptor(int fd, uint32_t events) { + if (TRANSPORT_EXPECT_FALSE(fd < 0)) { + TRANSPORT_LOGE("invalid fd %d", fd); + return -1; + } + + struct epoll_event evt; + memset(&evt, 0, sizeof(evt)); + evt.events = events; + evt.data.fd = fd; + + if (TRANSPORT_EXPECT_FALSE(epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &evt) < + 0)) { + TRANSPORT_LOGE("epoll_ctl: %s fd %d", strerror(errno), fd); + return -1; + } + + return 0; +} + +std::size_t EpollEventReactor::mapSize() { return event_callback_map_.size(); } + +int EpollEventReactor::delFileDescriptor(int fd) { + if (TRANSPORT_EXPECT_FALSE(fd < 0)) { + TRANSPORT_LOGE("invalid fd %d", fd); + return -1; + } + + struct epoll_event evt; + memset(&evt, 0, sizeof(evt)); + + if (TRANSPORT_EXPECT_FALSE(epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, &evt) < + 0)) { + TRANSPORT_LOGE("epoll_ctl: %s fd %d", strerror(errno), fd); + return -1; + } + + utils::SpinLock::Acquire locked(event_callback_map_lock_); + event_callback_map_.erase(fd); + + return 0; +} + +void EpollEventReactor::runEventLoop(int timeout) { + Event evt[128]; + int en = 0; + EventCallbackMap::iterator it; + EventCallback callback; + + // evt.events = EPOLLIN | EPOLLOUT; + sigset_t sigset; + sigemptyset(&sigset); + + while (run_event_loop_) { + memset(&evt, 0, sizeof(evt)); + + en = epoll_pwait(epoll_fd_, evt, 128, timeout, &sigset); + + if (TRANSPORT_EXPECT_FALSE(en < 0)) { + TRANSPORT_LOGE("epoll_pwait: %s", strerror(errno)); + return; + } + + for (int i = 0; i < en; i++) { + if (evt[i].data.fd > 0) { + { + utils::SpinLock::Acquire locked(event_callback_map_lock_); + it = event_callback_map_.find(evt[i].data.fd); + } + + if (TRANSPORT_EXPECT_FALSE(it == event_callback_map_.end())) { + TRANSPORT_LOGE("unexpected event. fd %d", evt[i].data.fd); + } else { + { + utils::SpinLock::Acquire locked(event_callback_map_lock_); + callback = event_callback_map_[evt[i].data.fd]; + } + + callback(evt[i]); + + // In the callback the epoll event reactor could have been stopped, + // then we need to check whether the event loop is still running. + if (TRANSPORT_EXPECT_FALSE(!run_event_loop_)) { + return; + } + } + } else { + TRANSPORT_LOGE("unexpected event. fd %d", evt[i].data.fd); + } + } + } +} + +void EpollEventReactor::runOneEvent() { + Event evt; + int en = 0; + EventCallbackMap::iterator it; + EventCallback callback; + + // evt.events = EPOLLIN | EPOLLOUT; + sigset_t sigset; + sigemptyset(&sigset); + + memset(&evt, 0, sizeof(evt)); + + en = epoll_pwait(epoll_fd_, &evt, 1, -1, &sigset); + + if (TRANSPORT_EXPECT_FALSE(en < 0)) { + TRANSPORT_LOGE("epoll_pwait: %s", strerror(errno)); + return; + } + + if (TRANSPORT_EXPECT_TRUE(evt.data.fd > 0)) { + { + utils::SpinLock::Acquire locked(event_callback_map_lock_); + it = event_callback_map_.find(evt.data.fd); + } + + if (TRANSPORT_EXPECT_FALSE(it == event_callback_map_.end())) { + TRANSPORT_LOGE("unexpected event. fd %d", evt.data.fd); + } else { + { + utils::SpinLock::Acquire locked(event_callback_map_lock_); + callback = event_callback_map_[evt.data.fd]; + } + + callback(evt); + } + } else { + TRANSPORT_LOGE("unexpected event. fd %d", evt.data.fd); + } +} + +void EpollEventReactor::stop() { run_event_loop_ = false; } + +} // namespace utils \ No newline at end of file diff --git a/libtransport/src/utils/epoll_event_reactor.h b/libtransport/src/utils/epoll_event_reactor.h new file mode 100644 index 000000000..4cb87ebd4 --- /dev/null +++ b/libtransport/src/utils/epoll_event_reactor.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define FD_NUMBER 20000 + +namespace utils { + +typedef struct epoll_event Event; +typedef std::function EventCallback; +typedef std::unordered_map EventCallbackMap; + +class EpollEventReactor : public EventReactor { + public: + explicit EpollEventReactor(); + + ~EpollEventReactor(); + + template + int addFileDescriptor(int fd, uint32_t events, EventHandler &&callback) { + auto it = event_callback_map_.find(fd); + int ret = 0; + + if (it == event_callback_map_.end()) { + { + utils::SpinLock::Acquire locked(event_callback_map_lock_); + event_callback_map_[fd] = std::forward(callback); + } + + ret = addFileDescriptor(fd, events); + } + + return ret; + } + + int delFileDescriptor(int fd); + + int modFileDescriptor(int fd, uint32_t events); + + void runEventLoop(int timeout = -1) override; + + void runOneEvent() override; + + void stop() override; + + std::size_t mapSize(); + + private: + int addFileDescriptor(int fd, uint32_t events); + + int epoll_fd_; + std::atomic_bool run_event_loop_; + EventCallbackMap event_callback_map_; + utils::SpinLock event_callback_map_lock_; +}; + +} // namespace utils diff --git a/libtransport/src/utils/event_reactor.h b/libtransport/src/utils/event_reactor.h new file mode 100644 index 000000000..4f8b58296 --- /dev/null +++ b/libtransport/src/utils/event_reactor.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace utils { + +typedef std::function UserCallback; + +class EventReactor { + public: + virtual ~EventReactor() = default; + + virtual void runEventLoop(int timeout = -1) = 0; + + virtual void runOneEvent() = 0; + + virtual void stop() = 0; +}; + +} // namespace utils diff --git a/libtransport/src/utils/event_thread.h b/libtransport/src/utils/event_thread.h new file mode 100644 index 000000000..e50ae9648 --- /dev/null +++ b/libtransport/src/utils/event_thread.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace utils { + +class EventThread { + private: + // No copies + EventThread(const EventThread&) = delete; // non construction-copyable + EventThread& operator=(const EventThread&) = delete; // non copyable + + public: + explicit EventThread(asio::io_service& io_service) + : internal_io_service_(nullptr), + io_service_(io_service), + work_(io_service_), + thread_(nullptr) { + run(); + } + + explicit EventThread() + : internal_io_service_(std::make_unique()), + io_service_(*internal_io_service_), + work_(io_service_), + thread_(nullptr) { + run(); + } + + ~EventThread() { stop(); } + + void run() { + if (stopped()) { + io_service_.reset(); + } + + thread_ = std::make_unique([this]() { io_service_.run(); }); + } + + std::thread::id getThreadId() const { + if (thread_) { + return thread_->get_id(); + } else { + throw errors::RuntimeException("Event thread is not running."); + } + } + + template + void add(Func&& f) { + // If the function f + // TODO USe post in mac os, asio->post in xenial + io_service_.post(std::forward(f)); + } + + template + void tryRunHandlerNow(Func&& f) { + io_service_.dispatch(std::forward(f)); + } + + void stop() { + io_service_.stop(); + + if (thread_ && thread_->joinable()) { + thread_->join(); + } + + thread_.reset(); + } + + bool stopped() { return io_service_.stopped(); } + + asio::io_service& getIoService() { return io_service_; } + + private: + std::unique_ptr internal_io_service_; + asio::io_service& io_service_; + asio::io_service::work work_; + std::unique_ptr thread_; +}; + +} // namespace utils \ No newline at end of file diff --git a/libtransport/src/utils/fd_deadline_timer.h b/libtransport/src/utils/fd_deadline_timer.h new file mode 100644 index 000000000..8bc3bbca3 --- /dev/null +++ b/libtransport/src/utils/fd_deadline_timer.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +namespace utils { + +class FdDeadlineTimer : public DeadlineTimer { + public: + explicit FdDeadlineTimer(EpollEventReactor &reactor) + : reactor_(reactor), + timer_fd_(timerfd_create(CLOCK_MONOTONIC, 0)), + flags_(0) { + if (timer_fd_ == -1) { + throw errors::RuntimeException("Impossible to create the timer!"); + } + } + + ~FdDeadlineTimer() { close(timer_fd_); } + + template + void asyncWaitImpl(WaitHandler &&callback) { + // ASIO_WAIT_HANDLER_CHECK(WaitHandler, callback) type_check; + + if (timerfd_settime(timer_fd_, flags_, &new_value_, NULL) == -1) { + throw errors::RuntimeException("Impossible to set the timer!"); + } + + uint32_t events = EPOLLIN; + + reactor_.addFileDescriptor( + timer_fd_, events, + [callback = std::forward(callback)]( + const Event &event) -> int { + uint64_t s = 0; + std::error_code ec; + + if (read(event.data.fd, &s, sizeof(s)) == -1) { + TRANSPORT_LOGE("Read error!!"); + } + + if (!(event.events & EPOLLIN)) { + ec = std::make_error_code(std::errc::operation_canceled); + } + + callback(ec); + + return 0; + }); + } + + void waitImpl() { + if (timerfd_settime(timer_fd_, flags_, &new_value_, NULL) == -1) { + throw errors::RuntimeException("Impossible to set the timer!"); + } + + uint64_t ret; + + if (read(timer_fd_, &ret, sizeof(ret)) == -1) { + throw errors::RuntimeException( + "Error while waiting for the timer expiration."); + } + } + + template + void expiresFromNowImpl(std::chrono::duration &&duration) { + std::memset(&new_value_, 0, sizeof(new_value_)); + new_value_.it_value = std::chrono::duration_cast( + std::forward>(duration)); + } + + template , + std::chrono::steady_clock::time_point>::value, + TimePoint>> + void expiresAtImpl(TimePoint &&time_point) { + std::memset(&new_value_, 0, sizeof(new_value_)); + + new_value_.it_value = std::chrono::duration_cast( + time_point.time_since_epoch()); + flags_ |= TFD_TIMER_ABSTIME; + } + + void cancelImpl() { + std::memset(&new_value_, 0, sizeof(new_value_)); + + if (timerfd_settime(timer_fd_, 0, &new_value_, NULL) == -1) { + throw errors::RuntimeException("Impossible to cancel the timer!"); + } + + // reactor_.delFileDescriptor(timer_fd_); + } + + EventReactor &getEventReactor() { return reactor_; } + + private: + EpollEventReactor &reactor_; + int timer_fd_; + EventCallback callback_; + struct itimerspec new_value_; + int flags_; +}; + +} // namespace utils diff --git a/libtransport/src/utils/log.cc b/libtransport/src/utils/log.cc new file mode 100644 index 000000000..27dd3f541 --- /dev/null +++ b/libtransport/src/utils/log.cc @@ -0,0 +1,1403 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 wonder-mice + * + * 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. + */ + +/* When defined, Android log (android/log.h) will be used by default instead of + * stderr (ignored on non-Android platforms). Date, time, pid and tid (context) + * will be provided by Android log. Android log features will be used to output + * log level and tag. + */ + +#if defined(__ANDROID__) +#define TRANSPORT_LOG_USE_ANDROID_LOG 1 +#define ANDROID_TAG "HicnTransport" +#else +#define TRANSPORT_LOG_USE_ANDROID_LOG 0 +#endif + +/* When defined, NSLog (uses Apple System Log) will be used instead of stderr + * (ignored on non-Apple platforms). Date, time, pid and tid (context) will be + * provided by NSLog. Curiously, doesn't use NSLog() directly, but piggybacks on + * non-public CFLog() function. Both use Apple System Log internally, but it's + * easier to call CFLog() from C than NSLog(). Current implementation doesn't + * support "%@" format specifier. + */ +#ifdef TRANSPORT_LOG_USE_NSLOG +#undef TRANSPORT_LOG_USE_NSLOG +#if defined(__APPLE__) && defined(__MACH__) +#define TRANSPORT_LOG_USE_NSLOG 1 +#else +#define TRANSPORT_LOG_USE_NSLOG 0 +#endif +#else +#define TRANSPORT_LOG_USE_NSLOG 0 +#endif +/* When defined, OutputDebugString() will be used instead of stderr (ignored on + * non-Windows platforms). Uses OutputDebugStringA() variant and feeds it with + * UTF-8 data. + */ +#ifdef TRANSPORT_LOG_USE_DEBUGSTRING +#undef TRANSPORT_LOG_USE_DEBUGSTRING +#if defined(_WIN32) || defined(_WIN64) +#define TRANSPORT_LOG_USE_DEBUGSTRING 1 +#else +#define TRANSPORT_LOG_USE_DEBUGSTRING 0 +#endif +#else +#define TRANSPORT_LOG_USE_DEBUGSTRING 0 +#endif +/* When defined, TRANSPORT_LOG library will not contain definition of tag prefix + * variable. In that case it must be defined elsewhere using + * TRANSPORT_LOG_DEFINE_TAG_PREFIX macro, for example: + * + * TRANSPORT_LOG_DEFINE_TAG_PREFIX = "ProcessName"; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef TRANSPORT_LOG_EXTERN_TAG_PREFIX +#undef TRANSPORT_LOG_EXTERN_TAG_PREFIX +#define TRANSPORT_LOG_EXTERN_TAG_PREFIX 1 +#else +#define TRANSPORT_LOG_EXTERN_TAG_PREFIX 0 +#endif +/* When defined, TRANSPORT_LOG library will not contain definition of global + * format variable. In that case it must be defined elsewhere using + * TRANSPORT_LOG_DEFINE_GLOBAL_FORMAT macro, for example: + * + * TRANSPORT_LOG_DEFINE_GLOBAL_FORMAT = {MEM_WIDTH}; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef TRANSPORT_LOG_EXTERN_GLOBAL_FORMAT +#undef TRANSPORT_LOG_EXTERN_GLOBAL_FORMAT +#define TRANSPORT_LOG_EXTERN_GLOBAL_FORMAT 1 +#else +#define TRANSPORT_LOG_EXTERN_GLOBAL_FORMAT 0 +#endif +/* When defined, transport_log library will not contain definition of global + * output variable. In that case it must be defined elsewhere using + * TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT macro, for example: + * + * TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT = {TRANSPORT_LOG_PUT_STD, + * custom_output_callback}; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT +#undef TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT +#define TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT 1 +#else +#define TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT 0 +#endif +/* When defined, transport_log library will not contain definition of global + * output level variable. In that case it must be defined elsewhere using + * TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL macro, for example: + * + * TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = TRANSPORT_LOG_WARN; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL +#undef TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL +#define TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL 1 +#else +#define TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL 0 +#endif +/* When defined, implementation will prefer smaller code size over speed. + * Very rough estimate is that code will be up to 2x smaller and up to 2x + * slower. Disabled by default. + */ +#ifdef TRANSPORT_LOG_OPTIMIZE_SIZE +#undef TRANSPORT_LOG_OPTIMIZE_SIZE +#define TRANSPORT_LOG_OPTIMIZE_SIZE 1 +#else +#define TRANSPORT_LOG_OPTIMIZE_SIZE 0 +#endif +/* Size of the log line buffer. The buffer is allocated on stack. It limits + * maximum length of a log line. + */ +#ifndef TRANSPORT_LOG_BUF_SZ +#define TRANSPORT_LOG_BUF_SZ 512 +#endif +/* Default number of bytes in one line of memory output. For large values + * TRANSPORT_LOG_BUF_SZ also must be increased. + */ +#ifndef TRANSPORT_LOG_MEM_WIDTH +#define TRANSPORT_LOG_MEM_WIDTH 32 +#endif +/* String to put in the end of each log line (can be empty). Its value used by + * stderr output callback. Its size used as a default value for + * TRANSPORT_LOG_EOL_SZ. + */ +#ifndef TRANSPORT_LOG_EOL +#define TRANSPORT_LOG_EOL "\n" +#endif +/* Default delimiter that separates parts of log message. Can NOT contain '%' + * or '\0'. + * + * Log message format specifications can override (or ignore) this value. For + * more details see TRANSPORT_LOG_MESSAGE_CTX_FORMAT, + * TRANSPORT_LOG_MESSAGE_SRC_FORMAT and TRANSPORT_LOG_MESSAGE_TAG_FORMAT. + */ +#ifndef TRANSPORT_LOG_DEF_DELIMITER +#define TRANSPORT_LOG_DEF_DELIMITER " " +#endif +/* Specifies log message context format. Log message context includes date, + * time, process id, thread id and message's log level. Custom information can + * be added as well. Supported fields: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, + * MILLISECOND, PID, TID, LEVEL, S(str), F_INIT(statements), + * F_UINT(width, value). + * + * Must be defined as a tuple, for example: + * + * #define TRANSPORT_LOG_MESSAGE_CTX_FORMAT (YEAR, S("."), MONTH, S("."), DAY, + * S(" > ")) + * + * In that case, resulting log message will be: + * + * 2016.12.22 > TAG function@filename.c:line Message text + * + * Note, that tag, source location and message text are not impacted by + * this setting. See TRANSPORT_LOG_MESSAGE_TAG_FORMAT and + * TRANSPORT_LOG_MESSAGE_SRC_FORMAT. + * + * If message context must be visually separated from the rest of the message, + * it must be reflected in context format (notice trailing S(" > ") in the + * example above). + * + * S(str) adds constant string str. String can NOT contain '%' or '\0'. + * + * F_INIT(statements) adds initialization statement(s) that will be evaluated + * once for each log message. All statements are evaluated in specified order. + * Several F_INIT() fields can be used in every log message format + * specification. Fields, like F_UINT(width, value), are allowed to use results + * of initialization statements. If statement introduces variables (or other + * names, like structures) they must be prefixed with "f_". Statements must be + * enclosed into additional "()". Example: + * + * #define TRANSPORT_LOG_MESSAGE_CTX_FORMAT \ + * (F_INIT(( struct rusage f_ru; getrusage(RUSAGE_SELF, &f_ru); )), \ + * YEAR, S("."), MONTH, S("."), DAY, S(" "), \ + * F_UINT(5, f_ru.ru_nsignals), \ + * S(" ")) + * + * F_UINT(width, value) adds unsigned integer value extended with up to width + * spaces (for alignment purposes). Value can be any expression that evaluates + * to unsigned integer. If expression contains non-standard functions, they + * must be declared with F_INIT(). Example: + * + * #define TRANSPORT_LOG_MESSAGE_CTX_FORMAT \ + * (YEAR, S("."), MONTH, S("."), DAY, S(" "), \ + * F_INIT(( unsigned tickcount(); )), \ + * F_UINT(5, tickcount()), \ + * S(" ")) + * + * Other log message format specifications follow same rules, but have a + * different set of supported fields. + */ +#ifndef TRANSPORT_LOG_MESSAGE_CTX_FORMAT +#define TRANSPORT_LOG_MESSAGE_CTX_FORMAT \ + (MONTH, S("-"), DAY, S(TRANSPORT_LOG_DEF_DELIMITER), HOUR, S(":"), MINUTE, \ + S(":"), SECOND, S("."), MILLISECOND, S(TRANSPORT_LOG_DEF_DELIMITER), PID, \ + S(TRANSPORT_LOG_DEF_DELIMITER), TID, S(TRANSPORT_LOG_DEF_DELIMITER), LEVEL, \ + S(TRANSPORT_LOG_DEF_DELIMITER)) +#endif +/* Example: + */ +/* Specifies log message tag format. It includes tag prefix and tag. Custom + * information can be added as well. Supported fields: + * TAG(prefix_delimiter, tag_delimiter), S(str), F_INIT(statements), + * F_UINT(width, value). + * + * TAG(prefix_delimiter, tag_delimiter) adds following string to log message: + * + * PREFIXTAG + * + * Prefix delimiter will be used only when prefix is not empty. Tag delimiter + * will be used only when prefixed tag is not empty. Example: + * + * #define TRANSPORT_LOG_TAG_FORMAT (S("["), TAG(".", ""), S("] ")) + * + * See TRANSPORT_LOG_MESSAGE_CTX_FORMAT for details. + */ +#ifndef TRANSPORT_LOG_MESSAGE_TAG_FORMAT +#define TRANSPORT_LOG_MESSAGE_TAG_FORMAT (TAG(".", TRANSPORT_LOG_DEF_DELIMITER)) +#endif +/* Specifies log message source location format. It includes function name, + * file name and file line. Custom information can be added as well. Supported + * fields: FUNCTION, FILENAME, FILELINE, S(str), F_INIT(statements), + * F_UINT(width, value). + * + * See TRANSPORT_LOG_MESSAGE_CTX_FORMAT for details. + */ +#ifndef TRANSPORT_LOG_MESSAGE_SRC_FORMAT +#define TRANSPORT_LOG_MESSAGE_SRC_FORMAT \ + (FUNCTION, S("@"), FILENAME, S(":"), FILELINE, S(TRANSPORT_LOG_DEF_DELIMITER)) +#endif +/* Fields that can be used in log message format specifications (see above). + * Mentioning them here explicitly, so we know that nobody else defined them + * before us. See TRANSPORT_LOG_MESSAGE_CTX_FORMAT for details. + */ +#define YEAR YEAR +#define MONTH MONTH +#define DAY DAY +#define MINUTE MINUTE +#define SECOND SECOND +#define MILLISECOND MILLISECOND +#define PID PID +#define TID TID +#define LEVEL LEVEL +#define TAG(prefix_delim, tag_delim) TAG(prefix_delim, tag_delim) +#define FUNCTION FUNCTION +#define FILENAME FILENAME +#define FILELINE FILELINE +#define S(str) S(str) +#define F_INIT(statements) F_INIT(statements) +#define F_UINT(width, value) F_UINT(width, value) +/* Number of bytes to reserve for EOL in the log line buffer (must be >0). + * Must be larger than or equal to length of TRANSPORT_LOG_EOL with terminating + * null. + */ +#ifndef TRANSPORT_LOG_EOL_SZ +#define TRANSPORT_LOG_EOL_SZ sizeof(TRANSPORT_LOG_EOL) +#endif +/* Compile instrumented version of the library to facilitate unit testing. + */ +#ifndef TRANSPORT_LOG_INSTRUMENTED +#define TRANSPORT_LOG_INSTRUMENTED 0 +#endif + +#if defined(__linux__) +#if !defined(__ANDROID__) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +#endif +#if defined(__MINGW32__) +#ifdef __STRICT_ANSI__ +#undef __STRICT_ANSI__ +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#include +#if defined(__linux__) +#include +#else +#include +#endif +#endif + +#if defined(__linux__) +#include +#include +#if !defined(__ANDROID__) +#include +#endif +#endif +#if defined(__MACH__) +#include +#endif + +#define INLINE _TRANSPORT_LOG_INLINE +#define VAR_UNUSED(var) (void)var +#define RETVAL_UNUSED(expr) \ + do { \ + while (expr) break; \ + } while (0) +#define STATIC_ASSERT(name, cond) typedef char assert_##name[(cond) ? 1 : -1] +#define ASSERT_UNREACHABLE(why) assert(!sizeof(why)) +#ifndef _countof +#define _countof(xs) (sizeof(xs) / sizeof((xs)[0])) +#endif + +#if TRANSPORT_LOG_INSTRUMENTED +#define INSTRUMENTED_CONST +#else +#define INSTRUMENTED_CONST const +#endif + +#define _PP_PASTE_2(a, b) a##b +#define _PP_CONCAT_2(a, b) _PP_PASTE_2(a, b) + +#define _PP_PASTE_3(a, b, c) a##b##c +#define _PP_CONCAT_3(a, b, c) _PP_PASTE_3(a, b, c) + +/* Microsoft C preprocessor is a piece of shit. This moron treats __VA_ARGS__ + * as a single token and requires additional expansion to realize that it's + * actually a list. If not for it, there would be no need in this extra + * expansion. + */ +#define _PP_ID(x) x +#define _PP_NARGS_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \ + _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \ + _24, ...) \ + _24 +#define _PP_NARGS(...) \ + _PP_ID(_PP_NARGS_N(__VA_ARGS__, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, \ + 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) + +/* There is a more efficient way to implement this, but it requires + * working C preprocessor. Unfortunately, Microsoft Visual Studio doesn't + * have one. + */ +#define _PP_HEAD__(x, ...) x +#define _PP_HEAD_(...) _PP_ID(_PP_HEAD__(__VA_ARGS__, ~)) +#define _PP_HEAD(xs) _PP_HEAD_ xs +#define _PP_TAIL_(x, ...) (__VA_ARGS__) +#define _PP_TAIL(xs) _PP_TAIL_ xs +#define _PP_UNTUPLE_(...) __VA_ARGS__ +#define _PP_UNTUPLE(xs) _PP_UNTUPLE_ xs + +/* Apply function macro to each element in tuple. Output is not + * enforced to be a tuple. + */ +#define _PP_MAP_1(f, xs) f(_PP_HEAD(xs)) +#define _PP_MAP_2(f, xs) f(_PP_HEAD(xs)) _PP_MAP_1(f, _PP_TAIL(xs)) +#define _PP_MAP_3(f, xs) f(_PP_HEAD(xs)) _PP_MAP_2(f, _PP_TAIL(xs)) +#define _PP_MAP_4(f, xs) f(_PP_HEAD(xs)) _PP_MAP_3(f, _PP_TAIL(xs)) +#define _PP_MAP_5(f, xs) f(_PP_HEAD(xs)) _PP_MAP_4(f, _PP_TAIL(xs)) +#define _PP_MAP_6(f, xs) f(_PP_HEAD(xs)) _PP_MAP_5(f, _PP_TAIL(xs)) +#define _PP_MAP_7(f, xs) f(_PP_HEAD(xs)) _PP_MAP_6(f, _PP_TAIL(xs)) +#define _PP_MAP_8(f, xs) f(_PP_HEAD(xs)) _PP_MAP_7(f, _PP_TAIL(xs)) +#define _PP_MAP_9(f, xs) f(_PP_HEAD(xs)) _PP_MAP_8(f, _PP_TAIL(xs)) +#define _PP_MAP_10(f, xs) f(_PP_HEAD(xs)) _PP_MAP_9(f, _PP_TAIL(xs)) +#define _PP_MAP_11(f, xs) f(_PP_HEAD(xs)) _PP_MAP_10(f, _PP_TAIL(xs)) +#define _PP_MAP_12(f, xs) f(_PP_HEAD(xs)) _PP_MAP_11(f, _PP_TAIL(xs)) +#define _PP_MAP_13(f, xs) f(_PP_HEAD(xs)) _PP_MAP_12(f, _PP_TAIL(xs)) +#define _PP_MAP_14(f, xs) f(_PP_HEAD(xs)) _PP_MAP_13(f, _PP_TAIL(xs)) +#define _PP_MAP_15(f, xs) f(_PP_HEAD(xs)) _PP_MAP_14(f, _PP_TAIL(xs)) +#define _PP_MAP_16(f, xs) f(_PP_HEAD(xs)) _PP_MAP_15(f, _PP_TAIL(xs)) +#define _PP_MAP_17(f, xs) f(_PP_HEAD(xs)) _PP_MAP_16(f, _PP_TAIL(xs)) +#define _PP_MAP_18(f, xs) f(_PP_HEAD(xs)) _PP_MAP_17(f, _PP_TAIL(xs)) +#define _PP_MAP_19(f, xs) f(_PP_HEAD(xs)) _PP_MAP_18(f, _PP_TAIL(xs)) +#define _PP_MAP_20(f, xs) f(_PP_HEAD(xs)) _PP_MAP_19(f, _PP_TAIL(xs)) +#define _PP_MAP_21(f, xs) f(_PP_HEAD(xs)) _PP_MAP_20(f, _PP_TAIL(xs)) +#define _PP_MAP_22(f, xs) f(_PP_HEAD(xs)) _PP_MAP_21(f, _PP_TAIL(xs)) +#define _PP_MAP_23(f, xs) f(_PP_HEAD(xs)) _PP_MAP_22(f, _PP_TAIL(xs)) +#define _PP_MAP_24(f, xs) f(_PP_HEAD(xs)) _PP_MAP_23(f, _PP_TAIL(xs)) +#define _PP_MAP(f, xs) _PP_CONCAT_2(_PP_MAP_, _PP_NARGS xs)(f, xs) + +/* Apply function macro to each element in tuple in reverse order. + * Output is not enforced to be a tuple. + */ +#define _PP_RMAP_1(f, xs) f(_PP_HEAD(xs)) +#define _PP_RMAP_2(f, xs) _PP_RMAP_1(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_3(f, xs) _PP_RMAP_2(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_4(f, xs) _PP_RMAP_3(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_5(f, xs) _PP_RMAP_4(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_6(f, xs) _PP_RMAP_5(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_7(f, xs) _PP_RMAP_6(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_8(f, xs) _PP_RMAP_7(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_9(f, xs) _PP_RMAP_8(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_10(f, xs) _PP_RMAP_9(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_11(f, xs) _PP_RMAP_10(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_12(f, xs) _PP_RMAP_11(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_13(f, xs) _PP_RMAP_12(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_14(f, xs) _PP_RMAP_13(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_15(f, xs) _PP_RMAP_14(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_16(f, xs) _PP_RMAP_15(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_17(f, xs) _PP_RMAP_16(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_18(f, xs) _PP_RMAP_17(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_19(f, xs) _PP_RMAP_18(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_20(f, xs) _PP_RMAP_19(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_21(f, xs) _PP_RMAP_20(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_22(f, xs) _PP_RMAP_21(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_23(f, xs) _PP_RMAP_22(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_24(f, xs) _PP_RMAP_23(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP(f, xs) _PP_CONCAT_2(_PP_RMAP_, _PP_NARGS xs)(f, xs) + +/* Used to implement _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS() macro. All + * possible fields must be mentioned here. Not counting F_INIT() here because + * it's somewhat special and is handled spearatly (at least for now). + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__ (0 << 0) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__YEAR (1 << 1) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__MONTH (1 << 2) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__DAY (1 << 3) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__HOUR (1 << 4) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__MINUTE (1 << 5) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__SECOND (1 << 6) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__MILLISECOND (1 << 7) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__PID (1 << 8) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__TID (1 << 9) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__LEVEL (1 << 10) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__TAG(ps, ts) (1 << 11) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__FUNCTION (1 << 12) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__FILENAME (1 << 13) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__FILELINE (1 << 14) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__S(s) (1 << 15) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__F_INIT(expr) (0 << 16) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK__F_UINT(w, v) (1 << 17) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_MASK(field) \ + _PP_CONCAT_3(_TRANSPORT_LOG_MESSAGE_FORMAT_MASK_, _, field) + +/* Logical "or" of masks of fields used in specified format specification. + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_FIELDS(format) \ + (0 _PP_MAP(| _TRANSPORT_LOG_MESSAGE_FORMAT_MASK, format)) + +/* Expands to expressions that evaluates to true if field is used in + * specified format specification. Example: + * + * #if _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(F_UINT, + * TRANSPORT_LOG_MESSAGE_CTX_FORMAT) + * ... + * #endif + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(field, format) \ + (_TRANSPORT_LOG_MESSAGE_FORMAT_MASK(field) & \ + _TRANSPORT_LOG_MESSAGE_FORMAT_FIELDS(format)) + +/* Same, but checks all supported format specifications. + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_FIELD_USED(field) \ + (_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(field, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(field, \ + TRANSPORT_LOG_MESSAGE_TAG_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(field, \ + TRANSPORT_LOG_MESSAGE_SRC_FORMAT)) + +#define _TRANSPORT_LOG_MESSAGE_FORMAT_DATETIME_USED \ + (_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(YEAR, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(MONTH, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(DAY, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(HOUR, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(MINUTE, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(SECOND, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(MILLISECOND, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT)) + +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) +#pragma warning(disable : 4204) /* nonstandard extension used: non-constant \ + aggregate initializer */ +#define memccpy _memccpy +#endif + +#if (defined(_MSC_VER) && !defined(__INTEL_COMPILER)) || defined(__MINGW64__) +#define vsnprintf(s, sz, fmt, va) fake_vsnprintf(s, sz, fmt, va) +static int fake_vsnprintf(char *s, size_t sz, const char *fmt, va_list ap) { + const int n = vsnprintf_s(s, sz, _TRUNCATE, fmt, ap); + return 0 < n ? n : (int)sz + 1; /* no need in _vscprintf() for now */ +} +#if TRANSPORT_LOG_OPTIMIZE_SIZE +#define snprintf(s, sz, ...) fake_snprintf(s, sz, __VA_ARGS__) +static int fake_snprintf(char *s, size_t sz, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + const int n = fake_vsnprintf(s, sz, fmt, va); + va_end(va); + return n; +} +#endif +#endif + +typedef void (*time_cb)(struct tm *const tm, unsigned *const usec); +typedef void (*pid_cb)(int *const pid, int *const tid); +typedef void (*buffer_cb)(transport_log_message *msg, char *buf); + +typedef struct src_location { + const char *const func; + const char *const file; + const unsigned line; +} src_location; + +typedef struct mem_block { + const void *const d; + const unsigned d_sz; +} mem_block; + +static void time_callback(struct tm *const tm, unsigned *const usec); +static void pid_callback(int *const pid, int *const tid); +static void buffer_callback(transport_log_message *msg, char *buf); + +STATIC_ASSERT(eol_fits_eol_sz, + sizeof(TRANSPORT_LOG_EOL) <= TRANSPORT_LOG_EOL_SZ); +STATIC_ASSERT(eol_sz_greater_than_zero, 0 < TRANSPORT_LOG_EOL_SZ); +STATIC_ASSERT(eol_sz_less_than_buf_sz, + TRANSPORT_LOG_EOL_SZ < TRANSPORT_LOG_BUF_SZ); +#if !defined(_WIN32) && !defined(_WIN64) +STATIC_ASSERT(buf_sz_less_than_pipe_buf, TRANSPORT_LOG_BUF_SZ <= PIPE_BUF); +#endif +static const char c_hex[] = "0123456789abcdef"; + +static INSTRUMENTED_CONST unsigned g_buf_sz = + TRANSPORT_LOG_BUF_SZ - TRANSPORT_LOG_EOL_SZ; +static INSTRUMENTED_CONST time_cb g_time_cb = time_callback; +static INSTRUMENTED_CONST pid_cb g_pid_cb = pid_callback; +static INSTRUMENTED_CONST buffer_cb g_buffer_cb = buffer_callback; + +#if TRANSPORT_LOG_USE_ANDROID_LOG +#include + +static INLINE int android_lvl(const int lvl) { + switch (lvl) { + case TRANSPORT_LOG_VERBOSE: + return ANDROID_LOG_VERBOSE; + case TRANSPORT_LOG_DEBUG: + return ANDROID_LOG_DEBUG; + case TRANSPORT_LOG_INFO: + return ANDROID_LOG_INFO; + case TRANSPORT_LOG_WARN: + return ANDROID_LOG_WARN; + case TRANSPORT_LOG_ERROR: + return ANDROID_LOG_ERROR; + case TRANSPORT_LOG_FATAL: + return ANDROID_LOG_FATAL; + default: + ASSERT_UNREACHABLE("Bad log level"); + return ANDROID_LOG_UNKNOWN; + } +} + +static void out_android_callback(const transport_log_message *const msg, + void *arg) { + VAR_UNUSED(arg); + *msg->p = 0; + const char *tag = msg->p; + if (msg->tag_e != msg->tag_b) { + tag = msg->tag_b; + *msg->tag_e = 0; + } + __android_log_print(android_lvl(msg->lvl), ANDROID_TAG, "%s", msg->msg_b); +} + +enum { OUT_ANDROID_MASK = TRANSPORT_LOG_PUT_STD & ~TRANSPORT_LOG_PUT_CTX }; +#define OUT_ANDROID OUT_ANDROID_MASK, 0, out_android_callback +#endif + +#if TRANSPORT_LOG_USE_NSLOG +#include +CF_EXPORT void CFLog(int32_t level, CFStringRef format, ...); + +static INLINE int apple_lvl(const int lvl) { + switch (lvl) { + case TRANSPORT_LOG_VERBOSE: + return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */ + ; + case TRANSPORT_LOG_DEBUG: + return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */ + ; + case TRANSPORT_LOG_INFO: + return 6; /* ASL_LEVEL_INFO / kCFLogLevelInfo */ + ; + case TRANSPORT_LOG_WARN: + return 4; /* ASL_LEVEL_WARNING / kCFLogLevelWarning */ + ; + case TRANSPORT_LOG_ERROR: + return 3; /* ASL_LEVEL_ERR / kCFLogLevelError */ + ; + case TRANSPORT_LOG_FATAL: + return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */ + ; + default: + ASSERT_UNREACHABLE("Bad log level"); + return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */ + ; + } +} + +static void out_nslog_callback(const transport_log_message *const msg, + void *arg) { + VAR_UNUSED(arg); + *msg->p = 0; + CFLog(apple_lvl(msg->lvl), CFSTR("%s"), msg->tag_b); +} + +enum { OUT_NSLOG_MASK = TRANSPORT_LOG_PUT_STD & ~TRANSPORT_LOG_PUT_CTX }; +#define OUT_NSLOG OUT_NSLOG_MASK, 0, out_nslog_callback +#endif + +#if TRANSPORT_LOG_USE_DEBUGSTRING +#include + +static void out_debugstring_callback(const transport_log_message *const msg, + void *arg) { + VAR_UNUSED(arg); + msg->p[0] = '\n'; + msg->p[1] = '\0'; + OutputDebugStringA(msg->buf); +} + +enum { OUT_DEBUGSTRING_MASK = TRANSPORT_LOG_PUT_STD }; +#define OUT_DEBUGSTRING OUT_DEBUGSTRING_MASK, 0, out_debugstring_callback +#endif + +void transport_log_out_stderr_callback(const transport_log_message *const msg, + void *arg) { + VAR_UNUSED(arg); + const size_t eol_len = sizeof(TRANSPORT_LOG_EOL) - 1; + memcpy(msg->p, TRANSPORT_LOG_EOL, eol_len); +#if defined(_WIN32) || defined(_WIN64) + /* WriteFile() is atomic for local files opened with FILE_APPEND_DATA and + without FILE_WRITE_DATA */ + DWORD written; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg->buf, + (DWORD)(msg->p - msg->buf + eol_len), &written, 0); +#else + /* write() is atomic for buffers less than or equal to PIPE_BUF. */ + RETVAL_UNUSED( + write(STDERR_FILENO, msg->buf, (size_t)(msg->p - msg->buf) + eol_len)); +#endif +} + +static const transport_log_output out_stderr = {TRANSPORT_LOG_OUT_STDERR}; + +#if !TRANSPORT_LOG_EXTERN_TAG_PREFIX +TRANSPORT_LOG_DEFINE_TAG_PREFIX = 0; +#endif + +#if !TRANSPORT_LOG_EXTERN_GLOBAL_FORMAT +TRANSPORT_LOG_DEFINE_GLOBAL_FORMAT = {TRANSPORT_LOG_MEM_WIDTH}; +#endif + +#if !TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT +#if TRANSPORT_LOG_USE_ANDROID_LOG +TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_ANDROID}; +#elif TRANSPORT_LOG_USE_NSLOG +TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_NSLOG}; +#elif TRANSPORT_LOG_USE_DEBUGSTRING +TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_DEBUGSTRING}; +#else +TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT = {TRANSPORT_LOG_OUT_STDERR}; +#endif +#endif + +#if !TRANSPORT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL +TRANSPORT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = 0; +#endif + +const transport_log_spec _transport_log_stderr_spec = { + TRANSPORT_LOG_GLOBAL_FORMAT, + &out_stderr, +}; + +static const transport_log_spec global_spec = { + TRANSPORT_LOG_GLOBAL_FORMAT, + TRANSPORT_LOG_GLOBAL_OUTPUT, +}; + +#if _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(LEVEL, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) +static char lvl_char(const int lvl) { + switch (lvl) { + case TRANSPORT_LOG_VERBOSE: + return 'V'; + case TRANSPORT_LOG_DEBUG: + return 'D'; + case TRANSPORT_LOG_INFO: + return 'I'; + case TRANSPORT_LOG_WARN: + return 'W'; + case TRANSPORT_LOG_ERROR: + return 'E'; + case TRANSPORT_LOG_FATAL: + return 'F'; + default: + ASSERT_UNREACHABLE("Bad log level"); + return '?'; + } +} +#endif + +#define GCCVER_LESS(MAJOR, MINOR, PATCH) \ + (__GNUC__ < MAJOR || (__GNUC__ == MAJOR && (__GNUC_MINOR__ < MINOR || \ + (__GNUC_MINOR__ == MINOR && \ + __GNUC_PATCHLEVEL__ < PATCH)))) + +#if !defined(__clang__) && defined(__GNUC__) && GCCVER_LESS(4, 7, 0) +#define __atomic_load_n(vp, model) __sync_fetch_and_add(vp, 0) +#define __atomic_fetch_add(vp, n, model) __sync_fetch_and_add(vp, n) +#define __atomic_sub_fetch(vp, n, model) __sync_sub_and_fetch(vp, n) +#define __atomic_or_fetch(vp, n, model) __sync_or_and_fetch(vp, n) +#define __atomic_and_fetch(vp, n, model) __sync_and_and_fetch(vp, n) +/* Note: will not store old value of *vp in *ep (non-standard behaviour) */ +#define __atomic_compare_exchange_n(vp, ep, d, weak, smodel, fmodel) \ + __sync_bool_compare_and_swap(vp, *(ep), d) +#endif + +#if !TRANSPORT_LOG_OPTIMIZE_SIZE && !defined(_WIN32) && !defined(_WIN64) +#define TCACHE +#define TCACHE_STALE (0x40000000) +#define TCACHE_FLUID (0x40000000 | 0x80000000) +static unsigned g_tcache_mode = TCACHE_STALE; +static struct timeval g_tcache_tv = {0, 0}; +static struct tm g_tcache_tm = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static INLINE int tcache_get(const struct timeval *const tv, + struct tm *const tm) { + unsigned mode; + mode = __atomic_load_n(&g_tcache_mode, __ATOMIC_RELAXED); + if (0 == (mode & TCACHE_FLUID)) { + mode = __atomic_fetch_add(&g_tcache_mode, 1, __ATOMIC_ACQUIRE); + if (0 == (mode & TCACHE_FLUID)) { + if (g_tcache_tv.tv_sec == tv->tv_sec) { + *tm = g_tcache_tm; + __atomic_sub_fetch(&g_tcache_mode, 1, __ATOMIC_RELEASE); + return !0; + } + __atomic_or_fetch(&g_tcache_mode, TCACHE_STALE, __ATOMIC_RELAXED); + } + __atomic_sub_fetch(&g_tcache_mode, 1, __ATOMIC_RELEASE); + } + return 0; +} + +static INLINE void tcache_set(const struct timeval *const tv, + struct tm *const tm) { + unsigned stale = TCACHE_STALE; + if (__atomic_compare_exchange_n(&g_tcache_mode, &stale, TCACHE_FLUID, 0, + __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { + g_tcache_tv = *tv; + g_tcache_tm = *tm; + __atomic_and_fetch(&g_tcache_mode, ~TCACHE_FLUID, __ATOMIC_RELEASE); + } +} +#endif + +static void time_callback(struct tm *const tm, unsigned *const msec) { +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_DATETIME_USED + VAR_UNUSED(tm); + VAR_UNUSED(msec); +#else +#if defined(_WIN32) || defined(_WIN64) + SYSTEMTIME st; + GetLocalTime(&st); + tm->tm_year = st.wYear; + tm->tm_mon = st.wMonth - 1; + tm->tm_mday = st.wDay; + tm->tm_wday = st.wDayOfWeek; + tm->tm_hour = st.wHour; + tm->tm_min = st.wMinute; + tm->tm_sec = st.wSecond; + *msec = st.wMilliseconds; +#else + struct timeval tv; + gettimeofday(&tv, 0); +#ifndef TCACHE + localtime_r(&tv.tv_sec, tm); +#else + if (!tcache_get(&tv, tm)) { + localtime_r(&tv.tv_sec, tm); + tcache_set(&tv, tm); + } +#endif + *msec = (unsigned)tv.tv_usec / 1000; +#endif +#endif +} + +static void pid_callback(int *const pid, int *const tid) { +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(PID, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) + VAR_UNUSED(pid); +#else +#if defined(_WIN32) || defined(_WIN64) + *pid = GetCurrentProcessId(); +#else + *pid = getpid(); +#endif +#endif + +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(TID, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) + VAR_UNUSED(tid); +#else +#if defined(_WIN32) || defined(_WIN64) + *tid = GetCurrentThreadId(); +#elif defined(__ANDROID__) + *tid = gettid(); +#elif defined(__linux__) + *tid = syscall(SYS_gettid); +#elif defined(__MACH__) + *tid = (int)pthread_mach_thread_np(pthread_self()); +#else +#define Platform not supported +#endif +#endif +} + +static void buffer_callback(transport_log_message *msg, char *buf) { + msg->e = (msg->p = msg->buf = buf) + g_buf_sz; +} + +#if _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(FUNCTION, \ + TRANSPORT_LOG_MESSAGE_SRC_FORMAT) +static const char *funcname(const char *func) { return func ? func : ""; } +#endif + +#if _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(FILENAME, \ + TRANSPORT_LOG_MESSAGE_SRC_FORMAT) +static const char *filename(const char *file) { + const char *f = file; + for (const char *p = file; 0 != *p; ++p) { + if ('/' == *p || '\\' == *p) { + f = p + 1; + } + } + return f; +} +#endif + +static INLINE size_t nprintf_size(transport_log_message *const msg) { + // *nprintf() always puts 0 in the end when input buffer is not empty. This + // 0 is not desired because its presence sets (ctx->p) to (ctx->e - 1) which + // leaves space for one more character. Some put_xxx() functions don't use + // *nprintf() and could use that last character. In that case log line will + // have multiple (two) half-written parts which is confusing. To workaround + // that we allow *nprintf() to write its 0 in the eol area (which is always + // not empty). + return (size_t)(msg->e - msg->p + 1); +} + +static INLINE void put_nprintf(transport_log_message *const msg, const int n) { + if (0 < n) { + msg->p = n < msg->e - msg->p ? msg->p + n : msg->e; + } +} + +static INLINE char *put_padding_r(const unsigned w, const char wc, char *p, + char *e) { + for (char *const b = e - w; b < p; *--p = wc) { + } + return p; +} + +static char *put_integer_r(unsigned v, const int sign, const unsigned w, + const char wc, char *const e) { + static const char _signs[] = {'-', '0', '+'}; + static const char *const signs = _signs + 1; + char *p = e; + do { + *--p = '0' + v % 10; + } while (0 != (v /= 10)); + if (0 == sign) return put_padding_r(w, wc, p, e); + if ('0' != wc) { + *--p = signs[sign]; + return put_padding_r(w, wc, p, e); + } + p = put_padding_r(w, wc, p, e + 1); + *--p = signs[sign]; + return p; +} + +static INLINE char *put_uint_r(const unsigned v, const unsigned w, + const char wc, char *const e) { + return put_integer_r(v, 0, w, wc, e); +} + +static INLINE char *put_int_r(const int v, const unsigned w, const char wc, + char *const e) { + return 0 <= v ? put_integer_r((unsigned)v, 0, w, wc, e) + : put_integer_r((unsigned)-v, -1, w, wc, e); +} + +static INLINE char *put_stringn(const char *const s_p, const char *const s_e, + char *const p, char *const e) { + const ptrdiff_t m = e - p; + ptrdiff_t n = s_e - s_p; + if (n > m) { + n = m; + } + memcpy(p, s_p, n); + return p + n; +} + +static INLINE char *put_string(const char *s, char *p, char *const e) { + const ptrdiff_t n = e - p; + char *const c = (char *)memccpy(p, s, '\0', n); + return 0 != c ? c - 1 : e; +} + +static INLINE char *put_uint(unsigned v, const unsigned w, const char wc, + char *const p, char *const e) { + char buf[16]; + char *const se = buf + _countof(buf); + char *sp = put_uint_r(v, w, wc, se); + return put_stringn(sp, se, p, e); +} + +#define PUT_CSTR_R(p, STR) \ + do { \ + for (unsigned i = sizeof(STR) - 1; 0 < i--;) { \ + *--(p) = (STR)[i]; \ + } \ + } \ + _TRANSPORT_LOG_ONCE + +#define PUT_CSTR_CHECKED(p, e, STR) \ + do { \ + for (unsigned i = 0; (e) > (p) && (sizeof(STR) - 1) > i; ++i) { \ + *(p)++ = (STR)[i]; \ + } \ + } \ + _TRANSPORT_LOG_ONCE + +/* F_INIT field support. + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__YEAR +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__MONTH +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__DAY +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__HOUR +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__MINUTE +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__SECOND +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__MILLISECOND +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__PID +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__TID +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__LEVEL +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__TAG(ps, ts) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__FUNCTION +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__FILENAME +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__FILELINE +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__S(s) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__F_INIT(expr) _PP_UNTUPLE(expr); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT__F_UINT(w, v) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_INIT(field) \ + _PP_CONCAT_3(_TRANSPORT_LOG_MESSAGE_FORMAT_INIT_, _, field) + +/* Implements generation of printf-like format string for log message + * format specification. + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__ "" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__YEAR "%04u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__MONTH "%02u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__DAY "%02u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__HOUR "%02u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__MINUTE "%02u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__SECOND "%02u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__MILLISECOND "%03u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__PID "%5i" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__TID "%5i" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__LEVEL "%c" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__TAG UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__FUNCTION "%s" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__FILENAME "%s" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__FILELINE "%u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__S(s) s +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__F_INIT(expr) "" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT__F_UINT(w, v) "%" #w "u" +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT(field) \ + _PP_CONCAT_3(_TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT_, _, field) + +/* Implements generation of printf-like format parameters for log message + * format specification. + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__YEAR \ + , (unsigned)(tm.tm_year + 1900) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__MONTH \ + , (unsigned)(tm.tm_mon + 1) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__DAY , (unsigned)tm.tm_mday +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__HOUR , (unsigned)tm.tm_hour +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__MINUTE , (unsigned)tm.tm_min +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__SECOND , (unsigned)tm.tm_sec +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__MILLISECOND , (unsigned)msec +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__PID , pid +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__TID , tid +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__LEVEL \ + , (char)lvl_char(msg->lvl) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__TAG UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__FUNCTION , funcname(src->func) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__FILENAME , filename(src->file) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__FILELINE , src->line +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__S(s) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__F_INIT(expr) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL__F_UINT(w, v) , v +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL(field) \ + _PP_CONCAT_3(_TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL_, _, field) + +/* Implements generation of put_xxx_t statements for log message specification. + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__YEAR \ + p = put_uint_r(tm.tm_year + 1900, 4, '0', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__MONTH \ + p = put_uint_r((unsigned)tm.tm_mon + 1, 2, '0', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__DAY \ + p = put_uint_r((unsigned)tm.tm_mday, 2, '0', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__HOUR \ + p = put_uint_r((unsigned)tm.tm_hour, 2, '0', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__MINUTE \ + p = put_uint_r((unsigned)tm.tm_min, 2, '0', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__SECOND \ + p = put_uint_r((unsigned)tm.tm_sec, 2, '0', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__MILLISECOND \ + p = put_uint_r(msec, 3, '0', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__PID p = put_int_r(pid, 5, ' ', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__TID p = put_int_r(tid, 5, ' ', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__LEVEL *--p = lvl_char(msg->lvl); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__TAG UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__FUNCTION UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__FILENAME UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__FILELINE UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__S(s) PUT_CSTR_R(p, s); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__F_INIT(expr) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R__F_UINT(w, v) \ + p = put_uint_r(v, w, ' ', p); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R(field) \ + _PP_CONCAT_3(_TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R_, _, field) + +static void put_ctx(transport_log_message *const msg) { + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_INIT, TRANSPORT_LOG_MESSAGE_CTX_FORMAT) +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_FIELDS(TRANSPORT_LOG_MESSAGE_CTX_FORMAT) + VAR_UNUSED(msg); +#else +#if _TRANSPORT_LOG_MESSAGE_FORMAT_DATETIME_USED + struct tm tm; + unsigned msec; + g_time_cb(&tm, &msec); +#endif +#if _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS( \ + PID, TRANSPORT_LOG_MESSAGE_CTX_FORMAT) || \ + _TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(TID, \ + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) + int pid, tid; + g_pid_cb(&pid, &tid); +#endif + +#if TRANSPORT_LOG_OPTIMIZE_SIZE + int n; + n = snprintf(msg->p, nprintf_size(msg), + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT, + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL, + TRANSPORT_LOG_MESSAGE_CTX_FORMAT)); + put_nprintf(msg, n); +#else + char buf[64]; + char *const e = buf + sizeof(buf); + char *p = e; + _PP_RMAP(_TRANSPORT_LOG_MESSAGE_FORMAT_PUT_R, + TRANSPORT_LOG_MESSAGE_CTX_FORMAT) + msg->p = put_stringn(p, e, msg->p, msg->e); +#endif +#endif +} + +#define PUT_TAG(msg, tag, prefix_delim, tag_delim) \ + do { \ + const char *ch; \ + msg->tag_b = msg->p; \ + if (0 != (ch = _transport_log_tag_prefix)) { \ + for (; msg->e != msg->p && 0 != (*msg->p = *ch); ++msg->p, ++ch) { \ + } \ + } \ + if (0 != (ch = tag) && 0 != tag[0]) { \ + if (msg->tag_b != msg->p) { \ + PUT_CSTR_CHECKED(msg->p, msg->e, prefix_delim); \ + } \ + for (; msg->e != msg->p && 0 != (*msg->p = *ch); ++msg->p, ++ch) { \ + } \ + } \ + msg->tag_e = msg->p; \ + if (msg->tag_b != msg->p) { \ + PUT_CSTR_CHECKED(msg->p, msg->e, tag_delim); \ + } \ + } \ + _TRANSPORT_LOG_ONCE + +/* Implements simple put statements for log message specification. + */ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__ +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__YEAR UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__MONTH UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__DAY UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__HOUR UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__MINUTE UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__SECOND UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__MILLISECOND UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__PID UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__TID UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__LEVEL UNDEFINED +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__TAG(pd, td) \ + PUT_TAG(msg, tag, pd, td); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__FUNCTION \ + msg->p = put_string(funcname(src->func), msg->p, msg->e); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__FILENAME \ + msg->p = put_string(filename(src->file), msg->p, msg->e); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__FILELINE \ + msg->p = put_uint(src->line, 0, '\0', msg->p, msg->e); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__S(s) \ + PUT_CSTR_CHECKED(msg->p, msg->e, s); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__F_INIT(expr) +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT__F_UINT(w, v) \ + msg->p = put_uint(v, w, ' ', msg->p, msg->e); +#define _TRANSPORT_LOG_MESSAGE_FORMAT_PUT(field) \ + _PP_CONCAT_3(_TRANSPORT_LOG_MESSAGE_FORMAT_PUT_, _, field) + +static void put_tag(transport_log_message *const msg, const char *const tag) { + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_INIT, TRANSPORT_LOG_MESSAGE_TAG_FORMAT) +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(TAG, \ + TRANSPORT_LOG_MESSAGE_TAG_FORMAT) + VAR_UNUSED(tag); +#endif +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_FIELDS(TRANSPORT_LOG_MESSAGE_TAG_FORMAT) + VAR_UNUSED(msg); +#else + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_PUT, TRANSPORT_LOG_MESSAGE_TAG_FORMAT) +#endif +} + +static void put_src(transport_log_message *const msg, + const src_location *const src) { + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_INIT, TRANSPORT_LOG_MESSAGE_SRC_FORMAT) +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS( \ + FUNCTION, TRANSPORT_LOG_MESSAGE_SRC_FORMAT) && \ + !_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS( \ + FILENAME, TRANSPORT_LOG_MESSAGE_SRC_FORMAT) && \ + !_TRANSPORT_LOG_MESSAGE_FORMAT_CONTAINS(FILELINE, \ + TRANSPORT_LOG_MESSAGE_SRC_FORMAT) + VAR_UNUSED(src); +#endif +#if !_TRANSPORT_LOG_MESSAGE_FORMAT_FIELDS(TRANSPORT_LOG_MESSAGE_SRC_FORMAT) + VAR_UNUSED(msg); +#else +#if TRANSPORT_LOG_OPTIMIZE_SIZE + int n; + n = snprintf(msg->p, nprintf_size(msg), + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_FMT, + TRANSPORT_LOG_MESSAGE_SRC_FORMAT) + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_PRINTF_VAL, + TRANSPORT_LOG_MESSAGE_SRC_FORMAT)); + put_nprintf(msg, n); +#else + _PP_MAP(_TRANSPORT_LOG_MESSAGE_FORMAT_PUT, TRANSPORT_LOG_MESSAGE_SRC_FORMAT) +#endif +#endif +} + +static void put_msg(transport_log_message *const msg, const char *const fmt, + va_list va) { + int n; + msg->msg_b = msg->p; + n = vsnprintf(msg->p, nprintf_size(msg), fmt, va); + put_nprintf(msg, n); +} + +static void output_mem(const transport_log_spec *log, + transport_log_message *const msg, + const mem_block *const mem) { + if (0 == mem->d || 0 == mem->d_sz) { + return; + } + const unsigned char *mem_p = (const unsigned char *)mem->d; + const unsigned char *const mem_e = mem_p + mem->d_sz; + const unsigned char *mem_cut; + const ptrdiff_t mem_width = (ptrdiff_t)log->format->mem_width; + char *const hex_b = msg->msg_b; + char *const ascii_b = hex_b + 2 * mem_width + 2; + char *const ascii_e = ascii_b + mem_width; + if (msg->e < ascii_e) { + return; + } + while (mem_p != mem_e) { + char *hex = hex_b; + char *ascii = ascii_b; + for (mem_cut = mem_width < mem_e - mem_p ? mem_p + mem_width : mem_e; + mem_cut != mem_p; ++mem_p) { + const unsigned char ch = *mem_p; + *hex++ = c_hex[(0xf0 & ch) >> 4]; + *hex++ = c_hex[(0x0f & ch)]; + *ascii++ = isprint(ch) ? (char)ch : '?'; + } + while (hex != ascii_b) { + *hex++ = ' '; + } + msg->p = ascii; + log->output->callback(msg, log->output->arg); + } +} + +void transport_log_set_tag_prefix(const char *const prefix) { + _transport_log_tag_prefix = prefix; +} + +void transport_log_set_mem_width(const unsigned w) { + _transport_log_global_format.mem_width = w; +} + +void transport_log_set_output_level(const int lvl) { + _transport_log_global_output_lvl = lvl; +} + +void transport_log_set_output_v(const unsigned mask, void *const arg, + const transport_log_output_cb callback) { + _transport_log_global_output.mask = mask; + _transport_log_global_output.arg = arg; + _transport_log_global_output.callback = callback; +} + +static void _transport_log_write_imp(const transport_log_spec *log, + const src_location *const src, + const mem_block *const mem, const int lvl, + const char *const tag, + const char *const fmt, va_list va) { + transport_log_message msg; + char buf[TRANSPORT_LOG_BUF_SZ]; + const unsigned mask = log->output->mask; + msg.lvl = lvl; + msg.tag = tag; + g_buffer_cb(&msg, buf); + if (TRANSPORT_LOG_PUT_CTX & mask) { + put_ctx(&msg); + } + if (TRANSPORT_LOG_PUT_TAG & mask) { + put_tag(&msg, tag); + } + if (0 != src && TRANSPORT_LOG_PUT_SRC & mask) { + put_src(&msg, src); + } + if (TRANSPORT_LOG_PUT_MSG & mask) { + put_msg(&msg, fmt, va); + } + log->output->callback(&msg, log->output->arg); + if (0 != mem && TRANSPORT_LOG_PUT_MSG & mask) { + output_mem(log, &msg, mem); + } +} + +void _transport_log_write_d(const char *const func, const char *const file, + const unsigned line, const int lvl, + const char *const tag, const char *const fmt, ...) { + const src_location src = {func, file, line}; + va_list va; + va_start(va, fmt); + _transport_log_write_imp(&global_spec, &src, 0, lvl, tag, fmt, va); + va_end(va); +} + +void _transport_log_write_aux_d(const char *const func, const char *const file, + const unsigned line, + const transport_log_spec *const log, + const int lvl, const char *const tag, + const char *const fmt, ...) { + const src_location src = {func, file, line}; + va_list va; + va_start(va, fmt); + _transport_log_write_imp(log, &src, 0, lvl, tag, fmt, va); + va_end(va); +} + +void _transport_log_write(const int lvl, const char *const tag, + const char *const fmt, ...) { + va_list va; + va_start(va, fmt); + _transport_log_write_imp(&global_spec, 0, 0, lvl, tag, fmt, va); + va_end(va); +} + +void _transport_log_write_aux(const transport_log_spec *const log, + const int lvl, const char *const tag, + const char *const fmt, ...) { + va_list va; + va_start(va, fmt); + _transport_log_write_imp(log, 0, 0, lvl, tag, fmt, va); + va_end(va); +} + +void _transport_log_write_mem_d(const char *const func, const char *const file, + const unsigned line, const int lvl, + const char *const tag, const void *const d, + const unsigned d_sz, const char *const fmt, + ...) { + const src_location src = {func, file, line}; + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _transport_log_write_imp(&global_spec, &src, &mem, lvl, tag, fmt, va); + va_end(va); +} + +void _transport_log_write_mem_aux_d(const char *const func, + const char *const file, const unsigned line, + const transport_log_spec *const log, + const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) { + const src_location src = {func, file, line}; + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _transport_log_write_imp(log, &src, &mem, lvl, tag, fmt, va); + va_end(va); +} + +void _transport_log_write_mem(const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) { + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _transport_log_write_imp(&global_spec, 0, &mem, lvl, tag, fmt, va); + va_end(va); +} + +void _transport_log_write_mem_aux(const transport_log_spec *const log, + const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) { + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _transport_log_write_imp(log, 0, &mem, lvl, tag, fmt, va); + va_end(va); +} \ No newline at end of file diff --git a/libtransport/src/utils/membuf.cc b/libtransport/src/utils/membuf.cc new file mode 100644 index 000000000..e75e85b35 --- /dev/null +++ b/libtransport/src/utils/membuf.cc @@ -0,0 +1,867 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * Copyright 2013-present Facebook, 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. + */ + +/* + * The code in this file if adapated from the IOBuf of folly: + * https://github.com/facebook/folly/blob/master/folly/io/IOBuf.h + */ +#ifdef _WIN32 +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +using std::unique_ptr; + +namespace { + +enum : uint16_t { + kHeapMagic = 0xa5a5, + // This memory segment contains an MemBuf that is still in use + kMemBufInUse = 0x01, + // This memory segment contains buffer data that is still in use + kDataInUse = 0x02, +}; + +enum : std::size_t { + // When create() is called for buffers less than kDefaultCombinedBufSize, + // we allocate a single combined memory segment for the MemBuf and the data + // together. See the comments for createCombined()/createSeparate() for more + // details. + // + // (The size of 1k is largely just a guess here. We could could probably do + // benchmarks of real applications to see if adjusting this number makes a + // difference. Callers that know their exact use case can also explicitly + // call createCombined() or createSeparate().) + kDefaultCombinedBufSize = 1024 +}; + +// Helper function for MemBuf::takeOwnership() +void takeOwnershipError(bool freeOnError, void* buf, + utils::MemBuf::FreeFunction freeFn, void* userData) { + if (!freeOnError) { + return; + } + if (!freeFn) { + free(buf); + return; + } + try { + freeFn(buf, userData); + } catch (...) { + // The user's free function is not allowed to throw. + // (We are already in the middle of throwing an exception, so + // we cannot let this exception go unhandled.) + abort(); + } +} + +} // namespace + +namespace utils { + +struct MemBuf::HeapPrefix { + explicit HeapPrefix(uint16_t flg) : magic(kHeapMagic), flags(flg) {} + ~HeapPrefix() { + // Reset magic to 0 on destruction. This is solely for debugging purposes + // to help catch bugs where someone tries to use HeapStorage after it has + // been deleted. + magic = 0; + } + + uint16_t magic; + std::atomic flags; +}; + +struct MemBuf::HeapStorage { + HeapPrefix prefix; + // The MemBuf is last in the HeapStorage object. + // This way operator new will work even if allocating a subclass of MemBuf + // that requires more space. + utils::MemBuf buf; +}; + +struct MemBuf::HeapFullStorage { + // Make sure jemalloc allocates from the 64-byte class. Putting this here + // because HeapStorage is private so it can't be at namespace level. + static_assert(sizeof(HeapStorage) <= 64, + "MemBuf may not grow over 56 bytes!"); + + HeapStorage hs; + SharedInfo shared; + std::max_align_t align; +}; + +MemBuf::SharedInfo::SharedInfo() : freeFn(nullptr), userData(nullptr) { + // Use relaxed memory ordering here. Since we are creating a new SharedInfo, + // no other threads should be referring to it yet. + refcount.store(1, std::memory_order_relaxed); +} + +MemBuf::SharedInfo::SharedInfo(FreeFunction fn, void* arg) + : freeFn(fn), userData(arg) { + // Use relaxed memory ordering here. Since we are creating a new SharedInfo, + // no other threads should be referring to it yet. + refcount.store(1, std::memory_order_relaxed); +} + +void* MemBuf::operator new(size_t size) { + size_t fullSize = offsetof(HeapStorage, buf) + size; + auto* storage = static_cast(malloc(fullSize)); + + new (&storage->prefix) HeapPrefix(kMemBufInUse); + return &(storage->buf); +} + +void* MemBuf::operator new(size_t /* size */, void* ptr) { return ptr; } + +void MemBuf::operator delete(void* ptr) { + auto* storageAddr = static_cast(ptr) - offsetof(HeapStorage, buf); + auto* storage = reinterpret_cast(storageAddr); + releaseStorage(storage, kMemBufInUse); +} + +void MemBuf::operator delete(void* /* ptr */, void* /* placement */) { + // Provide matching operator for `MemBuf::new` to avoid MSVC compilation + // warning (C4291) about memory leak when exception is thrown in the + // constructor. +} + +void MemBuf::releaseStorage(HeapStorage* storage, uint16_t freeFlags) { + // Use relaxed memory order here. If we are unlucky and happen to get + // out-of-date data the compare_exchange_weak() call below will catch + // it and load new data with memory_order_acq_rel. + auto flags = storage->prefix.flags.load(std::memory_order_acquire); + + while (true) { + uint16_t newFlags = uint16_t(flags & ~freeFlags); + if (newFlags == 0) { + // The storage space is now unused. Free it. + storage->prefix.HeapPrefix::~HeapPrefix(); + free(storage); + return; + } + + // This storage segment still contains portions that are in use. + // Just clear the flags specified in freeFlags for now. + auto ret = storage->prefix.flags.compare_exchange_weak( + flags, newFlags, std::memory_order_acq_rel); + if (ret) { + // We successfully updated the flags. + return; + } + + // We failed to update the flags. Some other thread probably updated them + // and cleared some of the other bits. Continue around the loop to see if + // we are the last user now, or if we need to try updating the flags again. + } +} + +void MemBuf::freeInternalBuf(void* /* buf */, void* userData) { + auto* storage = static_cast(userData); + releaseStorage(storage, kDataInUse); +} + +MemBuf::MemBuf(CreateOp, std::size_t capacity) + : next_(this), + prev_(this), + data_(nullptr), + length_(0), + flags_and_shared_info_(0) { + SharedInfo* info; + allocExtBuffer(capacity, &buf_, &info, &capacity_); + setSharedInfo(info); + data_ = buf_; +} + +MemBuf::MemBuf(CopyBufferOp /* op */, const void* buf, std::size_t size, + std::size_t headroom, std::size_t min_tailroom) + : MemBuf(CREATE, headroom + size + min_tailroom) { + advance(headroom); + if (size > 0) { + assert(buf != nullptr); + memcpy(writableData(), buf, size); + append(size); + } +} + +unique_ptr MemBuf::create(std::size_t capacity) { + // For smaller-sized buffers, allocate the MemBuf, SharedInfo, and the buffer + // all with a single allocation. + // + // We don't do this for larger buffers since it can be wasteful if the user + // needs to reallocate the buffer but keeps using the same MemBuf object. + // In this case we can't free the data space until the MemBuf is also + // destroyed. Callers can explicitly call createCombined() or + // createSeparate() if they know their use case better, and know if they are + // likely to reallocate the buffer later. + if (capacity <= kDefaultCombinedBufSize) { + return createCombined(capacity); + } + return createSeparate(capacity); +} + +unique_ptr MemBuf::createCombined(std::size_t capacity) { + // To save a memory allocation, allocate space for the MemBuf object, the + // SharedInfo struct, and the data itself all with a single call to malloc(). + size_t requiredStorage = offsetof(HeapFullStorage, align) + capacity; + size_t mallocSize = requiredStorage; + auto* storage = static_cast(malloc(mallocSize)); + + new (&storage->hs.prefix) HeapPrefix(kMemBufInUse | kDataInUse); + new (&storage->shared) SharedInfo(freeInternalBuf, storage); + + uint8_t* bufAddr = reinterpret_cast(&storage->align); + uint8_t* storageEnd = reinterpret_cast(storage) + mallocSize; + size_t actualCapacity = size_t(storageEnd - bufAddr); + unique_ptr ret(new (&storage->hs.buf) MemBuf( + InternalConstructor(), packFlagsAndSharedInfo(0, &storage->shared), + bufAddr, actualCapacity, bufAddr, 0)); + return ret; +} + +unique_ptr MemBuf::createSeparate(std::size_t capacity) { + return std::make_unique(CREATE, capacity); +} + +unique_ptr MemBuf::createChain(size_t totalCapacity, + std::size_t maxBufCapacity) { + unique_ptr out = + create(std::min(totalCapacity, size_t(maxBufCapacity))); + size_t allocatedCapacity = out->capacity(); + + while (allocatedCapacity < totalCapacity) { + unique_ptr newBuf = create( + std::min(totalCapacity - allocatedCapacity, size_t(maxBufCapacity))); + allocatedCapacity += newBuf->capacity(); + out->prependChain(std::move(newBuf)); + } + + return out; +} + +MemBuf::MemBuf(TakeOwnershipOp, void* buf, std::size_t capacity, + std::size_t length, FreeFunction freeFn, void* userData, + bool freeOnError) + : next_(this), + prev_(this), + data_(static_cast(buf)), + buf_(static_cast(buf)), + length_(length), + capacity_(capacity), + flags_and_shared_info_( + packFlagsAndSharedInfo(flag_free_shared_info, nullptr)) { + try { + setSharedInfo(new SharedInfo(freeFn, userData)); + } catch (...) { + takeOwnershipError(freeOnError, buf, freeFn, userData); + throw; + } +} + +unique_ptr MemBuf::takeOwnership(void* buf, std::size_t capacity, + std::size_t length, + FreeFunction freeFn, void* userData, + bool freeOnError) { + try { + // TODO: We could allocate the MemBuf object and SharedInfo all in a single + // memory allocation. We could use the existing HeapStorage class, and + // define a new kSharedInfoInUse flag. We could change our code to call + // releaseStorage(flag_free_shared_info) when this flag_free_shared_info, + // rather than directly calling delete. + // + // Note that we always pass freeOnError as false to the constructor. + // If the constructor throws we'll handle it below. (We have to handle + // allocation failures from std::make_unique too.) + return std::make_unique(TAKE_OWNERSHIP, buf, capacity, length, + freeFn, userData, false); + } catch (...) { + takeOwnershipError(freeOnError, buf, freeFn, userData); + throw; + } +} + +MemBuf::MemBuf(WrapBufferOp, const void* buf, std::size_t capacity) noexcept + : MemBuf(InternalConstructor(), 0, + // We cast away the const-ness of the buffer here. + // This is okay since MemBuf users must use unshare() to create a + // copy of this buffer before writing to the buffer. + static_cast(const_cast(buf)), capacity, + static_cast(const_cast(buf)), capacity) {} + +unique_ptr MemBuf::wrapBuffer(const void* buf, std::size_t capacity) { + return std::make_unique(WRAP_BUFFER, buf, capacity); +} + +MemBuf MemBuf::wrapBufferAsValue(const void* buf, + std::size_t capacity) noexcept { + return MemBuf(WrapBufferOp::WRAP_BUFFER, buf, capacity); +} + +MemBuf::MemBuf() noexcept {} + +MemBuf::MemBuf(MemBuf&& other) noexcept + : data_(other.data_), + buf_(other.buf_), + length_(other.length_), + capacity_(other.capacity_), + flags_and_shared_info_(other.flags_and_shared_info_) { + // Reset other so it is a clean state to be destroyed. + other.data_ = nullptr; + other.buf_ = nullptr; + other.length_ = 0; + other.capacity_ = 0; + other.flags_and_shared_info_ = 0; + + // If other was part of the chain, assume ownership of the rest of its chain. + // (It's only valid to perform move assignment on the head of a chain.) + if (other.next_ != &other) { + next_ = other.next_; + next_->prev_ = this; + other.next_ = &other; + + prev_ = other.prev_; + prev_->next_ = this; + other.prev_ = &other; + } +} + +MemBuf::MemBuf(const MemBuf& other) { *this = other.cloneAsValue(); } + +MemBuf::MemBuf(InternalConstructor, uintptr_t flagsAndSharedInfo, uint8_t* buf, + std::size_t capacity, uint8_t* data, std::size_t length) noexcept + : next_(this), + prev_(this), + data_(data), + buf_(buf), + length_(length), + capacity_(capacity), + flags_and_shared_info_(flagsAndSharedInfo) { + assert(data >= buf); + assert(data + length <= buf + capacity); +} + +MemBuf::~MemBuf() { + // Destroying an MemBuf destroys the entire chain. + // Users of MemBuf should only explicitly delete the head of any chain. + // The other elements in the chain will be automatically destroyed. + while (next_ != this) { + // Since unlink() returns unique_ptr() and we don't store it, + // it will automatically delete the unlinked element. + (void)next_->unlink(); + } + + decrementRefcount(); +} + +MemBuf& MemBuf::operator=(MemBuf&& other) noexcept { + if (this == &other) { + return *this; + } + + // If we are part of a chain, delete the rest of the chain. + while (next_ != this) { + // Since unlink() returns unique_ptr() and we don't store it, + // it will automatically delete the unlinked element. + (void)next_->unlink(); + } + + // Decrement our refcount on the current buffer + decrementRefcount(); + + // Take ownership of the other buffer's data + data_ = other.data_; + buf_ = other.buf_; + length_ = other.length_; + capacity_ = other.capacity_; + flags_and_shared_info_ = other.flags_and_shared_info_; + // Reset other so it is a clean state to be destroyed. + other.data_ = nullptr; + other.buf_ = nullptr; + other.length_ = 0; + other.capacity_ = 0; + other.flags_and_shared_info_ = 0; + + // If other was part of the chain, assume ownership of the rest of its chain. + // (It's only valid to perform move assignment on the head of a chain.) + if (other.next_ != &other) { + next_ = other.next_; + next_->prev_ = this; + other.next_ = &other; + + prev_ = other.prev_; + prev_->next_ = this; + other.prev_ = &other; + } + + return *this; +} + +MemBuf& MemBuf::operator=(const MemBuf& other) { + if (this != &other) { + *this = MemBuf(other); + } + return *this; +} + +bool MemBuf::empty() const { + const MemBuf* current = this; + do { + if (current->length() != 0) { + return false; + } + current = current->next_; + } while (current != this); + return true; +} + +size_t MemBuf::countChainElements() const { + size_t numElements = 1; + for (MemBuf* current = next_; current != this; current = current->next_) { + ++numElements; + } + return numElements; +} + +std::size_t MemBuf::computeChainDataLength() const { + std::size_t fullLength = length_; + for (MemBuf* current = next_; current != this; current = current->next_) { + fullLength += current->length_; + } + return fullLength; +} + +void MemBuf::prependChain(unique_ptr&& iobuf) { + // Take ownership of the specified MemBuf + MemBuf* other = iobuf.release(); + + // Remember the pointer to the tail of the other chain + MemBuf* otherTail = other->prev_; + + // Hook up prev_->next_ to point at the start of the other chain, + // and other->prev_ to point at prev_ + prev_->next_ = other; + other->prev_ = prev_; + + // Hook up otherTail->next_ to point at us, + // and prev_ to point back at otherTail, + otherTail->next_ = this; + prev_ = otherTail; +} + +unique_ptr MemBuf::clone() const { + return std::make_unique(cloneAsValue()); +} + +unique_ptr MemBuf::cloneOne() const { + return std::make_unique(cloneOneAsValue()); +} + +unique_ptr MemBuf::cloneCoalesced() const { + return std::make_unique(cloneCoalescedAsValue()); +} + +unique_ptr MemBuf::cloneCoalescedWithHeadroomTailroom( + std::size_t new_headroom, std::size_t new_tailroom) const { + return std::make_unique( + cloneCoalescedAsValueWithHeadroomTailroom(new_headroom, new_tailroom)); +} + +MemBuf MemBuf::cloneAsValue() const { + auto tmp = cloneOneAsValue(); + + for (MemBuf* current = next_; current != this; current = current->next_) { + tmp.prependChain(current->cloneOne()); + } + + return tmp; +} + +MemBuf MemBuf::cloneOneAsValue() const { + if (SharedInfo* info = sharedInfo()) { + setFlags(flag_maybe_shared); + info->refcount.fetch_add(1, std::memory_order_acq_rel); + } + return MemBuf(InternalConstructor(), flags_and_shared_info_, buf_, capacity_, + data_, length_); +} + +MemBuf MemBuf::cloneCoalescedAsValue() const { + const std::size_t new_headroom = headroom(); + const std::size_t new_tailroom = prev()->tailroom(); + return cloneCoalescedAsValueWithHeadroomTailroom(new_headroom, new_tailroom); +} + +MemBuf MemBuf::cloneCoalescedAsValueWithHeadroomTailroom( + std::size_t new_headroom, std::size_t new_tailroom) const { + if (!isChained()) { + return cloneOneAsValue(); + } + // Coalesce into newBuf + const std::size_t new_length = computeChainDataLength(); + const std::size_t new_capacity = new_length + new_headroom + new_tailroom; + MemBuf newBuf{CREATE, new_capacity}; + newBuf.advance(new_headroom); + + auto current = this; + do { + if (current->length() > 0) { + memcpy(newBuf.writableTail(), current->data(), current->length()); + newBuf.append(current->length()); + } + current = current->next(); + } while (current != this); + + return newBuf; +} + +void MemBuf::unshareOneSlow() { + // Allocate a new buffer for the data + uint8_t* buf; + SharedInfo* sharedInfo; + std::size_t actualCapacity; + allocExtBuffer(capacity_, &buf, &sharedInfo, &actualCapacity); + + // Copy the data + // Maintain the same amount of headroom. Since we maintained the same + // minimum capacity we also maintain at least the same amount of tailroom. + std::size_t headlen = headroom(); + if (length_ > 0) { + assert(data_ != nullptr); + memcpy(buf + headlen, data_, length_); + } + + // Release our reference on the old buffer + decrementRefcount(); + // Make sure flag_maybe_shared and flag_free_shared_info are all cleared. + setFlagsAndSharedInfo(0, sharedInfo); + + // Update the buffer pointers to point to the new buffer + data_ = buf + headlen; + buf_ = buf; +} + +void MemBuf::unshareChained() { + // unshareChained() should only be called if we are part of a chain of + // multiple MemBufs. The caller should have already verified this. + assert(isChained()); + + MemBuf* current = this; + while (true) { + if (current->isSharedOne()) { + // we have to unshare + break; + } + + current = current->next_; + if (current == this) { + // None of the MemBufs in the chain are shared, + // so return without doing anything + return; + } + } + + // We have to unshare. Let coalesceSlow() do the work. + coalesceSlow(); +} + +void MemBuf::markExternallyShared() { + MemBuf* current = this; + do { + current->markExternallySharedOne(); + current = current->next_; + } while (current != this); +} + +void MemBuf::makeManagedChained() { + assert(isChained()); + + MemBuf* current = this; + while (true) { + current->makeManagedOne(); + current = current->next_; + if (current == this) { + break; + } + } +} + +void MemBuf::coalesceSlow() { + // coalesceSlow() should only be called if we are part of a chain of multiple + // MemBufs. The caller should have already verified this. + + // Compute the length of the entire chain + std::size_t new_length = 0; + MemBuf* end = this; + do { + new_length += end->length_; + end = end->next_; + } while (end != this); + + coalesceAndReallocate(new_length, end); + // We should be only element left in the chain now +} + +void MemBuf::coalesceSlow(size_t max_length) { + // coalesceSlow() should only be called if we are part of a chain of multiple + // MemBufs. The caller should have already verified this. + + // Compute the length of the entire chain + std::size_t new_length = 0; + MemBuf* end = this; + while (true) { + new_length += end->length_; + end = end->next_; + if (new_length >= max_length) { + break; + } + if (end == this) { + throw std::overflow_error( + "attempted to coalesce more data than " + "available"); + } + } + + coalesceAndReallocate(new_length, end); + // We should have the requested length now +} + +void MemBuf::coalesceAndReallocate(size_t new_headroom, size_t new_length, + MemBuf* end, size_t new_tailroom) { + std::size_t new_capacity = new_length + new_headroom + new_tailroom; + + // Allocate space for the coalesced buffer. + // We always convert to an external buffer, even if we happened to be an + // internal buffer before. + uint8_t* newBuf; + SharedInfo* newInfo; + std::size_t actualCapacity; + allocExtBuffer(new_capacity, &newBuf, &newInfo, &actualCapacity); + + // Copy the data into the new buffer + uint8_t* new_data = newBuf + new_headroom; + uint8_t* p = new_data; + MemBuf* current = this; + size_t remaining = new_length; + do { + if (current->length_ > 0) { + assert(current->length_ <= remaining); + assert(current->data_ != nullptr); + remaining -= current->length_; + memcpy(p, current->data_, current->length_); + p += current->length_; + } + current = current->next_; + } while (current != end); + assert(remaining == 0); + + // Point at the new buffer + decrementRefcount(); + + // Make sure flag_maybe_shared and flag_free_shared_info are all cleared. + setFlagsAndSharedInfo(0, newInfo); + + capacity_ = actualCapacity; + buf_ = newBuf; + data_ = new_data; + length_ = new_length; + + // Separate from the rest of our chain. + // Since we don't store the unique_ptr returned by separateChain(), + // this will immediately delete the returned subchain. + if (isChained()) { + (void)separateChain(next_, current->prev_); + } +} + +void MemBuf::decrementRefcount() { + // Externally owned buffers don't have a SharedInfo object and aren't managed + // by the reference count + SharedInfo* info = sharedInfo(); + if (!info) { + return; + } + + // Decrement the refcount + uint32_t newcnt = info->refcount.fetch_sub(1, std::memory_order_acq_rel); + // Note that fetch_sub() returns the value before we decremented. + // If it is 1, we were the only remaining user; if it is greater there are + // still other users. + if (newcnt > 1) { + return; + } + + // We were the last user. Free the buffer + freeExtBuffer(); + + // Free the SharedInfo if it was allocated separately. + // + // This is only used by takeOwnership(). + // + // To avoid this special case handling in decrementRefcount(), we could have + // takeOwnership() set a custom freeFn() that calls the user's free function + // then frees the SharedInfo object. (This would require that + // takeOwnership() store the user's free function with its allocated + // SharedInfo object.) However, handling this specially with a flag seems + // like it shouldn't be problematic. + if (flags() & flag_free_shared_info) { + delete sharedInfo(); + } +} + +void MemBuf::reserveSlow(std::size_t min_headroom, std::size_t min_tailroom) { + size_t new_capacity = (size_t)length_ + min_headroom + min_tailroom; + + // // reserveSlow() is dangerous if anyone else is sharing the buffer, as we + // may + // // reallocate and free the original buffer. It should only ever be called + // if + // // we are the only user of the buffer. + + // We'll need to reallocate the buffer. + // There are a few options. + // - If we have enough total room, move the data around in the buffer + // and adjust the data_ pointer. + // - If we're using an internal buffer, we'll switch to an external + // buffer with enough headroom and tailroom. + // - If we have enough headroom (headroom() >= min_headroom) but not too much + // (so we don't waste memory), we can try: + // - If we don't have too much to copy, we'll use realloc() (note that + // realloc might have to copy + // headroom + data + tailroom) + // - Otherwise, bite the bullet and reallocate. + if (headroom() + tailroom() >= min_headroom + min_tailroom) { + uint8_t* new_data = writableBuffer() + min_headroom; + std::memmove(new_data, data_, length_); + data_ = new_data; + return; + } + + size_t new_allocated_capacity = 0; + uint8_t* new_buffer = nullptr; + std::size_t new_headroom = 0; + std::size_t old_headroom = headroom(); + + // If we have a buffer allocated with malloc and we just need more tailroom, + // try to use realloc()/xallocx() to grow the buffer in place. + SharedInfo* info = sharedInfo(); + if (info && (info->freeFn == nullptr) && length_ != 0 && + old_headroom >= min_headroom) { + size_t head_slack = old_headroom - min_headroom; + new_allocated_capacity = goodExtBufferSize(new_capacity + head_slack); + + size_t copySlack = capacity() - length_; + if (copySlack * 2 <= length_) { + void* p = realloc(buf_, new_allocated_capacity); + if (TRANSPORT_EXPECT_FALSE(p == nullptr)) { + throw std::bad_alloc(); + } + new_buffer = static_cast(p); + new_headroom = old_headroom; + } + } + + // None of the previous reallocation strategies worked (or we're using + // an internal buffer). malloc/copy/free. + if (new_buffer == nullptr) { + new_allocated_capacity = goodExtBufferSize(new_capacity); + new_buffer = static_cast(malloc(new_allocated_capacity)); + if (length_ > 0) { + assert(data_ != nullptr); + memcpy(new_buffer + min_headroom, data_, length_); + } + if (sharedInfo()) { + freeExtBuffer(); + } + new_headroom = min_headroom; + } + + std::size_t cap; + initExtBuffer(new_buffer, new_allocated_capacity, &info, &cap); + + if (flags() & flag_free_shared_info) { + delete sharedInfo(); + } + + setFlagsAndSharedInfo(0, info); + capacity_ = cap; + buf_ = new_buffer; + data_ = new_buffer + new_headroom; + // length_ is unchanged +} + +void MemBuf::freeExtBuffer() { + SharedInfo* info = sharedInfo(); + + if (info->freeFn) { + try { + info->freeFn(buf_, info->userData); + } catch (...) { + // The user's free function should never throw. Otherwise we might + // throw from the MemBuf destructor. Other code paths like coalesce() + // also assume that decrementRefcount() cannot throw. + abort(); + } + } else { + free(buf_); + } +} + +void MemBuf::allocExtBuffer(std::size_t minCapacity, uint8_t** bufReturn, + SharedInfo** infoReturn, + std::size_t* capacityReturn) { + size_t mallocSize = goodExtBufferSize(minCapacity); + uint8_t* buf = static_cast(malloc(mallocSize)); + initExtBuffer(buf, mallocSize, infoReturn, capacityReturn); + *bufReturn = buf; +} + +size_t MemBuf::goodExtBufferSize(std::size_t minCapacity) { + // Determine how much space we should allocate. We'll store the SharedInfo + // for the external buffer just after the buffer itself. (We store it just + // after the buffer rather than just before so that the code can still just + // use free(buf_) to free the buffer.) + size_t minSize = static_cast(minCapacity) + sizeof(SharedInfo); + // Add room for padding so that the SharedInfo will be aligned on an 8-byte + // boundary. + minSize = (minSize + 7) & ~7; + + // Use goodMallocSize() to bump up the capacity to a decent size to request + // from malloc, so we can use all of the space that malloc will probably give + // us anyway. + return minSize; +} + +void MemBuf::initExtBuffer(uint8_t* buf, size_t mallocSize, + SharedInfo** infoReturn, + std::size_t* capacityReturn) { + // Find the SharedInfo storage at the end of the buffer + // and construct the SharedInfo. + uint8_t* infoStart = (buf + mallocSize) - sizeof(SharedInfo); + SharedInfo* sharedInfo = new (infoStart) SharedInfo; + + *capacityReturn = std::size_t(infoStart - buf); + *infoReturn = sharedInfo; +} + +} // namespace utils \ No newline at end of file diff --git a/libtransport/src/utils/memory_pool_allocator.h b/libtransport/src/utils/memory_pool_allocator.h new file mode 100644 index 000000000..adc1443ad --- /dev/null +++ b/libtransport/src/utils/memory_pool_allocator.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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.sudo make instamake install + */ + +#pragma once + +#include + +namespace utils { + +template +class MemoryPool { + struct Block { + Block *next; + }; + + class Buffer { + static const std::size_t blockSize = sizeof(T) > sizeof(Block) + ? sizeof(T) + : sizeof(Block); + uint8_t data[blockSize * growSize]; + + public: + Buffer *const next; + + Buffer(Buffer *next) : next(next) {} + + T *getBlock(std::size_t index) { + return reinterpret_cast(&data[blockSize * index]); + } + }; + + Block *firstFreeBlock = nullptr; + Buffer *firstBuffer = nullptr; + std::size_t bufferedBlocks = growSize; + + public: + MemoryPool() = default; + MemoryPool(MemoryPool &&memoryPool) = delete; + MemoryPool(const MemoryPool &memoryPool) = delete; + MemoryPool operator=(MemoryPool &&memoryPool) = delete; + MemoryPool operator=(const MemoryPool &memoryPool) = delete; + + ~MemoryPool() { + while (firstBuffer) { + Buffer *buffer = firstBuffer; + firstBuffer = buffer->next; + delete buffer; + } + } + + T *allocate() { + if (firstFreeBlock) { + Block *block = firstFreeBlock; + firstFreeBlock = block->next; + return reinterpret_cast(block); + } + + if (bufferedBlocks >= growSize) { + firstBuffer = new Buffer(firstBuffer); + bufferedBlocks = 0; + } + + return firstBuffer->getBlock(bufferedBlocks++); + } + + void deallocate(T *pointer) { + Block *block = reinterpret_cast(pointer); + block->next = firstFreeBlock; + firstFreeBlock = block; + } +}; + +template +class Allocator : private MemoryPool { +#ifdef _WIN32 + Allocator *copyAllocator; + std::allocator *rebindAllocator = nullptr; +#endif + + public: + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef T *pointer; + typedef const T *const_pointer; + typedef T &reference; + typedef const T &const_reference; + typedef T value_type; + + template + struct rebind { + typedef Allocator other; + }; + +#ifdef _WIN32 + Allocator() = default; + + Allocator(Allocator &allocator) : copyAllocator(&allocator) {} + + template + Allocator(const Allocator &other) { + if (!std::is_same::value) rebindAllocator = new std::allocator(); + } + + ~Allocator() { delete rebindAllocator; } +#endif + + pointer allocate(size_type n, const void *hint = 0) { +#ifdef _WIN32 + if (copyAllocator) return copyAllocator->allocate(n, hint); + + if (rebindAllocator) return rebindAllocator->allocate(n, hint); +#endif + + if (n != 1 || hint) throw std::bad_alloc(); + + return MemoryPool::allocate(); + } + + void deallocate(pointer p, size_type n) { +#ifdef _WIN32 + if (copyAllocator) { + copyAllocator->deallocate(p, n); + return; + } + + if (rebindAllocator) { + rebindAllocator->deallocate(p, n); + return; + } +#endif + + MemoryPool::deallocate(p); + } + + void construct(pointer p, const_reference val) { new (p) T(val); } + + void destroy(pointer p) { p->~T(); } +}; + +} \ No newline at end of file diff --git a/libtransport/src/utils/min_filter.h b/libtransport/src/utils/min_filter.h new file mode 100644 index 000000000..dcfd5652d --- /dev/null +++ b/libtransport/src/utils/min_filter.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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.sudo make instamake install + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace utils { + +template +class MinFilter { + public: + MinFilter(std::size_t size) : size_(size) {} + + std::size_t size() { return by_arrival_.size(); } + + template + TRANSPORT_ALWAYS_INLINE void pushBack(R&& value) { + if (by_arrival_.size() >= size_) { + by_order_.erase(by_arrival_.back()); + by_arrival_.pop_back(); + } + + by_arrival_.push_front(by_order_.insert(std::forward(value))); + } + + TRANSPORT_ALWAYS_INLINE const T& begin() { return *by_order_.cbegin(); } + + TRANSPORT_ALWAYS_INLINE const T& rBegin() { return *by_order_.crbegin(); } + + private: + std::multiset by_order_; + std::deque::const_iterator> by_arrival_; + std::size_t size_; +}; + +} // namespace utils diff --git a/libtransport/src/utils/stream_buffer.h b/libtransport/src/utils/stream_buffer.h new file mode 100644 index 000000000..adfb696f2 --- /dev/null +++ b/libtransport/src/utils/stream_buffer.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include + +namespace utils { + +template +struct ostreambuf + : public std::basic_streambuf > { + ostreambuf(char_type* buffer, std::streamsize buffer_length) { + // set the "put" pointer the start of the buffer and record it's length. + setp(buffer, buffer + buffer_length); + } +}; + +} // namespace utils \ No newline at end of file diff --git a/libtransport/src/utils/string_tokenizer.cc b/libtransport/src/utils/string_tokenizer.cc new file mode 100644 index 000000000..a280a3c43 --- /dev/null +++ b/libtransport/src/utils/string_tokenizer.cc @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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 +#include + +namespace utils { + +StringTokenizer::StringTokenizer(const std::string &str) + : str_(str), delimiter_(" ") {} + +StringTokenizer::StringTokenizer(const std::string &str, + const std::string &delim) + : str_(str), delimiter_(delim) {} + +bool StringTokenizer::hasMoreTokens() { + return str_.find(delimiter_) != std::string::npos || !str_.empty(); +} + +std::string StringTokenizer::nextToken() { + unsigned long pos = (unsigned long)str_.find(delimiter_); + + bool token_found = std::string::npos != pos; + + if (!token_found && str_.empty()) { + throw errors::TokenizerException(); + } + + std::string token = str_.substr(0, pos); + str_.erase(0, token_found ? pos + delimiter_.length() : pos); + + return token; +} + +} // namespace utils \ No newline at end of file diff --git a/libtransport/src/utils/suffix_strategy.h b/libtransport/src/utils/suffix_strategy.h new file mode 100644 index 000000000..6c4dd2785 --- /dev/null +++ b/libtransport/src/utils/suffix_strategy.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include + +namespace utils { + +using transport::core::NextSegmentCalculationStrategy; + +class SuffixStrategy { + public: + static constexpr uint32_t INVALID_SUFFIX = + std::numeric_limits::max(); + + SuffixStrategy(NextSegmentCalculationStrategy strategy) + : suffix_stragegy_(strategy), + total_count_(0), + final_suffix_(INVALID_SUFFIX) {} + + virtual ~SuffixStrategy() = default; + + virtual uint32_t getNextSuffix() = 0; + + virtual uint32_t getFinalSuffix() { return final_suffix_; } + + virtual void setFinalSuffix(std::uint32_t final_suffix) { + if (final_suffix != INVALID_SUFFIX) { + final_suffix_ = final_suffix; + } + } + + virtual uint32_t getNextManifestSuffix() = 0; + + virtual uint32_t getNextContentSuffix() = 0; + + virtual void reset(uint32_t offset = 0) = 0; + + virtual uint32_t getManifestCapacity() = 0; + + virtual void setManifestCapacity(uint32_t capacity) = 0; + + virtual uint32_t getTotalCount() { return total_count_; }; + + NextSegmentCalculationStrategy getSuffixStrategy() { + return suffix_stragegy_; + } + + protected: + inline void incrementTotalCount() { total_count_++; }; + + protected: + NextSegmentCalculationStrategy suffix_stragegy_; + std::uint32_t total_count_; + std::uint32_t final_suffix_; +}; + +class IncrementalSuffixStrategy : public SuffixStrategy { + public: + IncrementalSuffixStrategy(std::uint32_t start_offset) + : SuffixStrategy(NextSegmentCalculationStrategy::INCREMENTAL), + next_suffix_(start_offset) {} + + TRANSPORT_ALWAYS_INLINE std::uint32_t getNextSuffix() override { + incrementTotalCount(); + return next_suffix_++; + } + + TRANSPORT_ALWAYS_INLINE std::uint32_t getNextContentSuffix() override { + return getNextSuffix(); + } + + TRANSPORT_ALWAYS_INLINE std::uint32_t getNextManifestSuffix() override { + return getNextSuffix(); + } + + uint32_t getManifestCapacity() override { + throw errors::RuntimeException( + "No manifest capacity in IncrementalSuffixStrategy."); + } + + void setManifestCapacity(uint32_t capacity) override { + throw errors::RuntimeException( + "No manifest capacity in IncrementalSuffixStrategy."); + } + + void reset(std::uint32_t offset = 0) override { next_suffix_ = offset; } + + protected: + std::uint32_t next_suffix_; +}; + +class CapacityBasedSuffixStrategy : public SuffixStrategy { + public: + CapacityBasedSuffixStrategy(std::uint32_t start_offset, + std::uint32_t manifest_capacity) + : SuffixStrategy(NextSegmentCalculationStrategy::INCREMENTAL), + next_suffix_(start_offset), + segments_in_manifest_(manifest_capacity), + current_manifest_iteration_(0) {} + + TRANSPORT_ALWAYS_INLINE std::uint32_t getNextSuffix() override { + incrementTotalCount(); + return next_suffix_++; + } + + TRANSPORT_ALWAYS_INLINE std::uint32_t getNextContentSuffix() override { + incrementTotalCount(); + return next_suffix_ % segments_in_manifest_ == 0 ? next_suffix_++ + : ++next_suffix_; + } + + TRANSPORT_ALWAYS_INLINE std::uint32_t getNextManifestSuffix() override { + incrementTotalCount(); + return (current_manifest_iteration_++) * (segments_in_manifest_ + 1); + } + + TRANSPORT_ALWAYS_INLINE uint32_t getManifestCapacity() override { + return segments_in_manifest_; + } + + TRANSPORT_ALWAYS_INLINE void setManifestCapacity(uint32_t capacity) override { + segments_in_manifest_ = capacity; + } + + void reset(std::uint32_t offset = 0) override { next_suffix_ = offset; } + + protected: + std::uint32_t next_suffix_; + std::uint32_t segments_in_manifest_; + std::uint32_t current_manifest_iteration_; +}; + +class SuffixStrategyFactory { + public: + static std::unique_ptr getSuffixStrategy( + NextSegmentCalculationStrategy strategy, uint32_t start_offset, + uint32_t manifest_capacity = 0) { + switch (strategy) { + case NextSegmentCalculationStrategy::INCREMENTAL: + return std::make_unique(start_offset); + case NextSegmentCalculationStrategy::MANIFEST_CAPACITY_BASED: + return std::make_unique(start_offset, + manifest_capacity); + default: + throw errors::RuntimeException( + "No valid NextSegmentCalculationStrategy specified."); + } + } +}; + +} // namespace utils diff --git a/libtransport/src/utils/test.h b/libtransport/src/utils/test.h new file mode 100644 index 000000000..e3dd619ac --- /dev/null +++ b/libtransport/src/utils/test.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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. + */ + +#pragma once + +#include + +namespace testing { + +namespace internal { + +enum GTestColor { COLOR_DEFAULT, COLOR_RED, COLOR_GREEN, COLOR_YELLOW }; + +extern void ColoredPrintf(GTestColor color, const char *fmt, ...); + +} // namespace internal + +} // namespace testing + +#define PRINTF(...) \ + do { \ + testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN, \ + "[ ] "); \ + testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, \ + __VA_ARGS__); \ + } while (0) + +// C++ stream interface +class TestCout : public std::stringstream { + public: + ~TestCout() {} +}; + +#define TEST_COUT TestCout() \ No newline at end of file diff --git a/libtransport/src/utils/uri.cc b/libtransport/src/utils/uri.cc new file mode 100644 index 000000000..33eb8b45b --- /dev/null +++ b/libtransport/src/utils/uri.cc @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017-2019 Cisco and/or its affiliates. + * 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 +#include + +namespace utils { + +Uri::Uri() {} + +Uri &Uri::parse(const std::string &uri) { + if (uri.length() == 0) { + throw errors::RuntimeException("Malformed URI."); + } + + iterator_t uri_end = uri.end(); + + // get query start + iterator_t query_start = std::find(uri.begin(), uri_end, '?'); + + // protocol + iterator_t protocol_start = uri.begin(); + iterator_t protocol_end = std::find(protocol_start, uri_end, ':'); //"://"); + + if (protocol_end != uri_end) { + std::string prot = &*(protocol_end); + if ((prot.length() > 3) && (prot.substr(0, 3) == "://")) { + protocol_ = std::string(protocol_start, protocol_end); + protocol_end += 3; // :// + } else { + protocol_end = uri.begin(); // no protocol + } + } else { + protocol_end = uri.begin(); // no protocol + } + // host + iterator_t host_start = protocol_end; + iterator_t path_start = + std::find(host_start, uri_end, '/'); // get path_start + + iterator_t host_end = std::find( + protocol_end, (path_start != uri_end) ? path_start : query_start, + ':'); // check for port + + locator_ = std::string(host_start, host_end); + + // port + if ((host_end != uri_end) && ((&*(host_end))[0] == ':')) { + host_end++; + iterator_t port_end = (path_start != uri_end) ? path_start : query_start; + port_ = std::string(host_end, port_end); + } + + // path + if (path_start != uri_end) { + path_ = std::string(path_start, query_start); + } + // query + if (query_start != uri_end) { + query_string_ = std::string(query_start, uri.end()); + } + + return *this; +} + +Uri &Uri::parseProtocolAndLocator(const std::string &locator) { + iterator_t total_end = locator.end(); + + // protocol + iterator_t protocol_start = locator.begin(); + iterator_t protocol_end = + std::find(protocol_start, total_end, ':'); //"://"); + + if (protocol_end != total_end) { + std::string prot = &*(protocol_end); + if ((prot.length() > 3) && (prot.substr(0, 3) == "://")) { + protocol_ = std::string(protocol_start, protocol_end); + protocol_end += 3; // :// + } else { + throw errors::RuntimeException("Malformed locator. (Missing \"://\")"); + } + } else { + throw errors::RuntimeException("Malformed locator. No protocol specified."); + } + + // locator + iterator_t host_start = protocol_end; + iterator_t host_end = std::find(protocol_end, total_end, '/'); + + if (host_start == host_end) { + throw errors::RuntimeException( + "Malformed locator. Locator name is missing"); + } + + locator_ = std::string(host_start, host_end); + + return *this; +} + +std::string Uri::getLocator() { return locator_; } + +std::string Uri::getPath() { return path_; } + +std::string Uri::getPort() { return port_; } + +std::string Uri::getProtocol() { return protocol_; } + +std::string Uri::getQueryString() { return query_string_; } + +} // end namespace utils -- cgit 1.2.3-korg