Commit 51becbde163409f1aa31c0102ca3da081e7eeb6f

Authored by Peter M. Groen
0 parents

Committed the entire project

CMakeLists.txt 0 → 100644
  1 +++ a/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.0)
  2 +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
  3 +
  4 +# Check to see if there is versioning information available
  5 +if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake)
  6 + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake)
  7 + include(osdevversion)
  8 +endif()
  9 +
  10 +include(projectheader)
  11 +project_header(osdev_mqtt)
  12 +
  13 +add_subdirectory(src)
  14 +add_subdirectory(tests/pub)
  15 +add_subdirectory(tests/sub)
  16 +
  17 +
  18 +# include(packaging)
  19 +# package_component()
... ...
src/CMakeLists.txt 0 → 100644
  1 +++ a/src/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.12)
  2 +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake)
  3 +include(projectheader)
  4 +project_header(mqtt)
  5 +
  6 +find_package( Boost REQUIRED COMPONENTS regex )
  7 +
  8 +include(compiler)
  9 +
  10 +include_directories(
  11 + ${CMAKE_CURRENT_SOURCE_DIR}/../logutils
  12 +)
  13 +
  14 +set(SRC_LIST
  15 + ${CMAKE_CURRENT_SOURCE_DIR}/clientpaho.cpp
  16 + ${CMAKE_CURRENT_SOURCE_DIR}/commondefs.cpp
  17 + ${CMAKE_CURRENT_SOURCE_DIR}/connectionstatus.cpp
  18 + ${CMAKE_CURRENT_SOURCE_DIR}/compiletimedigits.h
  19 + ${CMAKE_CURRENT_SOURCE_DIR}/compiletimestring.h
  20 + ${CMAKE_CURRENT_SOURCE_DIR}/credentials.cpp
  21 + ${CMAKE_CURRENT_SOURCE_DIR}/errorcode.cpp
  22 + ${CMAKE_CURRENT_SOURCE_DIR}/token.cpp
  23 + ${CMAKE_CURRENT_SOURCE_DIR}/ihistogram.cpp
  24 + ${CMAKE_CURRENT_SOURCE_DIR}/timemeasurement.cpp
  25 + ${CMAKE_CURRENT_SOURCE_DIR}/mqttidgenerator.cpp
  26 + ${CMAKE_CURRENT_SOURCE_DIR}/mqtttypeconverter.cpp
  27 + ${CMAKE_CURRENT_SOURCE_DIR}/mqttutil.cpp
  28 + ${CMAKE_CURRENT_SOURCE_DIR}/mqttmessage.cpp
  29 + ${CMAKE_CURRENT_SOURCE_DIR}/mqttclient.cpp
  30 + ${CMAKE_CURRENT_SOURCE_DIR}/mqttfailure.cpp
  31 + ${CMAKE_CURRENT_SOURCE_DIR}/mqttsuccess.cpp
  32 + ${CMAKE_CURRENT_SOURCE_DIR}/utils.cpp
  33 + ${CMAKE_CURRENT_SOURCE_DIR}/imqttclientimpl.cpp
  34 + ${CMAKE_CURRENT_SOURCE_DIR}/istatecallback.cpp
  35 + ${CMAKE_CURRENT_SOURCE_DIR}/scopeguard.cpp
  36 + ${CMAKE_CURRENT_SOURCE_DIR}/serverstate.cpp
  37 + ${CMAKE_CURRENT_SOURCE_DIR}/sharedreaderlock.cpp
  38 + ${CMAKE_CURRENT_SOURCE_DIR}/stringutils.cpp
  39 + ${CMAKE_CURRENT_SOURCE_DIR}/uriparser.cpp
  40 + # Helper files ( Utillities )
  41 + ${CMAKE_CURRENT_SOURCE_DIR}/bimap.h
  42 + ${CMAKE_CURRENT_SOURCE_DIR}/compat-c++14.h
  43 + ${CMAKE_CURRENT_SOURCE_DIR}/compat-chrono.h
  44 + ${CMAKE_CURRENT_SOURCE_DIR}/histogram.h
  45 + ${CMAKE_CURRENT_SOURCE_DIR}/histogramprovider.h
  46 + ${CMAKE_CURRENT_SOURCE_DIR}/imqttclient.h
  47 + ${CMAKE_CURRENT_SOURCE_DIR}/imqttclientimpl.h
  48 + ${CMAKE_CURRENT_SOURCE_DIR}/lockguard.h
  49 + ${CMAKE_CURRENT_SOURCE_DIR}/macrodefs.h
  50 + ${CMAKE_CURRENT_SOURCE_DIR}/measure.h
  51 + ${CMAKE_CURRENT_SOURCE_DIR}/metaprogrammingdefs.h
  52 + ${CMAKE_CURRENT_SOURCE_DIR}/mqttstream.h
  53 + ${CMAKE_CURRENT_SOURCE_DIR}/stringify.h
  54 + ${CMAKE_CURRENT_SOURCE_DIR}/stringutils.h
  55 + ${CMAKE_CURRENT_SOURCE_DIR}/synchronizedqueue.h
  56 + ${CMAKE_CURRENT_SOURCE_DIR}/utils.h
  57 + ${CMAKE_CURRENT_SOURCE_DIR}/uriutils.h
  58 +)
  59 +
  60 +include(library)
  61 +add_libraries(
  62 + PUBLIC
  63 + Boost::boost
  64 + Boost::regex
  65 + paho-mqtt3a
  66 +)
  67 +
  68 +include(installation)
  69 +install_component()
... ...
src/bimap.h 0 → 100644
  1 +++ a/src/bimap.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +
  20 +#ifndef OSDEV_COMPONENTS_MQTT_BIMAP_H
  21 +#define OSDEV_COMPONENTS_MQTT_BIMAP_H
  22 +
  23 +// boost
  24 +#include <boost/bimap.hpp>
  25 +
  26 +namespace osdev {
  27 +namespace components {
  28 +namespace mqtt {
  29 +
  30 +/**
  31 + * @brief Factory function to create boost::bimap from an initializer list
  32 + *
  33 + * Usage:
  34 + * @code
  35 + * auto myBimap = makeBimap<int, int>( { { 1, 2 }, { 2, 3 } } );
  36 + * @endcode
  37 + */
  38 +template <typename L, typename R>
  39 +boost::bimap<L, R> makeBimap(std::initializer_list<typename boost::bimap<L, R>::value_type> list)
  40 +{
  41 + return boost::bimap<L, R>(list.begin(), list.end());
  42 +}
  43 +
  44 +} // End namespace mqtt
  45 +} // End namespace components
  46 +} // End namespace osdev
  47 +
  48 +#endif // OSDEV_COMPONENTS_MQTT_BIMAP_H
... ...
src/clientpaho.cpp 0 → 100644
  1 +++ a/src/clientpaho.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "clientpaho.h"
  20 +
  21 +#include "errorcode.h"
  22 +#include "mqttutil.h"
  23 +#include "lockguard.h"
  24 +#include "metaprogrammingdefs.h"
  25 +#include "mqttstream.h"
  26 +#include "scopeguard.h"
  27 +#include "uriparser.h"
  28 +
  29 +// std::chrono
  30 +#include "compat-chrono.h"
  31 +
  32 +// std
  33 +#include <algorithm>
  34 +#include <iterator>
  35 +
  36 +using namespace osdev::components::mqtt;
  37 +
  38 +namespace {
  39 +
  40 +#if defined(__clang__)
  41 +#pragma GCC diagnostic push
  42 +#pragma GCC diagnostic ignored "-Wunused-template"
  43 +#endif
  44 +
  45 +OSDEV_COMPONENTS_HASMEMBER_TRAIT(onSuccess5)
  46 +
  47 +template <typename TRet>
  48 +inline typename std::enable_if<!has_onSuccess5<TRet>::value, TRet>::type initializeMqttStruct(TRet*)
  49 +{
  50 + return MQTTAsync_disconnectOptions_initializer;
  51 +}
  52 +
  53 +template <typename TRet>
  54 +inline typename std::enable_if<has_onSuccess5<TRet>::value, TRet>::type initializeMqttStruct(TRet*)
  55 +{
  56 +// For some reason g++ on centos7 evaluates the function body even when it is discarded by SFINAE.
  57 +// This leads to a compile error on an undefined symbol. We will use the old initializer macro, but this
  58 +// method should not be chosen when the struct does not contain member onSuccess5!
  59 +// On yocto warrior mqtt-paho-c 1.3.0 the macro MQTTAsync_disconnectOptions_initializer5 is not defined.
  60 +// while the struct does have an onSuccess5 member. In that case we do need correct initializer code.
  61 +// We fall back to the MQTTAsync_disconnectOptions_initializer macro and initialize
  62 +// additional fields ourself (which unfortunately results in a pesky compiler warning about missing field initializers).
  63 +#ifndef MQTTAsync_disconnectOptions_initializer5
  64 +#pragma GCC diagnostic push
  65 +#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
  66 + TRet ret = MQTTAsync_disconnectOptions_initializer;
  67 + ret.struct_version = 1;
  68 + ret.onSuccess5 = nullptr;
  69 + ret.onFailure5 = nullptr;
  70 + return ret;
  71 +#pragma GCC diagnostic pop
  72 +#else
  73 + return MQTTAsync_disconnectOptions_initializer5;
  74 +#endif
  75 +}
  76 +
  77 +template <typename TRet>
  78 +struct Init
  79 +{
  80 + static TRet initialize()
  81 + {
  82 + return initializeMqttStruct<TRet>(static_cast<TRet*>(nullptr));
  83 + }
  84 +};
  85 +#if defined(__clang__)
  86 +#pragma GCC diagnostic pop
  87 +#endif
  88 +
  89 +} // namespace
  90 +
  91 +std::atomic_int ClientPaho::s_numberOfInstances(0);
  92 +
  93 +ClientPaho::ClientPaho(const std::string& _endpoint,
  94 + const std::string& _id,
  95 + const std::function<void(const std::string&, ConnectionStatus)>& connectionStatusCallback,
  96 + const std::function<void(const std::string& clientId, std::int32_t pubMsgToken)>& deliveryCompleteCallback)
  97 + : m_mutex()
  98 + , m_endpoint()
  99 + , m_username()
  100 + , m_password()
  101 + , m_clientId(_id)
  102 + , m_pendingOperations()
  103 + , m_operationResult()
  104 + , m_operationsCompleteCV()
  105 + , m_subscriptions()
  106 + , m_pendingSubscriptions()
  107 + , m_subscribeTokenToTopic()
  108 + , m_unsubscribeTokenToTopic()
  109 + , m_pendingPublishes()
  110 + , m_processPendingPublishes(false)
  111 + , m_pendingPublishesReadyCV()
  112 + , m_client()
  113 + , m_connectionStatus(ConnectionStatus::Disconnected)
  114 + , m_connectionStatusCallback(connectionStatusCallback)
  115 + , m_deliveryCompleteCallback(deliveryCompleteCallback)
  116 + , m_lastUnsubscribe(-1)
  117 + , m_connectPromise()
  118 + , m_disconnectPromise()
  119 + , m_callbackEventQueue(m_clientId)
  120 + , m_workerThread()
  121 +{
  122 + if (0 == s_numberOfInstances++) {
  123 + MQTTAsync_setTraceCallback(&ClientPaho::onLogPaho);
  124 + }
  125 + // MLOGIC_COMMON_DEBUG("ClientPaho", "%1 - ctor ClientPaho %2", m_clientId, this);
  126 + parseEndpoint(_endpoint);
  127 + auto rc = MQTTAsync_create(&m_client, m_endpoint.c_str(), m_clientId.c_str(), MQTTCLIENT_PERSISTENCE_NONE, nullptr);
  128 + if (MQTTASYNC_SUCCESS == rc)
  129 + {
  130 + MQTTAsync_setCallbacks(m_client, reinterpret_cast<void*>(this), ClientPaho::onConnectionLost, ClientPaho::onMessageArrived, ClientPaho::onDeliveryComplete);
  131 + m_workerThread = std::thread(&ClientPaho::callbackEventHandler, this);
  132 + }
  133 + else
  134 + {
  135 + // Do something sensible here.
  136 + }
  137 +}
  138 +
  139 +ClientPaho::~ClientPaho()
  140 +{
  141 + if( MQTTAsync_isConnected( m_client ) )
  142 + {
  143 + this->unsubscribeAll();
  144 +
  145 + this->waitForCompletion(std::chrono::milliseconds(2000), std::set<int32_t>{});
  146 + this->disconnect(true, 5000);
  147 + }
  148 + else
  149 + {
  150 + // If the status was already disconnected this call does nothing
  151 + setConnectionStatus(ConnectionStatus::Disconnected);
  152 + }
  153 +
  154 + if (0 == --s_numberOfInstances)
  155 + {
  156 + // encountered a case where termination of the logging system within paho led to a segfault.
  157 + // This was a paho thread that was cleaned while at the same time the logging system was terminated.
  158 + // Removing the trace callback will not solve the underlying problem but hopefully will trigger it less
  159 + // frequently.
  160 + MQTTAsync_setTraceCallback(nullptr);
  161 + }
  162 +
  163 + MQTTAsync_destroy(&m_client);
  164 +
  165 + m_callbackEventQueue.stop();
  166 + if (m_workerThread.joinable())
  167 + {
  168 + m_workerThread.join();
  169 + }
  170 +}
  171 +
  172 +std::string ClientPaho::clientId() const
  173 +{
  174 + return m_clientId;
  175 +}
  176 +
  177 +ConnectionStatus ClientPaho::connectionStatus() const
  178 +{
  179 + return m_connectionStatus;
  180 +}
  181 +
  182 +std::int32_t ClientPaho::connect(bool wait)
  183 +{
  184 + {
  185 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  186 + if (ConnectionStatus::Disconnected != m_connectionStatus)
  187 + {
  188 + return -1;
  189 + }
  190 + setConnectionStatus(ConnectionStatus::ConnectInProgress);
  191 + }
  192 +
  193 + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer;
  194 + conn_opts.keepAliveInterval = 20;
  195 + conn_opts.cleansession = 1;
  196 + conn_opts.onSuccess = &ClientPaho::onConnectSuccess;
  197 + conn_opts.onFailure = &ClientPaho::onConnectFailure;
  198 + conn_opts.context = this;
  199 + conn_opts.automaticReconnect = 1;
  200 + if (!m_username.empty())
  201 + {
  202 + conn_opts.username = m_username.c_str();
  203 + }
  204 +
  205 + if (!m_password.empty())
  206 + {
  207 + conn_opts.password = m_password.c_str();
  208 + }
  209 +
  210 + std::promise<void> waitForConnectPromise{};
  211 + auto waitForConnect = waitForConnectPromise.get_future();
  212 + m_connectPromise.reset();
  213 + if (wait)
  214 + {
  215 + m_connectPromise = std::make_unique<std::promise<void>>(std::move(waitForConnectPromise));
  216 + }
  217 +
  218 + {
  219 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  220 + if (!m_pendingOperations.insert(-100).second)
  221 + {
  222 + // Write something
  223 + }
  224 + m_operationResult.erase(-100);
  225 + }
  226 +
  227 + int rc = MQTTAsync_connect(m_client, &conn_opts);
  228 + if (MQTTASYNC_SUCCESS != rc)
  229 + {
  230 + setConnectionStatus(ConnectionStatus::Disconnected);
  231 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  232 + m_operationResult[-100] = false;
  233 + m_pendingOperations.erase(-100);
  234 + }
  235 +
  236 + if (wait)
  237 + {
  238 + waitForConnect.get();
  239 + m_connectPromise.reset();
  240 + }
  241 + return -100;
  242 +}
  243 +
  244 +std::int32_t ClientPaho::disconnect(bool wait, int timeoutMs)
  245 +{
  246 + ConnectionStatus currentStatus = m_connectionStatus;
  247 +
  248 + {
  249 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  250 + if (ConnectionStatus::Disconnected == m_connectionStatus || ConnectionStatus::DisconnectInProgress == m_connectionStatus) {
  251 + return -1;
  252 + }
  253 +
  254 + currentStatus = m_connectionStatus;
  255 + setConnectionStatus(ConnectionStatus::DisconnectInProgress);
  256 + }
  257 +
  258 + MQTTAsync_disconnectOptions disconn_opts = Init<MQTTAsync_disconnectOptions>::initialize();
  259 + disconn_opts.timeout = timeoutMs;
  260 + disconn_opts.onSuccess = &ClientPaho::onDisconnectSuccess;
  261 + disconn_opts.onFailure = &ClientPaho::onDisconnectFailure;
  262 + disconn_opts.context = this;
  263 +
  264 + std::promise<void> waitForDisconnectPromise{};
  265 + auto waitForDisconnect = waitForDisconnectPromise.get_future();
  266 + m_disconnectPromise.reset();
  267 + if (wait) {
  268 + m_disconnectPromise = std::make_unique<std::promise<void>>(std::move(waitForDisconnectPromise));
  269 + }
  270 +
  271 + {
  272 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  273 + if (!m_pendingOperations.insert(-200).second)
  274 + {
  275 + // "ClientPaho", "%1 disconnect - token %2 already in use", m_clientId, -200)
  276 + }
  277 + m_operationResult.erase(-200);
  278 + }
  279 +
  280 + int rc = MQTTAsync_disconnect(m_client, &disconn_opts);
  281 + if (MQTTASYNC_SUCCESS != rc) {
  282 + if (MQTTASYNC_DISCONNECTED == rc) {
  283 + currentStatus = ConnectionStatus::Disconnected;
  284 + }
  285 +
  286 + setConnectionStatus(currentStatus);
  287 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  288 + m_operationResult[-200] = false;
  289 + m_pendingOperations.erase(-200);
  290 +
  291 + if (MQTTASYNC_DISCONNECTED == rc) {
  292 + return -1;
  293 + }
  294 + // ("ClientPaho", "%1 - failed to disconnect, return code %2", m_clientId, pahoAsyncErrorCodeToString(rc));
  295 + }
  296 +
  297 + if (wait) {
  298 + if (std::future_status::timeout == waitForDisconnect.wait_for(std::chrono::milliseconds(timeoutMs + 100)))
  299 + {
  300 + // ("ClientPaho", "%1 - timeout occurred on disconnect", m_clientId);
  301 +
  302 + }
  303 + waitForDisconnect.get();
  304 + m_disconnectPromise.reset();
  305 + }
  306 + return -200;
  307 +}
  308 +
  309 +std::int32_t ClientPaho::publish(const MqttMessage& message, int qos)
  310 +{
  311 + if (ConnectionStatus::DisconnectInProgress == m_connectionStatus)
  312 + {
  313 + // ("ClientPaho", "%1 - disconnect in progress, ignoring publish with qos %2 on topic %3", m_clientId, qos, message.topic());
  314 + return -1;
  315 + }
  316 + else if (ConnectionStatus::Disconnected == m_connectionStatus)
  317 + {
  318 + // ("ClientPaho", "%1 - unable to publish, not connected", m_clientId);
  319 + }
  320 +
  321 + if (!isValidTopic(message.topic()))
  322 + {
  323 + // ("ClientPaho", "%1 - topic %2 is invalid", m_clientId, message.topic());
  324 + }
  325 +
  326 + if (qos > 2)
  327 + {
  328 + qos = 2;
  329 + }
  330 + else if (qos < 0)
  331 + {
  332 + qos = 0;
  333 + }
  334 +
  335 +
  336 + std::unique_lock<std::mutex> lck(m_mutex);
  337 + if (ConnectionStatus::ReconnectInProgress == m_connectionStatus || m_processPendingPublishes) {
  338 + m_pendingPublishesReadyCV.wait(lck, [this]() { return !m_processPendingPublishes; });
  339 + if (ConnectionStatus::ReconnectInProgress == m_connectionStatus) {
  340 + // ("ClientPaho", "Adding publish to pending queue.");
  341 + m_pendingPublishes.push_front(Publish{ qos, message });
  342 + return -1;
  343 + }
  344 + }
  345 +
  346 + return publishInternal(message, qos);
  347 +}
  348 +
  349 +void ClientPaho::publishPending()
  350 +{
  351 + {
  352 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  353 + if (!m_processPendingPublishes) {
  354 + return;
  355 + }
  356 + }
  357 +
  358 + if (ConnectionStatus::Connected != m_connectionStatus)
  359 + {
  360 + // MqttException, "Not connected");
  361 + }
  362 +
  363 + while (!m_pendingPublishes.empty())
  364 + {
  365 + const auto& pub = m_pendingPublishes.back();
  366 + publishInternal(pub.data, pub.qos);
  367 + // else ("ClientPaho", "%1 - pending publish on topic %2 failed : %3", m_clientId, pub.data.topic(), e.what());
  368 +
  369 + m_pendingPublishes.pop_back();
  370 + }
  371 +
  372 + {
  373 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  374 + m_processPendingPublishes = false;
  375 + }
  376 + m_pendingPublishesReadyCV.notify_all();
  377 +}
  378 +
  379 +std::int32_t ClientPaho::subscribe(const std::string& topic, int qos, const std::function<void(MqttMessage msg)>& cb)
  380 +{
  381 + if (ConnectionStatus::Connected != m_connectionStatus)
  382 + {
  383 + // MqttException, "Not connected"
  384 + }
  385 +
  386 + if (!isValidTopic(topic))
  387 + {
  388 + // ("ClientPaho", "%1 - topic %2 is invalid", m_clientId, topic);
  389 + }
  390 +
  391 + if (qos > 2)
  392 + {
  393 + qos = 2;
  394 + }
  395 + else if (qos < 0)
  396 + {
  397 + qos = 0;
  398 + }
  399 +
  400 + {
  401 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  402 +
  403 + auto itExisting = m_subscriptions.find(topic);
  404 + if (m_subscriptions.end() != itExisting) {
  405 + if (itExisting->second.qos == qos) {
  406 + return -1;
  407 + }
  408 + // (OverlappingTopicException, "existing subscription with same topic, but different qos", topic);
  409 + }
  410 +
  411 + auto itPending = m_pendingSubscriptions.find(topic);
  412 + if (m_pendingSubscriptions.end() != itPending) {
  413 + if (itPending->second.qos == qos) {
  414 + auto itToken = std::find_if(m_subscribeTokenToTopic.begin(), m_subscribeTokenToTopic.end(), [&topic](const std::pair<MQTTAsync_token, std::string>& item) { return topic == item.second; });
  415 + if (m_subscribeTokenToTopic.end() != itToken) {
  416 + return itToken->first;
  417 + }
  418 + else {
  419 + return -1;
  420 + }
  421 + }
  422 + // (OverlappingTopicException, "pending subscription with same topic, but different qos", topic);
  423 + }
  424 +
  425 + std::string existingTopic{};
  426 + if (isOverlappingInternal(topic, existingTopic))
  427 + {
  428 + // (OverlappingTopicException, "overlapping topic", existingTopic, topic);
  429 + }
  430 +
  431 + // ("ClientPaho", "%1 - adding subscription on topic %2 to the pending subscriptions", m_clientId, topic);
  432 + m_pendingSubscriptions.emplace(std::make_pair(topic, Subscription{ qos, boost::regex(convertTopicToRegex(topic)), cb }));
  433 + }
  434 + return subscribeInternal(topic, qos);
  435 +}
  436 +
  437 +void ClientPaho::resubscribe()
  438 +{
  439 + decltype(m_pendingSubscriptions) pendingSubscriptions{};
  440 + {
  441 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  442 + std::copy(m_pendingSubscriptions.begin(), m_pendingSubscriptions.end(), std::inserter(pendingSubscriptions, pendingSubscriptions.end()));
  443 + }
  444 +
  445 + for (const auto& s : pendingSubscriptions)
  446 + {
  447 + subscribeInternal(s.first, s.second.qos);
  448 + }
  449 +}
  450 +
  451 +std::int32_t ClientPaho::unsubscribe(const std::string& topic, int qos)
  452 +{
  453 + {
  454 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  455 + bool found = false;
  456 + for (const auto& s : m_subscriptions)
  457 + {
  458 + if (topic == s.first && qos == s.second.qos)
  459 + {
  460 + found = true;
  461 + break;
  462 + }
  463 + }
  464 + if (!found)
  465 + {
  466 + return -1;
  467 + }
  468 + }
  469 +
  470 + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
  471 + opts.onSuccess = &ClientPaho::onUnsubscribeSuccess;
  472 + opts.onFailure = &ClientPaho::onUnsubscribeFailure;
  473 + opts.context = this;
  474 +
  475 + {
  476 + // Need to lock the mutex because it is possible that the callback is faster than
  477 + // the insertion of the token into the pending operations.
  478 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  479 + auto rc = MQTTAsync_unsubscribe(m_client, topic.c_str(), &opts);
  480 + if (MQTTASYNC_SUCCESS != rc)
  481 + {
  482 + // ("ClientPaho", "%1 - unsubscribe on topic %2 failed with code %3", m_clientId, topic, pahoAsyncErrorCodeToString(rc));
  483 + }
  484 +
  485 + if (!m_pendingOperations.insert(opts.token).second)
  486 + {
  487 + // ("ClientPaho", "%1 unsubscribe - token %2 already in use", m_clientId, opts.token);
  488 + }
  489 +
  490 + m_operationResult.erase(opts.token);
  491 + if (m_unsubscribeTokenToTopic.count(opts.token) > 0)
  492 + {
  493 + // ("ClientPaho", "%1 - token already in use, replacing unsubscribe from topic %2 with topic %3", m_clientId, m_unsubscribeTokenToTopic[opts.token], topic);
  494 + }
  495 + m_lastUnsubscribe = opts.token; // centos7 workaround
  496 + m_unsubscribeTokenToTopic[opts.token] = topic;
  497 + }
  498 +
  499 + // Because of a bug in paho-c on centos7 the unsubscribes need to be sequential (best effort).
  500 + this->waitForCompletion(std::chrono::seconds(1), std::set<int32_t>{ opts.token });
  501 +
  502 + return opts.token;
  503 +}
  504 +
  505 +void ClientPaho::unsubscribeAll()
  506 +{
  507 + decltype(m_subscriptions) subscriptions{};
  508 + {
  509 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  510 + subscriptions = m_subscriptions;
  511 + }
  512 +
  513 + for (const auto& s : subscriptions) {
  514 + this->unsubscribe(s.first, s.second.qos);
  515 + }
  516 +}
  517 +
  518 +std::chrono::milliseconds ClientPaho::waitForCompletion(std::chrono::milliseconds waitFor, const std::set<std::int32_t>& tokens) const
  519 +{
  520 + if (waitFor <= std::chrono::milliseconds(0)) {
  521 + return std::chrono::milliseconds(0);
  522 + }
  523 + std::chrono::milliseconds timeElapsed{};
  524 + {
  525 + osdev::components::mqtt::measurement::TimeMeasurement msr("waitForCompletion", [&timeElapsed](const std::string&, std::chrono::steady_clock::time_point, std::chrono::microseconds sinceStart, std::chrono::microseconds)
  526 + {
  527 + timeElapsed = std::chrono::ceil<std::chrono::milliseconds>(sinceStart);
  528 + });
  529 + std::unique_lock<std::mutex> lck(m_mutex);
  530 + // ("ClientPaho", "%1 waitForCompletion - pending operations : %2", m_clientId, m_pendingOperations);
  531 + m_operationsCompleteCV.wait_for(lck, waitFor, [this, &tokens]()
  532 + {
  533 + if (tokens.empty())
  534 + { // wait for all operations to end
  535 + return m_pendingOperations.empty();
  536 + }
  537 + else if (tokens.size() == 1)
  538 + {
  539 + return m_pendingOperations.find(*tokens.cbegin()) == m_pendingOperations.end();
  540 + }
  541 + std::vector<std::int32_t> intersect{};
  542 + std::set_intersection(m_pendingOperations.begin(), m_pendingOperations.end(), tokens.begin(), tokens.end(), std::back_inserter(intersect));
  543 + return intersect.empty();
  544 + } );
  545 + }
  546 + return timeElapsed;
  547 +}
  548 +
  549 +bool ClientPaho::isOverlapping(const std::string& topic) const
  550 +{
  551 + std::string existingTopic{};
  552 + return isOverlapping(topic, existingTopic);
  553 +}
  554 +
  555 +bool ClientPaho::isOverlapping(const std::string& topic, std::string& existingTopic) const
  556 +{
  557 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  558 + return isOverlappingInternal(topic, existingTopic);
  559 +}
  560 +
  561 +std::vector<std::int32_t> ClientPaho::pendingOperations() const
  562 +{
  563 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  564 + std::vector<std::int32_t> retval{};
  565 + retval.resize(m_pendingOperations.size());
  566 + std::copy(m_pendingOperations.begin(), m_pendingOperations.end(), retval.begin());
  567 + return retval;
  568 +}
  569 +
  570 +bool ClientPaho::hasPendingSubscriptions() const
  571 +{
  572 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  573 + return !m_pendingSubscriptions.empty();
  574 +}
  575 +
  576 +boost::optional<bool> ClientPaho::operationResult(std::int32_t token) const
  577 +{
  578 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  579 + boost::optional<bool> ret{};
  580 + auto cit = m_operationResult.find(token);
  581 + if (m_operationResult.end() != cit)
  582 + {
  583 + ret = cit->second;
  584 + }
  585 + return ret;
  586 +}
  587 +
  588 +void ClientPaho::parseEndpoint(const std::string& _endpoint)
  589 +{
  590 + auto ep = UriParser::parse(_endpoint);
  591 + if (ep.find("user") != ep.end())
  592 + {
  593 + m_username = ep["user"];
  594 + ep["user"].clear();
  595 + }
  596 +
  597 + if (ep.find("password") != ep.end())
  598 + {
  599 + m_password = ep["password"];
  600 + ep["password"].clear();
  601 + }
  602 + m_endpoint = UriParser::toString(ep);
  603 +}
  604 +
  605 +std::int32_t ClientPaho::publishInternal(const MqttMessage& message, int qos)
  606 +{
  607 + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
  608 + opts.onSuccess = &ClientPaho::onPublishSuccess;
  609 + opts.onFailure = &ClientPaho::onPublishFailure;
  610 + opts.context = this;
  611 + auto msg = message.toAsyncMessage();
  612 + msg.qos = qos;
  613 +
  614 + // Need to lock the mutex because it is possible that the callback is faster than
  615 + // the insertion of the token into the pending operations.
  616 +
  617 + // OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  618 + auto rc = MQTTAsync_sendMessage(m_client, message.topic().c_str(), &msg, &opts);
  619 + if (MQTTASYNC_SUCCESS != rc)
  620 + {
  621 + // ("ClientPaho", "%1 - publish on topic %2 failed with code %3", m_clientId, message.topic(), pahoAsyncErrorCodeToString(rc));
  622 + }
  623 +
  624 + if (!m_pendingOperations.insert(opts.token).second)
  625 + {
  626 + // ("ClientPaho", "%1 publishInternal - token %2 already in use", m_clientId, opts.token);
  627 + }
  628 + m_operationResult.erase(opts.token);
  629 + return opts.token;
  630 +}
  631 +
  632 +std::int32_t ClientPaho::subscribeInternal(const std::string& topic, int qos)
  633 +{
  634 + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
  635 + opts.onSuccess = &ClientPaho::onSubscribeSuccess;
  636 + opts.onFailure = &ClientPaho::onSubscribeFailure;
  637 + opts.context = this;
  638 +
  639 + // Need to lock the mutex because it is possible that the callback is faster than
  640 + // the insertion of the token into the pending operations.
  641 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  642 + auto rc = MQTTAsync_subscribe(m_client, topic.c_str(), qos, &opts);
  643 + if (MQTTASYNC_SUCCESS != rc)
  644 + {
  645 + m_pendingSubscriptions.erase(topic);
  646 + // ("ClientPaho", "%1 - subscription on topic %2 failed with code %3", m_clientId, topic, pahoAsyncErrorCodeToString(rc));
  647 + // (MqttException, "Subscription failed");
  648 + }
  649 +
  650 + if (!m_pendingOperations.insert(opts.token).second)
  651 + {
  652 + // ("ClientPaho", "%1 subscribe - token %2 already in use", m_clientId, opts.token);
  653 + }
  654 + m_operationResult.erase(opts.token);
  655 + if (m_subscribeTokenToTopic.count(opts.token) > 0)
  656 + {
  657 + // ("ClientPaho", "%1 - overwriting pending subscription on topic %2 with topic %3", m_clientId, m_subscribeTokenToTopic[opts.token], topic);
  658 + }
  659 + m_subscribeTokenToTopic[opts.token] = topic;
  660 + return opts.token;
  661 +}
  662 +
  663 +void ClientPaho::setConnectionStatus(ConnectionStatus status)
  664 +{
  665 + ConnectionStatus curStatus = m_connectionStatus;
  666 + m_connectionStatus = status;
  667 + if (status != curStatus && m_connectionStatusCallback)
  668 + {
  669 + m_connectionStatusCallback(m_clientId, status);
  670 + }
  671 +}
  672 +
  673 +bool ClientPaho::isOverlappingInternal(const std::string& topic, std::string& existingTopic) const
  674 +{
  675 + existingTopic.clear();
  676 + for (const auto& s : m_pendingSubscriptions)
  677 + {
  678 + if (testForOverlap(s.first, topic))
  679 + {
  680 + existingTopic = s.first;
  681 + return true;
  682 + }
  683 + }
  684 +
  685 + for (const auto& s : m_subscriptions)
  686 + {
  687 + if (testForOverlap(s.first, topic))
  688 + {
  689 + existingTopic = s.first;
  690 + return true;
  691 + }
  692 + }
  693 + return false;
  694 +}
  695 +
  696 +void ClientPaho::pushIncomingEvent(std::function<void()> ev)
  697 +{
  698 + m_callbackEventQueue.push(ev);
  699 +}
  700 +
  701 +void ClientPaho::callbackEventHandler()
  702 +{
  703 + // ("ClientPaho", "%1 - starting callback event handler", m_clientId);
  704 + for (;;) {
  705 + std::vector<std::function<void()>> events;
  706 + if (!m_callbackEventQueue.pop(events))
  707 + {
  708 + break;
  709 + }
  710 +
  711 + for (const auto& ev : events)
  712 + {
  713 + ev();
  714 + // ("ClientPaho", "%1 - Exception occurred: %2", m_clientId, mlogicException);
  715 + }
  716 + }
  717 + // ("ClientPaho", "%1 - leaving callback event handler", m_clientId);
  718 +}
  719 +
  720 +void ClientPaho::onConnectOnInstance(const std::string& cause)
  721 +{
  722 + (void)cause;
  723 + // toLogFile ("ClientPaho", "onConnectOnInstance %1 - reconnected (cause %2)", m_clientId, cause);
  724 + {
  725 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  726 + std::copy(m_subscriptions.begin(), m_subscriptions.end(), std::inserter(m_pendingSubscriptions, m_pendingSubscriptions.end()));
  727 + m_subscriptions.clear();
  728 + m_processPendingPublishes = true; // all publishes are on hold until publishPending is called.
  729 + }
  730 +
  731 + setConnectionStatus(ConnectionStatus::Connected);
  732 +}
  733 +
  734 +void ClientPaho::onConnectSuccessOnInstance(const MqttSuccess& response)
  735 +{
  736 + auto connectData = response.connectionData();
  737 + // ("ClientPaho", "onConnectSuccessOnInstance %1 - connected to endpoint %2 (mqtt version %3, session present %4)",
  738 + // m_clientId, connectData.serverUri(), connectData.mqttVersion(), connectData.sessionPresent());
  739 + {
  740 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  741 + // Register the connect callback that is used in reconnect scenarios.
  742 + auto rc = MQTTAsync_setConnected(m_client, this, &ClientPaho::onConnect);
  743 + if (MQTTASYNC_SUCCESS != rc)
  744 + {
  745 + // ("ClientPaho", "onConnectSuccessOnInstance %1 - registering the connected callback failed with code %2", m_clientId, pahoAsyncErrorCodeToString(rc));
  746 + }
  747 + // For MQTTV5
  748 + //rc = MQTTAsync_setDisconnected(m_client, this, &ClientPaho::onDisconnect);
  749 + //if (MQTTASYNC_SUCCESS != rc) {
  750 + // // ("ClientPaho", "onConnectSuccessOnInstance %1 - registering the disconnected callback failed with code %2", m_clientId, pahoAsyncErrorCodeToString(rc));
  751 + //}
  752 + // ("ClientPaho", "onConnectSuccessOnInstance %1 - pending operations : %2, removing operation -100", m_clientId, m_pendingOperations);
  753 + m_operationResult[-100] = true;
  754 + m_pendingOperations.erase(-100);
  755 + }
  756 + setConnectionStatus(ConnectionStatus::Connected);
  757 + if (m_connectPromise)
  758 + {
  759 + m_connectPromise->set_value();
  760 + }
  761 + m_operationsCompleteCV.notify_all();
  762 +}
  763 +
  764 +void ClientPaho::onConnectFailureOnInstance(const MqttFailure& response)
  765 +{
  766 + (void)response;
  767 + // ("ClientPaho", "onConnectFailureOnInstance %1 - connection failed with code %2 (%3)", m_clientId, response.codeToString(), response.message());
  768 + {
  769 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  770 + if (m_connectPromise) {
  771 + m_connectPromise->set_value();
  772 + }
  773 + // ("ClientPaho", "onConnectFailureOnInstance %1 - pending operations : %2, removing operation -100", m_clientId, m_pendingOperations);
  774 + m_operationResult[-100] = false;
  775 + m_pendingOperations.erase(-100);
  776 + }
  777 + if (ConnectionStatus::ConnectInProgress == m_connectionStatus)
  778 + {
  779 + setConnectionStatus(ConnectionStatus::Disconnected);
  780 + }
  781 + m_operationsCompleteCV.notify_all();
  782 +}
  783 +
  784 +//void ClientPaho::onDisconnectOnInstance(enum MQTTReasonCodes reasonCode)
  785 +//{
  786 +// MLOGIC_COMMON_INFO("ClientPaho", "onDisconnectOnInstance %1 - disconnect (reason %2)", MQTTReasonCode_toString(reasonCode));
  787 +//}
  788 +
  789 +void ClientPaho::onDisconnectSuccessOnInstance(const MqttSuccess&)
  790 +{
  791 + // ("ClientPaho", "onDisconnectSuccessOnInstance %1 - disconnected from endpoint %2", m_clientId, m_endpoint);
  792 + {
  793 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  794 + m_subscriptions.clear();
  795 + m_pendingSubscriptions.clear();
  796 + m_subscribeTokenToTopic.clear();
  797 + m_unsubscribeTokenToTopic.clear();
  798 +
  799 + // ("ClientPaho", "onDisconnectSuccessOnInstance %1 - pending operations : %2, removing all operations", m_clientId, m_pendingOperations);
  800 + m_operationResult[-200] = true;
  801 + m_pendingOperations.clear();
  802 + }
  803 +
  804 + setConnectionStatus(ConnectionStatus::Disconnected);
  805 +
  806 + if (m_disconnectPromise) {
  807 + m_disconnectPromise->set_value();
  808 + }
  809 + m_operationsCompleteCV.notify_all();
  810 +}
  811 +
  812 +void ClientPaho::onDisconnectFailureOnInstance(const MqttFailure& response)
  813 +{
  814 + (void)response;
  815 + // ("ClientPaho", "onDisconnectFailureOnInstance %1 - disconnect failed with code %2 (%3)", m_clientId, response.codeToString(), response.message());
  816 + {
  817 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  818 + // ("ClientPaho", "onDisconnectFailureOnInstance %1 - pending operations : %2, removing operation -200", m_clientId, m_pendingOperations);
  819 + m_operationResult[-200] = false;
  820 + m_pendingOperations.erase(-200);
  821 + }
  822 +
  823 + if (MQTTAsync_isConnected(m_client))
  824 + {
  825 + setConnectionStatus(ConnectionStatus::Connected);
  826 + }
  827 + else
  828 + {
  829 + setConnectionStatus(ConnectionStatus::Disconnected);
  830 + }
  831 +
  832 + if (m_disconnectPromise)
  833 + {
  834 + m_disconnectPromise->set_value();
  835 + }
  836 + m_operationsCompleteCV.notify_all();
  837 +}
  838 +
  839 +void ClientPaho::onPublishSuccessOnInstance(const MqttSuccess& response)
  840 +{
  841 + auto pd = response.publishData();
  842 + // ("ClientPaho", "onPublishSuccessOnInstance %1 - publish with token %2 succeeded (message was %3)", m_clientId, response.token(), pd.payload());
  843 + {
  844 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  845 + // ("ClientPaho", "onPublishSuccessOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, response.token());
  846 + m_operationResult[response.token()] = true;
  847 + m_pendingOperations.erase(response.token());
  848 + }
  849 + m_operationsCompleteCV.notify_all();
  850 +}
  851 +
  852 +void ClientPaho::onPublishFailureOnInstance(const MqttFailure& response)
  853 +{
  854 + // ("ClientPaho", "onPublishFailureOnInstance %1 - publish with token %2 failed with code %3 (%4)", m_clientId, response.token(), response.codeToString(), response.message());
  855 + {
  856 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  857 + // ("ClientPaho", "onPublishFailureOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, response.token());
  858 + m_operationResult[response.token()] = false;
  859 + m_pendingOperations.erase(response.token());
  860 + }
  861 + m_operationsCompleteCV.notify_all();
  862 +}
  863 +
  864 +void ClientPaho::onSubscribeSuccessOnInstance(const MqttSuccess& response)
  865 +{
  866 + // ("ClientPaho", "onSubscribeSuccessOnInstance %1 - subscribe with token %2 succeeded", m_clientId, response.token());
  867 + OSDEV_COMPONENTS_SCOPEGUARD(m_operationsCompleteCV, [this]() { m_operationsCompleteCV.notify_all(); });
  868 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  869 + bool operationOk = false;
  870 + OSDEV_COMPONENTS_SCOPEGUARD(m_pendingOperations, [this, &response, &operationOk]()
  871 + {
  872 + // ("ClientPaho", "onSubscribeSuccessOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, response.token());
  873 + m_operationResult[response.token()] = operationOk;
  874 + m_pendingOperations.erase(response.token());
  875 + });
  876 + auto it = m_subscribeTokenToTopic.find(response.token());
  877 + if (m_subscribeTokenToTopic.end() == it) {
  878 + // ("ClientPaho", "onSubscribeSuccessOnInstance %1 - unknown token %2", m_clientId, response.token());
  879 + return;
  880 + }
  881 + auto topic = it->second;
  882 + m_subscribeTokenToTopic.erase(it);
  883 +
  884 + auto pendingIt = m_pendingSubscriptions.find(topic);
  885 + if (m_pendingSubscriptions.end() == pendingIt)
  886 + {
  887 + // ("ClientPaho", "onSubscribeSuccessOnInstance %1 - cannot find pending subscription for token %2", m_clientId, response.token());
  888 + return;
  889 + }
  890 + if (response.qos() != pendingIt->second.qos)
  891 + {
  892 + // ("ClientPaho", "onSubscribeSuccessOnInstance %1 - subscription requested qos %2, endpoint assigned qos %3", m_clientId, pendingIt->second.qos, response.qos());
  893 + }
  894 + // ("ClientPaho", "onSubscribeSuccessOnInstance %1 - move pending subscription on topic %2 to the registered subscriptions", m_clientId, topic);
  895 + m_subscriptions.emplace(std::make_pair(pendingIt->first, std::move(pendingIt->second)));
  896 + m_pendingSubscriptions.erase(pendingIt);
  897 + operationOk = true;
  898 +}
  899 +
  900 +void ClientPaho::onSubscribeFailureOnInstance(const MqttFailure& response)
  901 +{
  902 + // ("ClientPaho", "onSubscribeFailureOnInstance %1 - subscription failed with code %2 (%3)", m_clientId, response.codeToString(), response.message());
  903 + OSDEV_COMPONENTS_SCOPEGUARD(m_operationsCompleteCV, [this]() { m_operationsCompleteCV.notify_all(); });
  904 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  905 + OSDEV_COMPONENTS_SCOPEGUARD(m_pendingOperations, [this, &response]()
  906 + {
  907 + // MLOGIC_COMMON_DEBUG("ClientPaho", "onSubscribeFailureOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, response.token());
  908 + m_operationResult[response.token()] = false;
  909 + m_pendingOperations.erase(response.token());
  910 + });
  911 +
  912 + auto it = m_subscribeTokenToTopic.find(response.token());
  913 + if (m_subscribeTokenToTopic.end() == it)
  914 + {
  915 + // ("ClientPaho", "onSubscribeFailureOnInstance %1 - unknown token %2", m_clientId, response.token());
  916 + return;
  917 + }
  918 + auto topic = it->second;
  919 + m_subscribeTokenToTopic.erase(it);
  920 +
  921 + auto pendingIt = m_pendingSubscriptions.find(topic);
  922 + if (m_pendingSubscriptions.end() == pendingIt)
  923 + {
  924 + // ("ClientPaho", "onSubscribeFailureOnInstance %1 - cannot find pending subscription for token %2", m_clientId, response.token());
  925 + return;
  926 + }
  927 + // ("ClientPaho", "onSubscribeFailureOnInstance %1 - remove pending subscription on topic %2", m_clientId, topic);
  928 + m_pendingSubscriptions.erase(pendingIt);
  929 +}
  930 +
  931 +void ClientPaho::onUnsubscribeSuccessOnInstance(const MqttSuccess& response)
  932 +{
  933 + // ("ClientPaho", "onUnsubscribeSuccessOnInstance %1 - unsubscribe with token %2 succeeded", m_clientId, response.token());
  934 +
  935 + OSDEV_COMPONENTS_SCOPEGUARD(m_operationsCompleteCV, [this]() { m_operationsCompleteCV.notify_all(); });
  936 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  937 +
  938 + // On centos7 the unsubscribe response is a nullptr, so we do not have a valid token.
  939 + // As a workaround the last unsubscribe token is stored and is used when no valid token is available.
  940 + // This is by no means bullet proof because rapid unsubscribes in succession will overwrite this member
  941 + // before the callback on the earlier unsubscribe has arrived. On centos7 the unsubscribes have to be handled
  942 + // sequentially (see ClientPaho::unsubscribe)!
  943 + auto token = response.token();
  944 + if (-1 == token)
  945 + {
  946 + token = m_lastUnsubscribe;
  947 + m_lastUnsubscribe = -1;
  948 + }
  949 +
  950 + bool operationOk = false;
  951 + OSDEV_COMPONENTS_SCOPEGUARD(m_pendingOperations, [this, token, &operationOk]()
  952 + {
  953 + // ("ClientPaho", "onUnsubscribeSuccessOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, token);
  954 + m_operationResult[token] = operationOk;
  955 + m_pendingOperations.erase(token);
  956 + });
  957 +
  958 + auto it = m_unsubscribeTokenToTopic.find(token);
  959 + if (m_unsubscribeTokenToTopic.end() == it)
  960 + {
  961 + // ("ClientPaho", "onUnsubscribeSuccessOnInstance %1 - unknown token %2", m_clientId, token);
  962 + return;
  963 + }
  964 + auto topic = it->second;
  965 + m_unsubscribeTokenToTopic.erase(it);
  966 +
  967 + auto registeredIt = m_subscriptions.find(topic);
  968 + if (m_subscriptions.end() == registeredIt) {
  969 + // ("ClientPaho", "onUnsubscribeSuccessOnInstance %1 - cannot find subscription for token %2", m_clientId, response.token());
  970 + return;
  971 + }
  972 + // ("ClientPaho", "onUnsubscribeSuccessOnInstance %1 - remove subscription on topic %2 from the registered subscriptions", m_clientId, topic);
  973 + m_subscriptions.erase(registeredIt);
  974 + operationOk = true;
  975 +}
  976 +
  977 +void ClientPaho::onUnsubscribeFailureOnInstance(const MqttFailure& response)
  978 +{
  979 + // ("ClientPaho", "onUnsubscribeFailureOnInstance %1 - subscription failed with code %2 (%3)", m_clientId, response.codeToString(), response.message());
  980 + OSDEV_COMPONENTS_SCOPEGUARD(m_operationsCompleteCV, [this]() { m_operationsCompleteCV.notify_all(); });
  981 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  982 + OSDEV_COMPONENTS_SCOPEGUARD(m_pendingOperations, [this, &response]()
  983 + {
  984 + // ("ClientPaho", "onUnsubscribeFailureOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, response.token());
  985 + m_operationResult[response.token()] = false;
  986 + m_pendingOperations.erase(response.token());
  987 + });
  988 +
  989 + auto it = m_unsubscribeTokenToTopic.find(response.token());
  990 + if (m_unsubscribeTokenToTopic.end() == it)
  991 + {
  992 + // ("ClientPaho", "onUnsubscribeFailureOnInstance %1 - unknown token %2", m_clientId, response.token());
  993 + return;
  994 + }
  995 + auto topic = it->second;
  996 + m_unsubscribeTokenToTopic.erase(it);
  997 +}
  998 +
  999 +int ClientPaho::onMessageArrivedOnInstance(const MqttMessage& message)
  1000 +{
  1001 + // ("ClientPaho", "onMessageArrivedOnInstance %1 - received message on topic %2, retained : %3, dup : %4", m_clientId, message.topic(), message.retained(), message.duplicate());
  1002 +
  1003 + std::function<void(MqttMessage)> cb;
  1004 +
  1005 + {
  1006 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  1007 + for (const auto& s : m_subscriptions)
  1008 + {
  1009 + if (boost::regex_match(message.topic(), s.second.topicRegex))
  1010 + {
  1011 + cb = s.second.callback;
  1012 + }
  1013 + }
  1014 + }
  1015 +
  1016 + if (cb)
  1017 + {
  1018 + cb(message);
  1019 + }
  1020 + else
  1021 + {
  1022 + // ("ClientPaho", "onMessageArrivedOnInstance %1 - no topic filter found for message received on topic %2", m_clientId, message.topic());
  1023 + }
  1024 + return 1;
  1025 +}
  1026 +
  1027 +void ClientPaho::onDeliveryCompleteOnInstance(MQTTAsync_token token)
  1028 +{
  1029 + // ("ClientPaho", "onDeliveryCompleteOnInstance %1 - message with token %2 is delivered", m_clientId, token);
  1030 + if (m_deliveryCompleteCallback)
  1031 + {
  1032 + m_deliveryCompleteCallback(m_clientId, static_cast<std::int32_t>(token));
  1033 + }
  1034 +}
  1035 +
  1036 +void ClientPaho::onConnectionLostOnInstance(const std::string& cause)
  1037 +{
  1038 + (void)cause;
  1039 + // ("ClientPaho", "onConnectionLostOnInstance %1 - connection lost (%2)", m_clientId, cause);
  1040 + setConnectionStatus(ConnectionStatus::ReconnectInProgress);
  1041 +
  1042 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  1043 + // Remove all tokens related to subscriptions from the active operations.
  1044 + for (const auto& p : m_subscribeTokenToTopic)
  1045 + {
  1046 + // ("ClientPaho", "onConnectionLostOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, p.first);
  1047 + m_pendingOperations.erase(p.first);
  1048 + }
  1049 +
  1050 + for (const auto& p : m_unsubscribeTokenToTopic)
  1051 + {
  1052 + // ("ClientPaho", "onConnectionLostOnInstance %1 - pending operations : %2, removing operation %3", m_clientId, m_pendingOperations, p.first);
  1053 + m_pendingOperations.erase(p.first);
  1054 + }
  1055 + // Clear the administration used in the subscribe process.
  1056 + m_subscribeTokenToTopic.clear();
  1057 + m_unsubscribeTokenToTopic.clear();
  1058 +}
  1059 +
  1060 +// static
  1061 +void ClientPaho::onConnect(void* context, char* cause)
  1062 +{
  1063 + if (context)
  1064 + {
  1065 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1066 + std::string reason(nullptr == cause ? "unknown cause" : cause);
  1067 + cl->pushIncomingEvent([cl, reason]() { cl->onConnectOnInstance(reason); });
  1068 + }
  1069 +}
  1070 +
  1071 +// static
  1072 +void ClientPaho::onConnectSuccess(void* context, MQTTAsync_successData* response)
  1073 +{
  1074 + if (context)
  1075 + {
  1076 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1077 + if (!response) {
  1078 + // connect should always have a valid response struct.
  1079 + // ("ClientPaho", "onConnectSuccess - no response data");
  1080 + }
  1081 + MqttSuccess resp(response->token, ConnectionData(response->alt.connect.serverURI, response->alt.connect.MQTTVersion, response->alt.connect.sessionPresent));
  1082 + cl->pushIncomingEvent([cl, resp]() { cl->onConnectSuccessOnInstance(resp); });
  1083 + }
  1084 +}
  1085 +
  1086 +// static
  1087 +void ClientPaho::onConnectFailure(void* context, MQTTAsync_failureData* response)
  1088 +{
  1089 + if (context)
  1090 + {
  1091 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1092 + MqttFailure resp(response);
  1093 + cl->pushIncomingEvent([cl, resp]() { cl->onConnectFailureOnInstance(resp); });
  1094 + }
  1095 +}
  1096 +
  1097 +//// static
  1098 +//void ClientPaho::onDisconnect(void* context, MQTTProperties* properties, enum MQTTReasonCodes reasonCode)
  1099 +//{
  1100 +// apply_unused_parameters(properties);
  1101 +// try {
  1102 +// if (context) {
  1103 +// auto* cl = reinterpret_cast<ClientPaho*>(context);
  1104 +// cl->pushIncomingEvent([cl, reasonCode]() { cl->onDisconnectOnInstance(reasonCode); });
  1105 +// }
  1106 +// }
  1107 +// catch (...) {
  1108 +// }
  1109 +// catch (const std::exception& e) {
  1110 +// MLOGIC_COMMON_ERROR("ClientPaho", "onDisconnect - exception : %1", e.what());
  1111 +// }
  1112 +// catch (...) {
  1113 +// MLOGIC_COMMON_ERROR("ClientPaho", "onDisconnect - unknown exception");
  1114 +// }
  1115 +//}
  1116 +
  1117 +// static
  1118 +void ClientPaho::onDisconnectSuccess(void* context, MQTTAsync_successData* response)
  1119 +{
  1120 + if (context)
  1121 + {
  1122 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1123 + MqttSuccess resp(response ? response->token : 0);
  1124 + cl->pushIncomingEvent([cl, resp]() { cl->onDisconnectSuccessOnInstance(resp); });
  1125 + }
  1126 +}
  1127 +
  1128 +// static
  1129 +void ClientPaho::onDisconnectFailure(void* context, MQTTAsync_failureData* response)
  1130 +{
  1131 + if (context)
  1132 + {
  1133 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1134 + MqttFailure resp(response);
  1135 + cl->pushIncomingEvent([cl, resp]() { cl->onDisconnectFailureOnInstance(resp); });
  1136 + }
  1137 +}
  1138 +
  1139 +// static
  1140 +void ClientPaho::onPublishSuccess(void* context, MQTTAsync_successData* response)
  1141 +{
  1142 + if (context)
  1143 + {
  1144 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1145 + if (!response)
  1146 + {
  1147 + // publish should always have a valid response struct.
  1148 + // toLogFile ("ClientPaho", "onPublishSuccess - no response data");
  1149 + }
  1150 + MqttSuccess resp(response->token, MqttMessage(response->alt.pub.destinationName == nullptr ? "null" : response->alt.pub.destinationName, response->alt.pub.message));
  1151 + cl->pushIncomingEvent([cl, resp]() { cl->onPublishSuccessOnInstance(resp); });
  1152 + }
  1153 +}
  1154 +
  1155 +// static
  1156 +void ClientPaho::onPublishFailure(void* context, MQTTAsync_failureData* response)
  1157 +{
  1158 + (void)response;
  1159 + if (context)
  1160 + {
  1161 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1162 + MqttFailure resp(response);
  1163 + cl->pushIncomingEvent([cl, resp]() { cl->onPublishFailureOnInstance(resp); });
  1164 + }
  1165 +}
  1166 +
  1167 +// static
  1168 +void ClientPaho::onSubscribeSuccess(void* context, MQTTAsync_successData* response)
  1169 +{
  1170 + if (context)
  1171 + {
  1172 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1173 + if (!response)
  1174 + {
  1175 + // subscribe should always have a valid response struct.
  1176 + // MLOGIC_COMMON_FATAL("ClientPaho", "onSubscribeSuccess - no response data");
  1177 + }
  1178 + MqttSuccess resp(response->token, response->alt.qos);
  1179 + cl->pushIncomingEvent([cl, resp]() { cl->onSubscribeSuccessOnInstance(resp); });
  1180 + }
  1181 +}
  1182 +
  1183 +// static
  1184 +void ClientPaho::onSubscribeFailure(void* context, MQTTAsync_failureData* response)
  1185 +{
  1186 + if (context)
  1187 + {
  1188 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1189 + MqttFailure resp(response);
  1190 + cl->pushIncomingEvent([cl, resp]() { cl->onSubscribeFailureOnInstance(resp); });
  1191 + }
  1192 +}
  1193 +
  1194 +// static
  1195 +void ClientPaho::onUnsubscribeSuccess(void* context, MQTTAsync_successData* response)
  1196 +{
  1197 + if (context)
  1198 + {
  1199 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1200 + MqttSuccess resp(response ? response->token : -1);
  1201 + cl->pushIncomingEvent([cl, resp]() { cl->onUnsubscribeSuccessOnInstance(resp); });
  1202 + }
  1203 +}
  1204 +
  1205 +// static
  1206 +void ClientPaho::onUnsubscribeFailure(void* context, MQTTAsync_failureData* response)
  1207 +{
  1208 + if (context)
  1209 + {
  1210 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1211 + MqttFailure resp(response);
  1212 + cl->pushIncomingEvent([cl, resp]() { cl->onUnsubscribeFailureOnInstance(resp); });
  1213 + }
  1214 +}
  1215 +
  1216 +// static
  1217 +int ClientPaho::onMessageArrived(void* context, char* topicName, int, MQTTAsync_message* message)
  1218 +{
  1219 +
  1220 + OSDEV_COMPONENTS_SCOPEGUARD(freeMessage, [&topicName, &message]()
  1221 + {
  1222 + MQTTAsync_freeMessage(&message);
  1223 + MQTTAsync_free(topicName);
  1224 + });
  1225 +
  1226 + if (context)
  1227 + {
  1228 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1229 + MqttMessage msg(topicName, *message);
  1230 + cl->pushIncomingEvent([cl, msg]() { cl->onMessageArrivedOnInstance(msg); });
  1231 + }
  1232 +
  1233 + return 1; // always return true. Otherwise this callback is triggered again.
  1234 +}
  1235 +
  1236 +// static
  1237 +void ClientPaho::onDeliveryComplete(void* context, MQTTAsync_token token)
  1238 +{
  1239 + if (context)
  1240 + {
  1241 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1242 + cl->pushIncomingEvent([cl, token]() { cl->onDeliveryCompleteOnInstance(token); });
  1243 + }
  1244 +}
  1245 +
  1246 +// static
  1247 +void ClientPaho::onConnectionLost(void* context, char* cause)
  1248 +{
  1249 + OSDEV_COMPONENTS_SCOPEGUARD(freeCause, [&cause]()
  1250 + {
  1251 + if (cause)
  1252 + {
  1253 + MQTTAsync_free(cause);
  1254 + }
  1255 + });
  1256 +
  1257 + if (context)
  1258 + {
  1259 + auto* cl = reinterpret_cast<ClientPaho*>(context);
  1260 + std::string msg(nullptr == cause ? "cause unknown" : cause);
  1261 + cl->pushIncomingEvent([cl, msg]() { cl->onConnectionLostOnInstance(msg); });
  1262 + }
  1263 +}
  1264 +
  1265 +// static
  1266 +void ClientPaho::onLogPaho(enum MQTTASYNC_TRACE_LEVELS level, char* message)
  1267 +{
  1268 + (void)message;
  1269 + switch (level)
  1270 + {
  1271 + case MQTTASYNC_TRACE_MAXIMUM:
  1272 + case MQTTASYNC_TRACE_MEDIUM:
  1273 + case MQTTASYNC_TRACE_MINIMUM: {
  1274 + // ("ClientPaho", "paho - %1", message)
  1275 + break;
  1276 + }
  1277 + case MQTTASYNC_TRACE_PROTOCOL: {
  1278 + // ("ClientPaho", "paho - %1", message)
  1279 + break;
  1280 + }
  1281 + case MQTTASYNC_TRACE_ERROR:
  1282 + case MQTTASYNC_TRACE_SEVERE:
  1283 + case MQTTASYNC_TRACE_FATAL: {
  1284 + // ("ClientPaho", "paho - %1", message)
  1285 + break;
  1286 + }
  1287 + }
  1288 +}
... ...
src/clientpaho.h 0 → 100644
  1 +++ a/src/clientpaho.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_CLIENTPAHO_H
  2 +#define OSDEV_COMPONENTS_MQTT_CLIENTPAHO_H
  3 +
  4 +// std
  5 +#include <atomic>
  6 +#include <condition_variable>
  7 +#include <deque>
  8 +#include <functional>
  9 +#include <future>
  10 +#include <map>
  11 +#include <memory>
  12 +#include <mutex>
  13 +#include <string>
  14 +#include <vector>
  15 +
  16 +// boost
  17 +#include <boost/regex.hpp>
  18 +
  19 +// paho
  20 +#include <MQTTAsync.h>
  21 +
  22 +// osdev::components::mqtt
  23 +#include "synchronizedqueue.h"
  24 +#include "imqttclientimpl.h"
  25 +#include "mqttfailure.h"
  26 +#include "mqttsuccess.h"
  27 +
  28 +namespace osdev {
  29 +namespace components {
  30 +namespace mqtt {
  31 +
  32 +/**
  33 + * @brief Wrapper class for the paho-c library.
  34 + * This implementation uses the clean session flag and recreates subscriptions on reconnect.
  35 + *
  36 + * The implementation allows multiple subscriptions as long as the subscriptions do not have overlap. For mqtt 3 it is not
  37 + * possible to track down the subscription based on the incoming message meta information. By matching the topic against
  38 + * the various topic filters a subscription can be identified (but only when there is only one subscription that matches).
  39 + */
  40 +class ClientPaho : public IMqttClientImpl
  41 +{
  42 +public:
  43 + /**
  44 + * @brief Construct a ClientPaho instance.
  45 + * @param endpoint The endpoint to connect to
  46 + * @param id The clientId that is used in the connection.
  47 + * @param connectionStatusCallback The callback on which connection status information is communicated.
  48 + * @param deliveryCompleteCallback Callback that is called with the publish message tokens for messages that are delivered.
  49 + * Being delivered has different meanings depending on the quality of service.
  50 + */
  51 + ClientPaho(const std::string& endpoint,
  52 + const std::string& id,
  53 + const std::function<void(const std::string&, ConnectionStatus)>& connectionStatusCallback,
  54 + const std::function<void(const std::string&, std::int32_t)>& deliveryCompleteCallback);
  55 + virtual ~ClientPaho() override;
  56 +
  57 + // Non copyable, non movable.
  58 + ClientPaho(const ClientPaho&) = delete;
  59 + ClientPaho& operator=(const ClientPaho&) = delete;
  60 + ClientPaho(ClientPaho&&) = delete;
  61 + ClientPaho& operator=(ClientPaho&&) = delete;
  62 +
  63 + /**
  64 + * @see IMqttClientImpl
  65 + */
  66 + virtual std::string clientId() const override;
  67 +
  68 + /**
  69 + * @see IMqttClientImpl
  70 + */
  71 + virtual ConnectionStatus connectionStatus() const override;
  72 +
  73 + /**
  74 + * @see IMqttClientImpl
  75 + */
  76 + virtual std::int32_t connect(bool wait) override;
  77 +
  78 + /**
  79 + * @see IMqttClientImpl
  80 + */
  81 + virtual std::int32_t disconnect(bool wait, int timeoutMs) override;
  82 +
  83 + /**
  84 + * @see IMqttClientImpl
  85 + */
  86 + virtual std::int32_t publish(const MqttMessage& message, int qos) override;
  87 +
  88 + /**
  89 + * @see IMqttClientImpl
  90 + */
  91 + virtual void publishPending() override;
  92 +
  93 + /**
  94 + * @see IMqttClientImpl
  95 + */
  96 + virtual std::int32_t subscribe(const std::string& topic, int qos, const std::function<void(MqttMessage msg)>& cb) override;
  97 +
  98 + /**
  99 + * @see IMqttClientImpl
  100 + */
  101 + virtual void resubscribe() override;
  102 +
  103 + /**
  104 + * @see IMqttClientImpl
  105 + */
  106 + virtual std::int32_t unsubscribe(const std::string& topic, int qos) override;
  107 +
  108 + /**
  109 + * @see IMqttClientImpl
  110 + */
  111 + virtual void unsubscribeAll() override;
  112 +
  113 + /**
  114 + * @see IMqttClientImpl
  115 + */
  116 + virtual std::chrono::milliseconds waitForCompletion(std::chrono::milliseconds waitFor, const std::set<std::int32_t>& tokens) const override;
  117 +
  118 + /**
  119 + * @see IMqttClientImpl
  120 + */
  121 + virtual bool isOverlapping(const std::string& topic) const override;
  122 +
  123 + /**
  124 + * @see IMqttClientImpl
  125 + */
  126 + virtual bool isOverlapping(const std::string& topic, std::string& existingTopic) const override;
  127 +
  128 + /**
  129 + * @see IMqttClientImpl
  130 + */
  131 + virtual std::vector<std::int32_t> pendingOperations() const override;
  132 +
  133 + /**
  134 + * @see IMqttClientImpl
  135 + */
  136 + virtual bool hasPendingSubscriptions() const override;
  137 +
  138 + /**
  139 + * @see IMqttClientImpl
  140 + */
  141 + virtual boost::optional<bool> operationResult(std::int32_t token) const override;
  142 +
  143 +private:
  144 + void parseEndpoint(const std::string& endpoint);
  145 +
  146 + std::int32_t publishInternal(const MqttMessage& message, int qos);
  147 + std::int32_t subscribeInternal(const std::string& topic, int qos);
  148 +
  149 + void setConnectionStatus(ConnectionStatus status);
  150 + bool isOverlappingInternal(const std::string& topic, std::string& existingTopic) const;
  151 +
  152 + /**
  153 + * @brief Internal struct for subscriber information.
  154 + * Used to store subscriptions.
  155 + */
  156 + struct Subscription
  157 + {
  158 + int qos;
  159 + boost::regex topicRegex;
  160 + std::function<void(MqttMessage)> callback;
  161 + };
  162 +
  163 + /**
  164 + * @brief Internal struct for publisher information.
  165 + * Used to store pending publishes during reconnection.
  166 + */
  167 + struct Publish
  168 + {
  169 + int qos;
  170 + MqttMessage data;
  171 + };
  172 +
  173 + /**
  174 + * @brief Add an incoming callback event to the synchronized queue.
  175 + * @param ev A function object that calls one of the event handlers on a ClientPaho instance, but other types of actions are also possible.
  176 + */
  177 + void pushIncomingEvent(std::function<void()> ev);
  178 +
  179 + /**
  180 + * @brief Worker method that executes the events.
  181 + */
  182 + void callbackEventHandler();
  183 +
  184 + /**
  185 + * @brief Callback method that is called when a reconnect succeeds.
  186 + * @param cause The cause of the original disconnect.
  187 + */
  188 + void onConnectOnInstance(const std::string& cause);
  189 +
  190 + /**
  191 + * @brief Callback that is called when a connect call succeeds.
  192 + * This callback is also called when a reconnect succeeds because the paho library reuses the initial connect command!
  193 + * The connection status is set to Connected.
  194 + * @param response A success response with connection data.
  195 + */
  196 + void onConnectSuccessOnInstance(const MqttSuccess& response);
  197 +
  198 + /**
  199 + * @brief Callback that is called when a connect call fails after being sent to the endpoint.
  200 + * This callback is also called when a reconnect fails because the paho library reuses the initial connect command!
  201 + * The connection status is set to Disconnected when the connection state is ConnectInProgress, othwerwise the connection status is left as is.
  202 + * @param response A failure response.
  203 + */
  204 + void onConnectFailureOnInstance(const MqttFailure& response);
  205 +
  206 + //void onDisconnectOnInstance(enum MQTTReasonCodes reasonCode); for MQTT V5 which is not supported by centos7 paho-c
  207 +
  208 + /**
  209 + * @brief Callback that is called when a disconnect call succeeds.
  210 + * The connection status is set to Disconnected.
  211 + * @param response A success response with no specific data.
  212 + */
  213 + void onDisconnectSuccessOnInstance(const MqttSuccess& response);
  214 +
  215 + /**
  216 + * @brief Callback that is called when a disconnect call fails after being sent to the endpoint.
  217 + * Based on the result returned by the paho library The connection status is set to Disconnected or Connected.
  218 + * @param response A failure response.
  219 + */
  220 + void onDisconnectFailureOnInstance(const MqttFailure& response);
  221 +
  222 + /**
  223 + * @brief Callback that is called when a publish call succeeds.
  224 + * This callback is called before the delivery complete callback.
  225 + * @param response A success response with the published message.
  226 + */
  227 + void onPublishSuccessOnInstance(const MqttSuccess& response);
  228 +
  229 + /**
  230 + * @brief Callback that is called when a publish call fails after being sent to the endpoint.
  231 + * @param response A failure response.
  232 + */
  233 + void onPublishFailureOnInstance(const MqttFailure& response);
  234 +
  235 + /**
  236 + * @brief Callback that is called when a subscribe call succeeds.
  237 + * @param response A success response with the subscription information. The actual used qos is conveyed in this response.
  238 + */
  239 + void onSubscribeSuccessOnInstance(const MqttSuccess& response);
  240 +
  241 + /**
  242 + * @brief Callback that is called when a subscribe call fails after being sent to the endpoint.
  243 + * @param response A failure response.
  244 + */
  245 + void onSubscribeFailureOnInstance(const MqttFailure& response);
  246 +
  247 + /**
  248 + * @brief Callback that is called when an unsubscribe call succeeds.
  249 + * @param response A success response with no specific data.
  250 + */
  251 + void onUnsubscribeSuccessOnInstance(const MqttSuccess& response);
  252 +
  253 + /**
  254 + * @brief Callback that is called when an unsubscribe call fails after being sent to the endpoint.
  255 + * @param response A failure response.
  256 + */
  257 + void onUnsubscribeFailureOnInstance(const MqttFailure& response);
  258 +
  259 + /**
  260 + * @brief Callback that is called when a message is received.
  261 + * @param message The message payload and meta data.
  262 + */
  263 + int onMessageArrivedOnInstance(const MqttMessage& message);
  264 +
  265 + /**
  266 + * @brief Callback that is called when the delivery of a publish message is considered complete.
  267 + * The definition of complete depends on the quality of service used in the publish command.
  268 + * @param token The token with the publish command is sent.
  269 + */
  270 + void onDeliveryCompleteOnInstance(MQTTAsync_token token);
  271 +
  272 + /**
  273 + * @brief Callback that is called when the connection is broken.
  274 + * @param cause The reason string. Always "cause unknown" for mqtt3 endpoints.
  275 + */
  276 + void onConnectionLostOnInstance(const std::string& cause);
  277 +
  278 + // Static callback functions that are registered on the paho library. Functions call their *OnInstance() counterparts.
  279 + static void onConnect(void* context, char* cause);
  280 + static void onConnectSuccess(void* context, MQTTAsync_successData* response);
  281 + static void onConnectFailure(void* context, MQTTAsync_failureData* response);
  282 + //static void onDisconnect(void* context, MQTTProperties* properties, enum MQTTReasonCodes reasonCode); for MQTT V5 which is not supported by centos7 paho-c
  283 + static void onDisconnectSuccess(void* context, MQTTAsync_successData* response);
  284 + static void onDisconnectFailure(void* context, MQTTAsync_failureData* response);
  285 + static void onPublishSuccess(void* context, MQTTAsync_successData* response);
  286 + static void onPublishFailure(void* context, MQTTAsync_failureData* response);
  287 + static void onSubscribeSuccess(void* context, MQTTAsync_successData* response);
  288 + static void onSubscribeFailure(void* context, MQTTAsync_failureData* response);
  289 + static void onUnsubscribeSuccess(void* context, MQTTAsync_successData* response);
  290 + static void onUnsubscribeFailure(void* context, MQTTAsync_failureData* response);
  291 + static int onMessageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message);
  292 + static void onDeliveryComplete(void* context, MQTTAsync_token token);
  293 + static void onConnectionLost(void* context, char* cause);
  294 +
  295 + /**
  296 + * @brief Connects the paho logging to the mlogic logging system.
  297 + * This callback is registered the first time a ClientPaho instance is constructed.
  298 + */
  299 + static void onLogPaho(enum MQTTASYNC_TRACE_LEVELS level, char* message);
  300 +
  301 + mutable std::mutex m_mutex;
  302 + std::string m_endpoint;
  303 + std::string m_username;
  304 + std::string m_password;
  305 + std::string m_clientId;
  306 + std::set<MQTTAsync_token> m_pendingOperations;
  307 + std::map<MQTTAsync_token, bool> m_operationResult;
  308 + mutable std::condition_variable m_operationsCompleteCV;
  309 + std::map<std::string, Subscription> m_subscriptions;
  310 + std::map<std::string, Subscription> m_pendingSubscriptions;
  311 + std::map<MQTTAsync_token, std::string> m_subscribeTokenToTopic;
  312 + std::map<MQTTAsync_token, std::string> m_unsubscribeTokenToTopic;
  313 + std::deque<Publish> m_pendingPublishes;
  314 + bool m_processPendingPublishes;
  315 + mutable std::condition_variable m_pendingPublishesReadyCV;
  316 + ::MQTTAsync m_client;
  317 + std::atomic<ConnectionStatus> m_connectionStatus;
  318 + std::function<void(const std::string&, ConnectionStatus)> m_connectionStatusCallback;
  319 + std::function<void(const std::string&, std::int32_t)> m_deliveryCompleteCallback;
  320 + MQTTAsync_token m_lastUnsubscribe; ///< centos7 workaround
  321 + std::unique_ptr<std::promise<void>> m_connectPromise;
  322 + std::unique_ptr<std::promise<void>> m_disconnectPromise;
  323 +
  324 + SynchronizedQueue<std::function<void()>> m_callbackEventQueue;
  325 + std::thread m_workerThread;
  326 +
  327 + static std::atomic_int s_numberOfInstances;
  328 +};
  329 +
  330 +} // End namespace mqtt
  331 +} // End namespace components
  332 +} // End namespace osdev
  333 +
  334 +#endif // OSDEV_COMPONENTS_MQTT_CLIENTPAHO_H
... ...
src/commondefs.cpp 0 → 100644
  1 +++ a/src/commondefs.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "commondefs.h"
  20 +
  21 +// std
  22 +#include <ctime>
  23 +#include <iomanip>
  24 +
  25 +namespace osdev {
  26 +namespace components {
  27 +namespace mqtt {
  28 +
  29 +std::ostream& operator<<(std::ostream& os, const StdTimeMs& rhs)
  30 +{
  31 + const std::time_t rhsTimeT = std::chrono::system_clock::to_time_t(rhs);
  32 + struct tm tms
  33 + {
  34 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr
  35 + };
  36 + if (nullptr == gmtime_r(&rhsTimeT, &tms)) {
  37 + os << "unknown";
  38 + }
  39 + else {
  40 + char str[26] = { 0 };
  41 + if (std::strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S.", &tms) > 0) {
  42 + std::ostringstream oss;
  43 + oss << str << std::setfill('0') << std::setw(3) << std::chrono::duration_cast<std::chrono::milliseconds>(rhs.timePoint - std::chrono::system_clock::from_time_t(rhsTimeT)).count() << ' ';
  44 + if (std::strftime(str, sizeof(str), "%z", &tms) == 0) {
  45 + return os;
  46 + }
  47 + oss << str;
  48 + os << oss.str();
  49 + }
  50 + }
  51 + return os;
  52 +}
  53 +
  54 +} // End namespace mqtt
  55 +} // End namespace components
  56 +} // End namespace osdev
  57 +
  58 +namespace std {
  59 +
  60 +std::ostream& operator<<(std::ostream& os, const osdev::components::mqtt::StdTime& rhs)
  61 +{
  62 + /// @todo: Use put_time when it's implemented in gcc (5.0)
  63 + // auto rhsTimeT = std::chrono::system_clock::to_time_t(rhs);
  64 + // os << std::put_time(std::localtime(&rhsTimeT), "%F %T");
  65 + const std::time_t rhsTimeT = std::chrono::system_clock::to_time_t(rhs);
  66 + struct tm tms
  67 + {
  68 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr
  69 + };
  70 + if (nullptr == gmtime_r(&rhsTimeT, &tms)) {
  71 + os << "unknown";
  72 + }
  73 + else {
  74 + char str[26];
  75 + if (std::strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %z", &tms) > 0) {
  76 + os << str;
  77 + }
  78 + }
  79 + return os;
  80 +}
  81 +
  82 +} // End namespace std
... ...
src/commondefs.h 0 → 100644
  1 +++ a/src/commondefs.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_COMMONDEFS_H
  2 +#define OSDEV_COMPONENTS_MQTT_COMMONDEFS_H
  3 +
  4 +// std
  5 +#include <chrono>
  6 +#include <cstdint>
  7 +#include <map>
  8 +#include <ostream>
  9 +#include <set>
  10 +#include <string>
  11 +#include <vector>
  12 +
  13 +// boost
  14 +#include <boost/lexical_cast.hpp>
  15 +#include <boost/optional.hpp>
  16 +#include <boost/uuid/uuid.hpp>
  17 +#include <boost/uuid/uuid_io.hpp>
  18 +
  19 +#include "utils.h"
  20 +
  21 +
  22 +/// @brief Check if an id is valid
  23 +/// @throws InvalidArgumentException if id is invalid
  24 +#define OSDEV_COMPONENTS_CHECKMQTTID(id) \
  25 + [&] { \
  26 + if (id.is_nil()) { \
  27 + } \
  28 + }()
  29 +
  30 +
  31 +namespace osdev {
  32 +namespace components {
  33 +namespace mqtt {
  34 +
  35 +using MqttId = boost::uuids::uuid;
  36 +using OptionalId = MqttId;
  37 +using MqttIdSet = std::set<MqttId>;
  38 +using MqttIdSetIterator = MqttIdSet::const_iterator;
  39 +using MqttIdSetDelta = std::pair<MqttIdSet, MqttIdSet>;
  40 +using StdTime = std::chrono::system_clock::time_point;
  41 +using OptionalTime = boost::optional<StdTime>;
  42 +using StdTimeVec = std::vector<StdTime>;
  43 +using SequenceNumber = std::uint32_t;
  44 +using OptionalSequenceNumber = boost::optional<SequenceNumber>;
  45 +using CustomField = std::string;
  46 +using CustomFieldCollection = std::vector<CustomField>;
  47 +
  48 +using CountryCodeEnum = std::int32_t;
  49 +
  50 +/**
  51 + * @brief Defines a parsed uri.
  52 + */
  53 +using ParsedUri = std::map<std::string, std::string>;
  54 +
  55 +/**
  56 + * @brief Type for the parsed query part of a uri.
  57 + */
  58 +using ParsedQuery = std::map<std::string, std::string>;
  59 +
  60 +/**
  61 + * @brief Type for the parsed path part of a uri.
  62 + */
  63 +using ParsedPath = std::vector<std::string>;
  64 +
  65 +/**
  66 + * @brief Defines a duration with the granularity of a day in seconds (24 * 60 * 60 = 86400).
  67 + * This duration can be used to create a time_point at midnight of a given DateTime amongst others.
  68 + *
  69 + * The representation is a signed type so that negative durations are also possible.
  70 + */
  71 +using days = std::chrono::duration<std::int32_t, std::ratio<86400>>;
  72 +
  73 +/**
  74 + * @brief Defines a duration with the granularity of a year in seconds. A year is a bit longer than 365 days (365.2425). If a year is
  75 + * subtracted from a date the time part of the new date will therefore differ from the time part of the subtracted from date.
  76 + *
  77 + * The representation is a signed type so that negative durations are also possible.
  78 + */
  79 +using years = std::chrono::duration<std::int32_t, std::ratio<31556952>>; // excactly 365 days would give 31536000
  80 +
  81 +/**
  82 + * A timepoint type that is printed with millisecond resolution.
  83 + */
  84 +struct StdTimeMs
  85 +{
  86 + StdTimeMs(const StdTime& tp)
  87 + : timePoint(tp)
  88 + {
  89 + }
  90 +
  91 + operator StdTime() const { return timePoint; }
  92 +
  93 + StdTime timePoint;
  94 +};
  95 +
  96 +std::ostream& operator<<(std::ostream& os, const StdTimeMs& rhs);
  97 +
  98 +} // End namespace mqtt
  99 +} // End namespace components
  100 +} // End namespace osdev
  101 +
  102 +namespace std {
  103 +
  104 +std::ostream& operator<<(std::ostream& os, const osdev::components::mqtt::StdTime& rhs);
  105 +
  106 +} // End namespace std
  107 +
  108 +#endif // OSDEV_COMPONENTS_MQTT_COMMONDEFS_H
... ...
src/compat-c++14.h 0 → 100644
  1 +++ a/src/compat-c++14.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_COMPATCXX14
  20 +#define OSDEV_COMPONENTS_COMPATCXX14
  21 +
  22 +#include <memory>
  23 +
  24 +// The below code must be skipped if we use a C++14 or newer compiler
  25 +#if __cplusplus == 201103L
  26 +
  27 +namespace std {
  28 +/// Copied from libstdc++ 4.9.2 bits/unique_ptr.h
  29 +
  30 +template <typename _Tp>
  31 +struct _MakeUniq
  32 +{
  33 + typedef unique_ptr<_Tp> __single_object;
  34 +};
  35 +
  36 +template <typename _Tp>
  37 +struct _MakeUniq<_Tp[]>
  38 +{
  39 + typedef unique_ptr<_Tp[]> __array;
  40 +};
  41 +
  42 +template <typename _Tp, size_t _Bound>
  43 +struct _MakeUniq<_Tp[_Bound]>
  44 +{
  45 + struct __invalid_type
  46 + {
  47 + };
  48 +};
  49 +
  50 +/// std::make_unique for single objects
  51 +template <typename _Tp, typename... _Args>
  52 +inline typename _MakeUniq<_Tp>::__single_object make_unique(_Args&&... __args)
  53 +{
  54 + return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...));
  55 +}
  56 +
  57 +/// std::make_unique for arrays of unknown bound
  58 +template <typename _Tp>
  59 +inline typename _MakeUniq<_Tp>::__array make_unique(size_t __num)
  60 +{
  61 + return unique_ptr<_Tp>(new typename remove_extent<_Tp>::type[__num]());
  62 +}
  63 +
  64 +/// Disable std::make_unique for arrays of known bound
  65 +template <typename _Tp, typename... _Args>
  66 +inline typename _MakeUniq<_Tp>::__invalid_type make_unique(_Args&&...) = delete;
  67 +
  68 +} // End namespace std
  69 +
  70 +#endif // Check for C++14
  71 +
  72 +#endif
... ...
src/compat-chrono.h 0 → 100644
  1 +++ a/src/compat-chrono.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include <chrono>
  20 +#include <type_traits>
  21 +
  22 +// function std::chrono::ceil is added in the c++17 standard.
  23 +// Added implementation from https://en.cppreference.com/w/cpp/chrono/duration/ceil
  24 +// to this compatibility header. The header only defines this implementation when
  25 +// the c++ compiler is used with an older standard.
  26 +
  27 +#if !defined __cpp_lib_chrono || __cpp_lib_chrono < 201611
  28 +
  29 +namespace std {
  30 +namespace chrono {
  31 +
  32 +template <class T>
  33 +struct is_duration : std::false_type
  34 +{
  35 +};
  36 +template <class Rep, class Period>
  37 +struct is_duration<
  38 + std::chrono::duration<Rep, Period>> : std::true_type
  39 +{
  40 +};
  41 +
  42 +template <class To, class Rep, class Period,
  43 + class = typename enable_if<is_duration<To>{}>::type>
  44 +To ceil(const std::chrono::duration<Rep, Period>& d)
  45 +{
  46 + To t = std::chrono::duration_cast<To>(d);
  47 + if (t < d)
  48 + return t + To{ 1 };
  49 + return t;
  50 +}
  51 +
  52 +} // End namespace chrono
  53 +} // End namespace std
  54 +
  55 +#endif
... ...
src/compiletimedigits.h 0 → 100644
  1 +++ a/src/compiletimedigits.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_COMPILETIMEDIGITS_H
  20 +#define OSDEV_COMPONENTS_MQTT_COMPILETIMEDIGITS_H
  21 +
  22 +// std
  23 +#include <string>
  24 +
  25 +#include "compiletimestring.h"
  26 +
  27 +namespace osdev {
  28 +namespace components {
  29 +namespace mqtt {
  30 +
  31 +/**
  32 + * @brief Calculate the number of digits needed to represent a given value in a given base.
  33 + * @tparam B The base to use.
  34 + * @param value The value to represent.
  35 + * @return The number of digits.
  36 + */
  37 +template <unsigned B>
  38 +constexpr std::size_t numberOfDigits(unsigned value) noexcept
  39 +{
  40 + static_assert(B > 0, "base must be larger than zero");
  41 + return (value / B) == 0 ? 1 : (1 + numberOfDigits<B>(value / B));
  42 +}
  43 +
  44 +/**
  45 + * @return stringified digit that represents the given single digit value.
  46 + * @note Values can range from 0 to 37 inclusive which maps on 0-9A-Z.
  47 + */
  48 +template <unsigned value>
  49 +constexpr char digit() noexcept
  50 +{
  51 + static_assert(value <= 37, "Value must lie between 0 and 37 both inclusive");
  52 + return (value < 10 ? '0' + static_cast<char>(value) : 'A' + static_cast<char>(value) - 10);
  53 +}
  54 +
  55 +/**
  56 + * @brief Recursive template definition that is used to determine the value of a given number N in a given base B.
  57 + * @tparam N The number to obtain the representation for.
  58 + * @tparam B The base to use.
  59 + * @tparam Len The length of the representation including the 0 terminator.
  60 + * If not provided the length is calculated on first instantiation of this template.
  61 + * @tparam remains Empty parameter pack on first instantiation. Will be filled further by every following instantiation.
  62 + */
  63 +template <unsigned N, unsigned B, std::size_t Len = numberOfDigits<B>(N) + 1, unsigned... remains>
  64 +struct Digits
  65 +{
  66 +
  67 + static constexpr auto create() noexcept -> std::array<char, Len>
  68 + {
  69 + static_assert(B > 0, "base must be larger than zero");
  70 + return Digits<N / B, B, Len, digit<N % B>(), remains...>::create();
  71 + }
  72 +};
  73 +
  74 +/**
  75 + * @brief Termination template that will return the actual hex digit array that represents the given value.
  76 + * @tparam B The base to use.
  77 + * @tparam Len The length of the hexadecimal representation including 0 terminator.
  78 + * @tparam remains Parameter pack that contains the hexadecimal character representations.
  79 + */
  80 +template <unsigned B, std::size_t Len, unsigned... remains>
  81 +struct Digits<0, B, Len, remains...>
  82 +{
  83 +
  84 + static constexpr auto create() noexcept -> std::array<char, Len>
  85 + {
  86 + static_assert(sizeof...(remains) + 1 == Len, "Parameter 'Len' must be equal to the length of the parameter pack 'remains' including the termination zero");
  87 + return std::array<char, Len>{ { remains..., 0 } };
  88 + }
  89 +};
  90 +
  91 +/**
  92 + * @brief Digits builder can be used in combination with the compiletime_string.
  93 + * @tparam N The number to obtain the compile time string representation for.
  94 + * @tparam B The base to use (up to 37).
  95 + * This template struct defines a produce() method and the return type of that method.
  96 + */
  97 +template <unsigned N, unsigned B>
  98 +struct ProduceDigits
  99 +{
  100 + /**
  101 + * @brief Return type definition of the produce method.
  102 + */
  103 + using return_t = std::array<char, numberOfDigits<B>(N) + 1>;
  104 +
  105 + /**
  106 + * @brief Produce the actual representation.
  107 + */
  108 + static constexpr return_t produce() noexcept
  109 + {
  110 + return Digits<N, B>::create();
  111 + }
  112 +};
  113 +
  114 +} // End namespace mqtt
  115 +} // End namespace components
  116 +} // End namespace osdev
  117 +
  118 +#endif // OSDEV_COMPONENTS_MQTT_COMPILETIMEDIGITS_H
... ...
src/compiletimestring.h 0 → 100644
  1 +++ a/src/compiletimestring.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_COMPILETIMESTRING_H
  20 +#define OSDEV_COMPONENTS_MQTT_COMPILETIMESTRING_H
  21 +
  22 +/// @file
  23 +///
  24 +/// Support for compiletime string building.
  25 +///
  26 +/// In some cases it is necessary to embed string messages as read only string literals
  27 +/// so that they can be used in low memory conditions for example.
  28 +/// This header defines two macro's that can be used to build managed string literals.
  29 +/// These managed string literals can be concatenated and searched at compiletime.
  30 +/// When the compiler is done with this code only the embedded string literal that are
  31 +/// used by normal expressions remain in the binary.
  32 +/// The implementation is a mix of meta template programming and the use of constexpr
  33 +/// expressions and is based on an idea found on <a href="http://stackoverflow.com/a/15912824">stackoverflow</a>.
  34 +/// This code is meant to handle small strings (up to say 100 characters). Larger strings can trigger the maximum
  35 +/// template recursion depth of the compiler (-ftemplate-depth). This depth is set at 900. So in principle strings
  36 +/// of 900 characters can be handled. However this is very inefficient and will prolong compiletime severly.
  37 +///
  38 +/// Here follows an example of how this code can be used.
  39 +/// @code
  40 +/// auto str1 = OSDEV_COMPONENTS_CSTRING("This is a test");
  41 +/// auto str2 = OSDEV_COMPONENTS_CSTRING(" with concatenation");
  42 +/// auto str3 = str1 + str2;
  43 +/// const char* s = str3.chars;
  44 +/// @endcode
  45 +/// str3.chars contains the compiletime concatenated string.
  46 +///
  47 +/// When examing the binary one will see that it contains the null terminated string literal
  48 +/// @code
  49 +/// This is a test with concatenation^@
  50 +/// @endcode
  51 +/// The str1 and str2 literals are discarded since they are never used in a non compiletime way.
  52 +/// @note This code is not meant to handle string literals with internal \0 characters. Beware!
  53 +
  54 +#include <limits>
  55 +
  56 +#include "metaprogrammingdefs.h"
  57 +
  58 +/// @brief Build a managed substring string literal from a string literal input
  59 +/// The strings are build compile time.
  60 +/// The upper bound is one past the last character that the caller is interested in.
  61 +#define OSDEV_COMPONENTS_CSTRING_BOUNDED(string_literal, lower, upper) \
  62 + [] { \
  63 + constexpr std::size_t lb = (lower); \
  64 + constexpr std::size_t ub = (upper); \
  65 + static_assert(lb <= ub, "lower bound must be smaller than or equal to upper bound"); \
  66 + static_assert(ub < sizeof(string_literal), \
  67 + "upper bound must be smaller than or equal to the length of the string"); \
  68 + struct constexpr_string_type \
  69 + { \
  70 + const char* chars = string_literal; \
  71 + }; \
  72 + return typename osdev::components::mqtt::apply_bounded_range<lb, ub, \
  73 + osdev::components::mqtt::string_builder<constexpr_string_type>::template produce>::result{}; \
  74 + }()
  75 +
  76 +/// @brief Build a managed string literal from a string literal input
  77 +#define OSDEV_COMPONENTS_CSTRING(string_literal) \
  78 + OSDEV_COMPONENTS_CSTRING_BOUNDED(string_literal, 0, sizeof(string_literal) - 1)
  79 +
  80 +namespace osdev {
  81 +namespace components {
  82 +namespace mqtt {
  83 +
  84 +/// @brief Managed string literal.
  85 +/// This class is used to hold string literals at compile time.
  86 +template <char... str>
  87 +struct compiletime_string
  88 +{
  89 + /// @brief Declaration of the string
  90 + static constexpr const char chars[sizeof...(str) + 1] = { str..., '\0' };
  91 +};
  92 +
  93 +/// @brief Definition of the string
  94 +template <char... str>
  95 +constexpr const char compiletime_string<str...>::chars[sizeof...(str) + 1];
  96 +
  97 +/// @brief Managed string literal builder.
  98 +/// This class is used to build string literals at compile time.
  99 +template <typename lambda_str_type>
  100 +struct string_builder
  101 +{
  102 + /// @brief maps indices list on the char array
  103 + template <std::size_t... indices>
  104 + struct produce
  105 + {
  106 + typedef compiletime_string<lambda_str_type{}.chars[indices]...> result;
  107 + };
  108 +};
  109 +
  110 +/// @brief Adapter for coupling other producers to string_builder.
  111 +/// tparam T The type of producer to adapt.
  112 +/// @note The adapter must define its return type as return_t and provide a static method produce() which produces the result.
  113 +/// The return type must be a kind of array type of chars (char[len], std::array<char,len>, etc).
  114 +template <typename T>
  115 +struct adapt_for_string_builder
  116 +{
  117 + static constexpr typename T::return_t chars = T::produce();
  118 +};
  119 +
  120 +/// @brief Concatenate two managed string literals
  121 +/// The literals are packed in a variadic template.
  122 +/// These templates are formed by the MLOGIC_COMMON_CSTRING* macro's
  123 +/// The concatenation is done at compiletime.
  124 +template <char... str0, char... str1>
  125 +compiletime_string<str0..., str1...> operator+(compiletime_string<str0...>, compiletime_string<str1...>)
  126 +{
  127 + return {};
  128 +}
  129 +
  130 +/// @brief Search for a character in a string literal from the right side.
  131 +/// The character index is returned relative to the left side.
  132 +/// If the character is not found a std::size_t(-1) is returned.
  133 +/// The default search starts at the end of the string. The index
  134 +/// can be given to start the search at another point in the string.
  135 +/// The index is given as nr of characters from the right end side of
  136 +/// the string. To proceed with a search see following example.
  137 +/// @code
  138 +/// constexpr const char s[] = "This is a test";
  139 +/// constexpr std::size_t index = osdev_components::mqtt::rfind(s, 'i'); // gives 5
  140 +/// static_assert(index == 5, "");
  141 +/// constexpr std::size_t index2 = osdev::components::mqtt::rfind(s, 'i', sizeof(s) - index); // gives 2
  142 +/// static_assert(index2 == 2, "");
  143 +/// @endcode
  144 +/// This function should only be used at compile time!
  145 +template <std::size_t N>
  146 +constexpr std::size_t rfind(const char (&str)[N], char c, std::size_t index = 0)
  147 +{
  148 + return index >= N ? std::numeric_limits<std::size_t>::max() : str[N - index - 1] == c ? N - index - 1 : rfind(str, c, index + 1);
  149 +}
  150 +
  151 +} // End namespace mqtt
  152 +} // End namespace components
  153 +} // End namespace osdev
  154 +
  155 +#endif // OSDEV_COMPONENTS_MQTT_COMPILETIMESTRING_H
... ...
src/connectionstatus.cpp 0 → 100644
  1 +++ a/src/connectionstatus.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "connectionstatus.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +std::ostream& operator<<(std::ostream &os, ConnectionStatus rhs)
  24 +{
  25 + switch( rhs )
  26 + {
  27 + case ConnectionStatus::Disconnected:
  28 + return os << "Disconnected";
  29 + case ConnectionStatus::DisconnectInProgress:
  30 + return os << "DisconnectInProgress";
  31 + case ConnectionStatus::ConnectInProgress:
  32 + return os << "ConnectInProgress";
  33 + case ConnectionStatus::ReconnectInProgress:
  34 + return os << "ReconnectInProgress";
  35 + case ConnectionStatus::Connected:
  36 + return os << "Connected";
  37 + }
  38 + return os << "Unknown"; // Should never be reached.
  39 +}
... ...
src/connectionstatus.h 0 → 100644
  1 +++ a/src/connectionstatus.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_CONNECTIONSTATUS_H
  2 +#define OSDEV_COMPONENTS_MQTT_CONNECTIONSTATUS_H
  3 +
  4 +// std
  5 +#include <ostream>
  6 +
  7 +namespace osdev {
  8 +namespace components {
  9 +namespace mqtt {
  10 +
  11 +/*!
  12 + * \brief Enumeration for MQTT connection Status
  13 + */
  14 +enum class ConnectionStatus
  15 +{
  16 + Disconnected, ///< Client is disconnected.
  17 + DisconnectInProgress, ///< Client is being disconnected.
  18 + ConnectInProgress, ///< Client is being connected.
  19 + ReconnectInProgress, ///< Client tries to reconnect.
  20 + Connected, ///< Client is connected.
  21 +};
  22 +
  23 +/*!
  24 + * \brief Stream operator for the connection status
  25 + */
  26 +std::ostream& operator<<(std::ostream &os, ConnectionStatus rhs);
  27 +
  28 +} // End namespace mqtt
  29 +} // End namespace components
  30 +} // End namespace osdev
  31 +
  32 +#endif // OSDEV_COMPONENTS_MQTT_CONNECTIONSTATUS_H
... ...
src/credentials.cpp 0 → 100644
  1 +++ a/src/credentials.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "credentials.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +Credentials::Credentials()
  24 + : m_username()
  25 + , m_password()
  26 +{}
  27 +
  28 +Credentials::Credentials( const std::string &username, const std::string &password )
  29 + : m_username( username )
  30 + , m_password( password )
  31 +{}
... ...
src/credentials.h 0 → 100644
  1 +++ a/src/credentials.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_CREDENTIALS_H
  2 +#define OSDEV_COMPONENTS_MQTT_CREDENTIALS_H
  3 +
  4 +// std
  5 +#include <string>
  6 +
  7 +namespace osdev {
  8 +namespace components {
  9 +namespace mqtt {
  10 +
  11 +/*!
  12 + * \brief Class that holds user credentials
  13 + */
  14 +class Credentials
  15 +{
  16 +public:
  17 + /*!
  18 + * \brief Default CTor, empty credentials
  19 + */
  20 + Credentials();
  21 +
  22 + /*!
  23 + * \brief Constructor for username/password credentials
  24 + * \param username - The username
  25 + * \param password - The password
  26 + */
  27 + Credentials(const std::string &username, const std::string &password);
  28 +
  29 + const std::string& username() const { return m_username; }
  30 + const std::string& password() const { return m_password; }
  31 +private:
  32 + std::string m_username;
  33 + std::string m_password;
  34 +};
  35 +
  36 +} // End namespace mqtt
  37 +} // End namespace components
  38 +} // End namespace osdev
  39 +
  40 +#endif // OSDEV_COMPONENTS_MQTT_CREDENTIALS_H
... ...
src/date.h 0 → 100644
Changes suppressed. Click to show
  1 +++ a/src/date.h
  1 +#ifndef DATE_H
  2 +#define DATE_H
  3 +
  4 +// The MIT License (MIT)
  5 +//
  6 +// Copyright (c) 2015, 2016, 2017 Howard Hinnant
  7 +// Copyright (c) 2016 Adrian Colomitchi
  8 +// Copyright (c) 2017 Florian Dang
  9 +// Copyright (c) 2017 Paul Thompson
  10 +// Copyright (c) 2018, 2019 Tomasz Kamiński
  11 +// Copyright (c) 2019 Jiangang Zhuang
  12 +//
  13 +// Permission is hereby granted, free of charge, to any person obtaining a copy
  14 +// of this software and associated documentation files (the "Software"), to deal
  15 +// in the Software without restriction, including without limitation the rights
  16 +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  17 +// copies of the Software, and to permit persons to whom the Software is
  18 +// furnished to do so, subject to the following conditions:
  19 +//
  20 +// The above copyright notice and this permission notice shall be included in all
  21 +// copies or substantial portions of the Software.
  22 +//
  23 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24 +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25 +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27 +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  28 +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  29 +// SOFTWARE.
  30 +//
  31 +// Our apologies. When the previous paragraph was written, lowercase had not yet
  32 +// been invented (that would involve another several millennia of evolution).
  33 +// We did not mean to shout.
  34 +
  35 +#ifndef HAS_STRING_VIEW
  36 +# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
  37 +# define HAS_STRING_VIEW 1
  38 +# else
  39 +# define HAS_STRING_VIEW 0
  40 +# endif
  41 +#endif // HAS_STRING_VIEW
  42 +
  43 +#include <cassert>
  44 +#include <algorithm>
  45 +#include <cctype>
  46 +#include <chrono>
  47 +#include <climits>
  48 +#include <cmath>
  49 +#include <cstddef>
  50 +#include <cstdint>
  51 +#include <cstdlib>
  52 +#include <ctime>
  53 +#include <ios>
  54 +#include <istream>
  55 +#include <iterator>
  56 +#include <limits>
  57 +#include <locale>
  58 +#include <memory>
  59 +#include <ostream>
  60 +#include <ratio>
  61 +#include <sstream>
  62 +#include <stdexcept>
  63 +#include <string>
  64 +#if HAS_STRING_VIEW
  65 +# include <string_view>
  66 +#endif
  67 +#include <utility>
  68 +#include <type_traits>
  69 +
  70 +#ifdef __GNUC__
  71 +# pragma GCC diagnostic push
  72 +# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7)
  73 +# pragma GCC diagnostic ignored "-Wpedantic"
  74 +# endif
  75 +# if __GNUC__ < 5
  76 + // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers
  77 +# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
  78 +# endif
  79 +#endif
  80 +
  81 +#ifdef _MSC_VER
  82 +# pragma warning(push)
  83 +// warning C4127: conditional expression is constant
  84 +# pragma warning(disable : 4127)
  85 +#endif
  86 +
  87 +namespace date
  88 +{
  89 +
  90 +//---------------+
  91 +// Configuration |
  92 +//---------------+
  93 +
  94 +#ifndef ONLY_C_LOCALE
  95 +# define ONLY_C_LOCALE 0
  96 +#endif
  97 +
  98 +#if defined(_MSC_VER) && (!defined(__clang__) || (_MSC_VER < 1910))
  99 +// MSVC
  100 +# ifndef _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING
  101 +# define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING
  102 +# endif
  103 +# if _MSC_VER < 1910
  104 +// before VS2017
  105 +# define CONSTDATA const
  106 +# define CONSTCD11
  107 +# define CONSTCD14
  108 +# define NOEXCEPT _NOEXCEPT
  109 +# else
  110 +// VS2017 and later
  111 +# define CONSTDATA constexpr const
  112 +# define CONSTCD11 constexpr
  113 +# define CONSTCD14 constexpr
  114 +# define NOEXCEPT noexcept
  115 +# endif
  116 +
  117 +#elif defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x5150
  118 +// Oracle Developer Studio 12.6 and earlier
  119 +# define CONSTDATA constexpr const
  120 +# define CONSTCD11 constexpr
  121 +# define CONSTCD14
  122 +# define NOEXCEPT noexcept
  123 +
  124 +#elif __cplusplus >= 201402
  125 +// C++14
  126 +# define CONSTDATA constexpr const
  127 +# define CONSTCD11 constexpr
  128 +# define CONSTCD14 constexpr
  129 +# define NOEXCEPT noexcept
  130 +#else
  131 +// C++11
  132 +# define CONSTDATA constexpr const
  133 +# define CONSTCD11 constexpr
  134 +# define CONSTCD14
  135 +# define NOEXCEPT noexcept
  136 +#endif
  137 +
  138 +#ifndef HAS_UNCAUGHT_EXCEPTIONS
  139 +# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
  140 +# define HAS_UNCAUGHT_EXCEPTIONS 1
  141 +# else
  142 +# define HAS_UNCAUGHT_EXCEPTIONS 0
  143 +# endif
  144 +#endif // HAS_UNCAUGHT_EXCEPTIONS
  145 +
  146 +#ifndef HAS_VOID_T
  147 +# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
  148 +# define HAS_VOID_T 1
  149 +# else
  150 +# define HAS_VOID_T 0
  151 +# endif
  152 +#endif // HAS_VOID_T
  153 +
  154 +// Protect from Oracle sun macro
  155 +#ifdef sun
  156 +# undef sun
  157 +#endif
  158 +
  159 +// Work around for a NVCC compiler bug which causes it to fail
  160 +// to compile std::ratio_{multiply,divide} when used directly
  161 +// in the std::chrono::duration template instantiations below
  162 +namespace detail {
  163 +template <typename R1, typename R2>
  164 +using ratio_multiply = decltype(std::ratio_multiply<R1, R2>{});
  165 +
  166 +template <typename R1, typename R2>
  167 +using ratio_divide = decltype(std::ratio_divide<R1, R2>{});
  168 +} // namespace detail
  169 +
  170 +//-----------+
  171 +// Interface |
  172 +//-----------+
  173 +
  174 +// durations
  175 +
  176 +using days = std::chrono::duration
  177 + <int, detail::ratio_multiply<std::ratio<24>, std::chrono::hours::period>>;
  178 +
  179 +using weeks = std::chrono::duration
  180 + <int, detail::ratio_multiply<std::ratio<7>, days::period>>;
  181 +
  182 +using years = std::chrono::duration
  183 + <int, detail::ratio_multiply<std::ratio<146097, 400>, days::period>>;
  184 +
  185 +using months = std::chrono::duration
  186 + <int, detail::ratio_divide<years::period, std::ratio<12>>>;
  187 +
  188 +// time_point
  189 +
  190 +template <class Duration>
  191 + using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;
  192 +
  193 +using sys_days = sys_time<days>;
  194 +using sys_seconds = sys_time<std::chrono::seconds>;
  195 +
  196 +struct local_t {};
  197 +
  198 +template <class Duration>
  199 + using local_time = std::chrono::time_point<local_t, Duration>;
  200 +
  201 +using local_seconds = local_time<std::chrono::seconds>;
  202 +using local_days = local_time<days>;
  203 +
  204 +// types
  205 +
  206 +struct last_spec
  207 +{
  208 + explicit last_spec() = default;
  209 +};
  210 +
  211 +class day;
  212 +class month;
  213 +class year;
  214 +
  215 +class weekday;
  216 +class weekday_indexed;
  217 +class weekday_last;
  218 +
  219 +class month_day;
  220 +class month_day_last;
  221 +class month_weekday;
  222 +class month_weekday_last;
  223 +
  224 +class year_month;
  225 +
  226 +class year_month_day;
  227 +class year_month_day_last;
  228 +class year_month_weekday;
  229 +class year_month_weekday_last;
  230 +
  231 +// date composition operators
  232 +
  233 +CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT;
  234 +CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT;
  235 +
  236 +CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT;
  237 +CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT;
  238 +CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT;
  239 +CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT;
  240 +CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT;
  241 +
  242 +CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT;
  243 +CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT;
  244 +CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT;
  245 +CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT;
  246 +
  247 +CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT;
  248 +CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT;
  249 +CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT;
  250 +CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT;
  251 +
  252 +CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT;
  253 +CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT;
  254 +CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT;
  255 +CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT;
  256 +
  257 +CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT;
  258 +CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT;
  259 +CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT;
  260 +CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT;
  261 +CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT;
  262 +CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT;
  263 +
  264 +CONSTCD11
  265 + year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT;
  266 +CONSTCD11
  267 + year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT;
  268 +CONSTCD11
  269 + year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT;
  270 +CONSTCD11
  271 + year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT;
  272 +CONSTCD11
  273 + year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT;
  274 +
  275 +CONSTCD11
  276 +year_month_weekday
  277 +operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT;
  278 +
  279 +CONSTCD11
  280 +year_month_weekday
  281 +operator/(const year& y, const month_weekday& mwd) NOEXCEPT;
  282 +
  283 +CONSTCD11
  284 +year_month_weekday
  285 +operator/(int y, const month_weekday& mwd) NOEXCEPT;
  286 +
  287 +CONSTCD11
  288 +year_month_weekday
  289 +operator/(const month_weekday& mwd, const year& y) NOEXCEPT;
  290 +
  291 +CONSTCD11
  292 +year_month_weekday
  293 +operator/(const month_weekday& mwd, int y) NOEXCEPT;
  294 +
  295 +CONSTCD11
  296 +year_month_weekday_last
  297 +operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT;
  298 +
  299 +CONSTCD11
  300 +year_month_weekday_last
  301 +operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT;
  302 +
  303 +CONSTCD11
  304 +year_month_weekday_last
  305 +operator/(int y, const month_weekday_last& mwdl) NOEXCEPT;
  306 +
  307 +CONSTCD11
  308 +year_month_weekday_last
  309 +operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT;
  310 +
  311 +CONSTCD11
  312 +year_month_weekday_last
  313 +operator/(const month_weekday_last& mwdl, int y) NOEXCEPT;
  314 +
  315 +// Detailed interface
  316 +
  317 +// day
  318 +
  319 +class day
  320 +{
  321 + unsigned char d_;
  322 +
  323 +public:
  324 + day() = default;
  325 + explicit CONSTCD11 day(unsigned d) NOEXCEPT;
  326 +
  327 + CONSTCD14 day& operator++() NOEXCEPT;
  328 + CONSTCD14 day operator++(int) NOEXCEPT;
  329 + CONSTCD14 day& operator--() NOEXCEPT;
  330 + CONSTCD14 day operator--(int) NOEXCEPT;
  331 +
  332 + CONSTCD14 day& operator+=(const days& d) NOEXCEPT;
  333 + CONSTCD14 day& operator-=(const days& d) NOEXCEPT;
  334 +
  335 + CONSTCD11 explicit operator unsigned() const NOEXCEPT;
  336 + CONSTCD11 bool ok() const NOEXCEPT;
  337 +};
  338 +
  339 +CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT;
  340 +CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT;
  341 +CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT;
  342 +CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT;
  343 +CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT;
  344 +CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT;
  345 +
  346 +CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT;
  347 +CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT;
  348 +CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT;
  349 +CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT;
  350 +
  351 +template<class CharT, class Traits>
  352 +std::basic_ostream<CharT, Traits>&
  353 +operator<<(std::basic_ostream<CharT, Traits>& os, const day& d);
  354 +
  355 +// month
  356 +
  357 +class month
  358 +{
  359 + unsigned char m_;
  360 +
  361 +public:
  362 + month() = default;
  363 + explicit CONSTCD11 month(unsigned m) NOEXCEPT;
  364 +
  365 + CONSTCD14 month& operator++() NOEXCEPT;
  366 + CONSTCD14 month operator++(int) NOEXCEPT;
  367 + CONSTCD14 month& operator--() NOEXCEPT;
  368 + CONSTCD14 month operator--(int) NOEXCEPT;
  369 +
  370 + CONSTCD14 month& operator+=(const months& m) NOEXCEPT;
  371 + CONSTCD14 month& operator-=(const months& m) NOEXCEPT;
  372 +
  373 + CONSTCD11 explicit operator unsigned() const NOEXCEPT;
  374 + CONSTCD11 bool ok() const NOEXCEPT;
  375 +};
  376 +
  377 +CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT;
  378 +CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT;
  379 +CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT;
  380 +CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT;
  381 +CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT;
  382 +CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT;
  383 +
  384 +CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT;
  385 +CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT;
  386 +CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT;
  387 +CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT;
  388 +
  389 +template<class CharT, class Traits>
  390 +std::basic_ostream<CharT, Traits>&
  391 +operator<<(std::basic_ostream<CharT, Traits>& os, const month& m);
  392 +
  393 +// year
  394 +
  395 +class year
  396 +{
  397 + short y_;
  398 +
  399 +public:
  400 + year() = default;
  401 + explicit CONSTCD11 year(int y) NOEXCEPT;
  402 +
  403 + CONSTCD14 year& operator++() NOEXCEPT;
  404 + CONSTCD14 year operator++(int) NOEXCEPT;
  405 + CONSTCD14 year& operator--() NOEXCEPT;
  406 + CONSTCD14 year operator--(int) NOEXCEPT;
  407 +
  408 + CONSTCD14 year& operator+=(const years& y) NOEXCEPT;
  409 + CONSTCD14 year& operator-=(const years& y) NOEXCEPT;
  410 +
  411 + CONSTCD11 year operator-() const NOEXCEPT;
  412 + CONSTCD11 year operator+() const NOEXCEPT;
  413 +
  414 + CONSTCD11 bool is_leap() const NOEXCEPT;
  415 +
  416 + CONSTCD11 explicit operator int() const NOEXCEPT;
  417 + CONSTCD11 bool ok() const NOEXCEPT;
  418 +
  419 + static CONSTCD11 year min() NOEXCEPT { return year{-32767}; }
  420 + static CONSTCD11 year max() NOEXCEPT { return year{32767}; }
  421 +};
  422 +
  423 +CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT;
  424 +CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT;
  425 +CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT;
  426 +CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT;
  427 +CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT;
  428 +CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT;
  429 +
  430 +CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT;
  431 +CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT;
  432 +CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT;
  433 +CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT;
  434 +
  435 +template<class CharT, class Traits>
  436 +std::basic_ostream<CharT, Traits>&
  437 +operator<<(std::basic_ostream<CharT, Traits>& os, const year& y);
  438 +
  439 +// weekday
  440 +
  441 +class weekday
  442 +{
  443 + unsigned char wd_;
  444 +public:
  445 + weekday() = default;
  446 + explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT;
  447 + CONSTCD14 weekday(const sys_days& dp) NOEXCEPT;
  448 + CONSTCD14 explicit weekday(const local_days& dp) NOEXCEPT;
  449 +
  450 + CONSTCD14 weekday& operator++() NOEXCEPT;
  451 + CONSTCD14 weekday operator++(int) NOEXCEPT;
  452 + CONSTCD14 weekday& operator--() NOEXCEPT;
  453 + CONSTCD14 weekday operator--(int) NOEXCEPT;
  454 +
  455 + CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT;
  456 + CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT;
  457 +
  458 + CONSTCD11 bool ok() const NOEXCEPT;
  459 +
  460 + CONSTCD11 unsigned c_encoding() const NOEXCEPT;
  461 + CONSTCD11 unsigned iso_encoding() const NOEXCEPT;
  462 +
  463 + CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT;
  464 + CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT;
  465 +
  466 +private:
  467 + static CONSTCD14 unsigned char weekday_from_days(int z) NOEXCEPT;
  468 +
  469 + friend CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT;
  470 + friend CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT;
  471 + friend CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT;
  472 + template<class CharT, class Traits>
  473 + friend std::basic_ostream<CharT, Traits>&
  474 + operator<<(std::basic_ostream<CharT, Traits>& os, const weekday& wd);
  475 + friend class weekday_indexed;
  476 +};
  477 +
  478 +CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT;
  479 +CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT;
  480 +
  481 +CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT;
  482 +CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT;
  483 +CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT;
  484 +CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT;
  485 +
  486 +template<class CharT, class Traits>
  487 +std::basic_ostream<CharT, Traits>&
  488 +operator<<(std::basic_ostream<CharT, Traits>& os, const weekday& wd);
  489 +
  490 +// weekday_indexed
  491 +
  492 +class weekday_indexed
  493 +{
  494 + unsigned char wd_ : 4;
  495 + unsigned char index_ : 4;
  496 +
  497 +public:
  498 + weekday_indexed() = default;
  499 + CONSTCD11 weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT;
  500 +
  501 + CONSTCD11 date::weekday weekday() const NOEXCEPT;
  502 + CONSTCD11 unsigned index() const NOEXCEPT;
  503 + CONSTCD11 bool ok() const NOEXCEPT;
  504 +};
  505 +
  506 +CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT;
  507 +CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT;
  508 +
  509 +template<class CharT, class Traits>
  510 +std::basic_ostream<CharT, Traits>&
  511 +operator<<(std::basic_ostream<CharT, Traits>& os, const weekday_indexed& wdi);
  512 +
  513 +// weekday_last
  514 +
  515 +class weekday_last
  516 +{
  517 + date::weekday wd_;
  518 +
  519 +public:
  520 + explicit CONSTCD11 weekday_last(const date::weekday& wd) NOEXCEPT;
  521 +
  522 + CONSTCD11 date::weekday weekday() const NOEXCEPT;
  523 + CONSTCD11 bool ok() const NOEXCEPT;
  524 +};
  525 +
  526 +CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT;
  527 +CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT;
  528 +
  529 +template<class CharT, class Traits>
  530 +std::basic_ostream<CharT, Traits>&
  531 +operator<<(std::basic_ostream<CharT, Traits>& os, const weekday_last& wdl);
  532 +
  533 +namespace detail
  534 +{
  535 +
  536 +struct unspecified_month_disambiguator {};
  537 +
  538 +} // namespace detail
  539 +
  540 +// year_month
  541 +
  542 +class year_month
  543 +{
  544 + date::year y_;
  545 + date::month m_;
  546 +
  547 +public:
  548 + year_month() = default;
  549 + CONSTCD11 year_month(const date::year& y, const date::month& m) NOEXCEPT;
  550 +
  551 + CONSTCD11 date::year year() const NOEXCEPT;
  552 + CONSTCD11 date::month month() const NOEXCEPT;
  553 +
  554 + template<class = detail::unspecified_month_disambiguator>
  555 + CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT;
  556 + template<class = detail::unspecified_month_disambiguator>
  557 + CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT;
  558 + CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT;
  559 + CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT;
  560 +
  561 + CONSTCD11 bool ok() const NOEXCEPT;
  562 +};
  563 +
  564 +CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT;
  565 +CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT;
  566 +CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT;
  567 +CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT;
  568 +CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT;
  569 +CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT;
  570 +
  571 +template<class = detail::unspecified_month_disambiguator>
  572 +CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT;
  573 +template<class = detail::unspecified_month_disambiguator>
  574 +CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT;
  575 +template<class = detail::unspecified_month_disambiguator>
  576 +CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT;
  577 +
  578 +CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT;
  579 +CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT;
  580 +CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT;
  581 +CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT;
  582 +
  583 +template<class CharT, class Traits>
  584 +std::basic_ostream<CharT, Traits>&
  585 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month& ym);
  586 +
  587 +// month_day
  588 +
  589 +class month_day
  590 +{
  591 + date::month m_;
  592 + date::day d_;
  593 +
  594 +public:
  595 + month_day() = default;
  596 + CONSTCD11 month_day(const date::month& m, const date::day& d) NOEXCEPT;
  597 +
  598 + CONSTCD11 date::month month() const NOEXCEPT;
  599 + CONSTCD11 date::day day() const NOEXCEPT;
  600 +
  601 + CONSTCD14 bool ok() const NOEXCEPT;
  602 +};
  603 +
  604 +CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT;
  605 +CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT;
  606 +CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT;
  607 +CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT;
  608 +CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT;
  609 +CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT;
  610 +
  611 +template<class CharT, class Traits>
  612 +std::basic_ostream<CharT, Traits>&
  613 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_day& md);
  614 +
  615 +// month_day_last
  616 +
  617 +class month_day_last
  618 +{
  619 + date::month m_;
  620 +
  621 +public:
  622 + CONSTCD11 explicit month_day_last(const date::month& m) NOEXCEPT;
  623 +
  624 + CONSTCD11 date::month month() const NOEXCEPT;
  625 + CONSTCD11 bool ok() const NOEXCEPT;
  626 +};
  627 +
  628 +CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT;
  629 +CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT;
  630 +CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT;
  631 +CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT;
  632 +CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT;
  633 +CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT;
  634 +
  635 +template<class CharT, class Traits>
  636 +std::basic_ostream<CharT, Traits>&
  637 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_day_last& mdl);
  638 +
  639 +// month_weekday
  640 +
  641 +class month_weekday
  642 +{
  643 + date::month m_;
  644 + date::weekday_indexed wdi_;
  645 +public:
  646 + CONSTCD11 month_weekday(const date::month& m,
  647 + const date::weekday_indexed& wdi) NOEXCEPT;
  648 +
  649 + CONSTCD11 date::month month() const NOEXCEPT;
  650 + CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT;
  651 +
  652 + CONSTCD11 bool ok() const NOEXCEPT;
  653 +};
  654 +
  655 +CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT;
  656 +CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT;
  657 +
  658 +template<class CharT, class Traits>
  659 +std::basic_ostream<CharT, Traits>&
  660 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_weekday& mwd);
  661 +
  662 +// month_weekday_last
  663 +
  664 +class month_weekday_last
  665 +{
  666 + date::month m_;
  667 + date::weekday_last wdl_;
  668 +
  669 +public:
  670 + CONSTCD11 month_weekday_last(const date::month& m,
  671 + const date::weekday_last& wd) NOEXCEPT;
  672 +
  673 + CONSTCD11 date::month month() const NOEXCEPT;
  674 + CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT;
  675 +
  676 + CONSTCD11 bool ok() const NOEXCEPT;
  677 +};
  678 +
  679 +CONSTCD11
  680 + bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT;
  681 +CONSTCD11
  682 + bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT;
  683 +
  684 +template<class CharT, class Traits>
  685 +std::basic_ostream<CharT, Traits>&
  686 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_weekday_last& mwdl);
  687 +
  688 +// class year_month_day
  689 +
  690 +class year_month_day
  691 +{
  692 + date::year y_;
  693 + date::month m_;
  694 + date::day d_;
  695 +
  696 +public:
  697 + year_month_day() = default;
  698 + CONSTCD11 year_month_day(const date::year& y, const date::month& m,
  699 + const date::day& d) NOEXCEPT;
  700 + CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT;
  701 +
  702 + CONSTCD14 year_month_day(sys_days dp) NOEXCEPT;
  703 + CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT;
  704 +
  705 + template<class = detail::unspecified_month_disambiguator>
  706 + CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT;
  707 + template<class = detail::unspecified_month_disambiguator>
  708 + CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT;
  709 + CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT;
  710 + CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT;
  711 +
  712 + CONSTCD11 date::year year() const NOEXCEPT;
  713 + CONSTCD11 date::month month() const NOEXCEPT;
  714 + CONSTCD11 date::day day() const NOEXCEPT;
  715 +
  716 + CONSTCD14 operator sys_days() const NOEXCEPT;
  717 + CONSTCD14 explicit operator local_days() const NOEXCEPT;
  718 + CONSTCD14 bool ok() const NOEXCEPT;
  719 +
  720 +private:
  721 + static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT;
  722 + CONSTCD14 days to_days() const NOEXCEPT;
  723 +};
  724 +
  725 +CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT;
  726 +CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT;
  727 +CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT;
  728 +CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT;
  729 +CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT;
  730 +CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT;
  731 +
  732 +template<class = detail::unspecified_month_disambiguator>
  733 +CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT;
  734 +template<class = detail::unspecified_month_disambiguator>
  735 +CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT;
  736 +template<class = detail::unspecified_month_disambiguator>
  737 +CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT;
  738 +CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT;
  739 +CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT;
  740 +CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT;
  741 +
  742 +template<class CharT, class Traits>
  743 +std::basic_ostream<CharT, Traits>&
  744 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_day& ymd);
  745 +
  746 +// year_month_day_last
  747 +
  748 +class year_month_day_last
  749 +{
  750 + date::year y_;
  751 + date::month_day_last mdl_;
  752 +
  753 +public:
  754 + CONSTCD11 year_month_day_last(const date::year& y,
  755 + const date::month_day_last& mdl) NOEXCEPT;
  756 +
  757 + template<class = detail::unspecified_month_disambiguator>
  758 + CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT;
  759 + template<class = detail::unspecified_month_disambiguator>
  760 + CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT;
  761 + CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT;
  762 + CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT;
  763 +
  764 + CONSTCD11 date::year year() const NOEXCEPT;
  765 + CONSTCD11 date::month month() const NOEXCEPT;
  766 + CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT;
  767 + CONSTCD14 date::day day() const NOEXCEPT;
  768 +
  769 + CONSTCD14 operator sys_days() const NOEXCEPT;
  770 + CONSTCD14 explicit operator local_days() const NOEXCEPT;
  771 + CONSTCD11 bool ok() const NOEXCEPT;
  772 +};
  773 +
  774 +CONSTCD11
  775 + bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT;
  776 +CONSTCD11
  777 + bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT;
  778 +CONSTCD11
  779 + bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT;
  780 +CONSTCD11
  781 + bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT;
  782 +CONSTCD11
  783 + bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT;
  784 +CONSTCD11
  785 + bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT;
  786 +
  787 +template<class = detail::unspecified_month_disambiguator>
  788 +CONSTCD14
  789 +year_month_day_last
  790 +operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT;
  791 +
  792 +template<class = detail::unspecified_month_disambiguator>
  793 +CONSTCD14
  794 +year_month_day_last
  795 +operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT;
  796 +
  797 +CONSTCD11
  798 +year_month_day_last
  799 +operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT;
  800 +
  801 +CONSTCD11
  802 +year_month_day_last
  803 +operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT;
  804 +
  805 +template<class = detail::unspecified_month_disambiguator>
  806 +CONSTCD14
  807 +year_month_day_last
  808 +operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT;
  809 +
  810 +CONSTCD11
  811 +year_month_day_last
  812 +operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT;
  813 +
  814 +template<class CharT, class Traits>
  815 +std::basic_ostream<CharT, Traits>&
  816 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_day_last& ymdl);
  817 +
  818 +// year_month_weekday
  819 +
  820 +class year_month_weekday
  821 +{
  822 + date::year y_;
  823 + date::month m_;
  824 + date::weekday_indexed wdi_;
  825 +
  826 +public:
  827 + year_month_weekday() = default;
  828 + CONSTCD11 year_month_weekday(const date::year& y, const date::month& m,
  829 + const date::weekday_indexed& wdi) NOEXCEPT;
  830 + CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT;
  831 + CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT;
  832 +
  833 + template<class = detail::unspecified_month_disambiguator>
  834 + CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT;
  835 + template<class = detail::unspecified_month_disambiguator>
  836 + CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT;
  837 + CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT;
  838 + CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT;
  839 +
  840 + CONSTCD11 date::year year() const NOEXCEPT;
  841 + CONSTCD11 date::month month() const NOEXCEPT;
  842 + CONSTCD11 date::weekday weekday() const NOEXCEPT;
  843 + CONSTCD11 unsigned index() const NOEXCEPT;
  844 + CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT;
  845 +
  846 + CONSTCD14 operator sys_days() const NOEXCEPT;
  847 + CONSTCD14 explicit operator local_days() const NOEXCEPT;
  848 + CONSTCD14 bool ok() const NOEXCEPT;
  849 +
  850 +private:
  851 + static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT;
  852 + CONSTCD14 days to_days() const NOEXCEPT;
  853 +};
  854 +
  855 +CONSTCD11
  856 + bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT;
  857 +CONSTCD11
  858 + bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT;
  859 +
  860 +template<class = detail::unspecified_month_disambiguator>
  861 +CONSTCD14
  862 +year_month_weekday
  863 +operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT;
  864 +
  865 +template<class = detail::unspecified_month_disambiguator>
  866 +CONSTCD14
  867 +year_month_weekday
  868 +operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT;
  869 +
  870 +CONSTCD11
  871 +year_month_weekday
  872 +operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT;
  873 +
  874 +CONSTCD11
  875 +year_month_weekday
  876 +operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT;
  877 +
  878 +template<class = detail::unspecified_month_disambiguator>
  879 +CONSTCD14
  880 +year_month_weekday
  881 +operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT;
  882 +
  883 +CONSTCD11
  884 +year_month_weekday
  885 +operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT;
  886 +
  887 +template<class CharT, class Traits>
  888 +std::basic_ostream<CharT, Traits>&
  889 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_weekday& ymwdi);
  890 +
  891 +// year_month_weekday_last
  892 +
  893 +class year_month_weekday_last
  894 +{
  895 + date::year y_;
  896 + date::month m_;
  897 + date::weekday_last wdl_;
  898 +
  899 +public:
  900 + CONSTCD11 year_month_weekday_last(const date::year& y, const date::month& m,
  901 + const date::weekday_last& wdl) NOEXCEPT;
  902 +
  903 + template<class = detail::unspecified_month_disambiguator>
  904 + CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT;
  905 + template<class = detail::unspecified_month_disambiguator>
  906 + CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT;
  907 + CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT;
  908 + CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT;
  909 +
  910 + CONSTCD11 date::year year() const NOEXCEPT;
  911 + CONSTCD11 date::month month() const NOEXCEPT;
  912 + CONSTCD11 date::weekday weekday() const NOEXCEPT;
  913 + CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT;
  914 +
  915 + CONSTCD14 operator sys_days() const NOEXCEPT;
  916 + CONSTCD14 explicit operator local_days() const NOEXCEPT;
  917 + CONSTCD11 bool ok() const NOEXCEPT;
  918 +
  919 +private:
  920 + CONSTCD14 days to_days() const NOEXCEPT;
  921 +};
  922 +
  923 +CONSTCD11
  924 +bool
  925 +operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT;
  926 +
  927 +CONSTCD11
  928 +bool
  929 +operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT;
  930 +
  931 +template<class = detail::unspecified_month_disambiguator>
  932 +CONSTCD14
  933 +year_month_weekday_last
  934 +operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT;
  935 +
  936 +template<class = detail::unspecified_month_disambiguator>
  937 +CONSTCD14
  938 +year_month_weekday_last
  939 +operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT;
  940 +
  941 +CONSTCD11
  942 +year_month_weekday_last
  943 +operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT;
  944 +
  945 +CONSTCD11
  946 +year_month_weekday_last
  947 +operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT;
  948 +
  949 +template<class = detail::unspecified_month_disambiguator>
  950 +CONSTCD14
  951 +year_month_weekday_last
  952 +operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT;
  953 +
  954 +CONSTCD11
  955 +year_month_weekday_last
  956 +operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT;
  957 +
  958 +template<class CharT, class Traits>
  959 +std::basic_ostream<CharT, Traits>&
  960 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_weekday_last& ymwdl);
  961 +
  962 +#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
  963 +inline namespace literals
  964 +{
  965 +
  966 +CONSTCD11 date::day operator "" _d(unsigned long long d) NOEXCEPT;
  967 +CONSTCD11 date::year operator "" _y(unsigned long long y) NOEXCEPT;
  968 +
  969 +} // inline namespace literals
  970 +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900)
  971 +
  972 +// CONSTDATA date::month January{1};
  973 +// CONSTDATA date::month February{2};
  974 +// CONSTDATA date::month March{3};
  975 +// CONSTDATA date::month April{4};
  976 +// CONSTDATA date::month May{5};
  977 +// CONSTDATA date::month June{6};
  978 +// CONSTDATA date::month July{7};
  979 +// CONSTDATA date::month August{8};
  980 +// CONSTDATA date::month September{9};
  981 +// CONSTDATA date::month October{10};
  982 +// CONSTDATA date::month November{11};
  983 +// CONSTDATA date::month December{12};
  984 +//
  985 +// CONSTDATA date::weekday Sunday{0u};
  986 +// CONSTDATA date::weekday Monday{1u};
  987 +// CONSTDATA date::weekday Tuesday{2u};
  988 +// CONSTDATA date::weekday Wednesday{3u};
  989 +// CONSTDATA date::weekday Thursday{4u};
  990 +// CONSTDATA date::weekday Friday{5u};
  991 +// CONSTDATA date::weekday Saturday{6u};
  992 +
  993 +#if HAS_VOID_T
  994 +
  995 +template <class T, class = std::void_t<>>
  996 +struct is_clock
  997 + : std::false_type
  998 +{};
  999 +
  1000 +template <class T>
  1001 +struct is_clock<T, std::void_t<decltype(T::now()), typename T::rep, typename T::period,
  1002 + typename T::duration, typename T::time_point,
  1003 + decltype(T::is_steady)>>
  1004 + : std::true_type
  1005 +{};
  1006 +
  1007 +template<class T> inline constexpr bool is_clock_v = is_clock<T>::value;
  1008 +
  1009 +#endif // HAS_VOID_T
  1010 +
  1011 +//----------------+
  1012 +// Implementation |
  1013 +//----------------+
  1014 +
  1015 +// utilities
  1016 +namespace detail {
  1017 +
  1018 +template<class CharT, class Traits = std::char_traits<CharT>>
  1019 +class save_istream
  1020 +{
  1021 +protected:
  1022 + std::basic_ios<CharT, Traits>& is_;
  1023 + CharT fill_;
  1024 + std::ios::fmtflags flags_;
  1025 + std::streamsize precision_;
  1026 + std::streamsize width_;
  1027 + std::basic_ostream<CharT, Traits>* tie_;
  1028 + std::locale loc_;
  1029 +
  1030 +public:
  1031 + ~save_istream()
  1032 + {
  1033 + is_.fill(fill_);
  1034 + is_.flags(flags_);
  1035 + is_.precision(precision_);
  1036 + is_.width(width_);
  1037 + is_.imbue(loc_);
  1038 + is_.tie(tie_);
  1039 + }
  1040 +
  1041 + save_istream(const save_istream&) = delete;
  1042 + save_istream& operator=(const save_istream&) = delete;
  1043 +
  1044 + explicit save_istream(std::basic_ios<CharT, Traits>& is)
  1045 + : is_(is)
  1046 + , fill_(is.fill())
  1047 + , flags_(is.flags())
  1048 + , precision_(is.precision())
  1049 + , width_(is.width(0))
  1050 + , tie_(is.tie(nullptr))
  1051 + , loc_(is.getloc())
  1052 + {
  1053 + if (tie_ != nullptr)
  1054 + tie_->flush();
  1055 + }
  1056 +};
  1057 +
  1058 +template<class CharT, class Traits = std::char_traits<CharT>>
  1059 +class save_ostream
  1060 + : private save_istream<CharT, Traits>
  1061 +{
  1062 +public:
  1063 + ~save_ostream()
  1064 + {
  1065 + if ((this->flags_ & std::ios::unitbuf) &&
  1066 +#if HAS_UNCAUGHT_EXCEPTIONS
  1067 + std::uncaught_exceptions() == 0 &&
  1068 +#else
  1069 + !std::uncaught_exception() &&
  1070 +#endif
  1071 + this->is_.good())
  1072 + this->is_.rdbuf()->pubsync();
  1073 + }
  1074 +
  1075 + save_ostream(const save_ostream&) = delete;
  1076 + save_ostream& operator=(const save_ostream&) = delete;
  1077 +
  1078 + explicit save_ostream(std::basic_ios<CharT, Traits>& os)
  1079 + : save_istream<CharT, Traits>(os)
  1080 + {
  1081 + }
  1082 +};
  1083 +
  1084 +template <class T>
  1085 +struct choose_trunc_type
  1086 +{
  1087 + static const int digits = std::numeric_limits<T>::digits;
  1088 + using type = typename std::conditional
  1089 + <
  1090 + digits < 32,
  1091 + std::int32_t,
  1092 + typename std::conditional
  1093 + <
  1094 + digits < 64,
  1095 + std::int64_t,
  1096 +#ifdef __SIZEOF_INT128__
  1097 + __int128
  1098 +#else
  1099 + std::int64_t
  1100 +#endif
  1101 + >::type
  1102 + >::type;
  1103 +};
  1104 +
  1105 +template <class T>
  1106 +CONSTCD11
  1107 +inline
  1108 +typename std::enable_if
  1109 +<
  1110 + !std::chrono::treat_as_floating_point<T>::value,
  1111 + T
  1112 +>::type
  1113 +trunc(T t) NOEXCEPT
  1114 +{
  1115 + return t;
  1116 +}
  1117 +
  1118 +template <class T>
  1119 +CONSTCD14
  1120 +inline
  1121 +typename std::enable_if
  1122 +<
  1123 + std::chrono::treat_as_floating_point<T>::value,
  1124 + T
  1125 +>::type
  1126 +trunc(T t) NOEXCEPT
  1127 +{
  1128 + using std::numeric_limits;
  1129 + using I = typename choose_trunc_type<T>::type;
  1130 + CONSTDATA auto digits = numeric_limits<T>::digits;
  1131 + static_assert(digits < numeric_limits<I>::digits, "");
  1132 + CONSTDATA auto max = I{1} << (digits-1);
  1133 + CONSTDATA auto min = -max;
  1134 + const auto negative = t < T{0};
  1135 + if (min <= t && t <= max && t != 0 && t == t)
  1136 + {
  1137 + t = static_cast<T>(static_cast<I>(t));
  1138 + if (t == 0 && negative)
  1139 + t = -t;
  1140 + }
  1141 + return t;
  1142 +}
  1143 +
  1144 +template <std::intmax_t Xp, std::intmax_t Yp>
  1145 +struct static_gcd
  1146 +{
  1147 + static const std::intmax_t value = static_gcd<Yp, Xp % Yp>::value;
  1148 +};
  1149 +
  1150 +template <std::intmax_t Xp>
  1151 +struct static_gcd<Xp, 0>
  1152 +{
  1153 + static const std::intmax_t value = Xp;
  1154 +};
  1155 +
  1156 +template <>
  1157 +struct static_gcd<0, 0>
  1158 +{
  1159 + static const std::intmax_t value = 1;
  1160 +};
  1161 +
  1162 +template <class R1, class R2>
  1163 +struct no_overflow
  1164 +{
  1165 +private:
  1166 + static const std::intmax_t gcd_n1_n2 = static_gcd<R1::num, R2::num>::value;
  1167 + static const std::intmax_t gcd_d1_d2 = static_gcd<R1::den, R2::den>::value;
  1168 + static const std::intmax_t n1 = R1::num / gcd_n1_n2;
  1169 + static const std::intmax_t d1 = R1::den / gcd_d1_d2;
  1170 + static const std::intmax_t n2 = R2::num / gcd_n1_n2;
  1171 + static const std::intmax_t d2 = R2::den / gcd_d1_d2;
  1172 +#ifdef __cpp_constexpr
  1173 + static const std::intmax_t max = std::numeric_limits<std::intmax_t>::max();
  1174 +#else
  1175 + static const std::intmax_t max = LLONG_MAX;
  1176 +#endif
  1177 +
  1178 + template <std::intmax_t Xp, std::intmax_t Yp, bool overflow>
  1179 + struct mul // overflow == false
  1180 + {
  1181 + static const std::intmax_t value = Xp * Yp;
  1182 + };
  1183 +
  1184 + template <std::intmax_t Xp, std::intmax_t Yp>
  1185 + struct mul<Xp, Yp, true>
  1186 + {
  1187 + static const std::intmax_t value = 1;
  1188 + };
  1189 +
  1190 +public:
  1191 + static const bool value = (n1 <= max / d2) && (n2 <= max / d1);
  1192 + typedef std::ratio<mul<n1, d2, !value>::value,
  1193 + mul<n2, d1, !value>::value> type;
  1194 +};
  1195 +
  1196 +} // detail
  1197 +
  1198 +// trunc towards zero
  1199 +template <class To, class Rep, class Period>
  1200 +CONSTCD11
  1201 +inline
  1202 +typename std::enable_if
  1203 +<
  1204 + detail::no_overflow<Period, typename To::period>::value,
  1205 + To
  1206 +>::type
  1207 +trunc(const std::chrono::duration<Rep, Period>& d)
  1208 +{
  1209 + return To{detail::trunc(std::chrono::duration_cast<To>(d).count())};
  1210 +}
  1211 +
  1212 +template <class To, class Rep, class Period>
  1213 +CONSTCD11
  1214 +inline
  1215 +typename std::enable_if
  1216 +<
  1217 + !detail::no_overflow<Period, typename To::period>::value,
  1218 + To
  1219 +>::type
  1220 +trunc(const std::chrono::duration<Rep, Period>& d)
  1221 +{
  1222 + using std::chrono::duration_cast;
  1223 + using std::chrono::duration;
  1224 + using rep = typename std::common_type<Rep, typename To::rep>::type;
  1225 + return To{detail::trunc(duration_cast<To>(duration_cast<duration<rep>>(d)).count())};
  1226 +}
  1227 +
  1228 +#ifndef HAS_CHRONO_ROUNDING
  1229 +# if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023918 || (_MSC_FULL_VER >= 190000000 && defined (__clang__)))
  1230 +# define HAS_CHRONO_ROUNDING 1
  1231 +# elif defined(__cpp_lib_chrono) && __cplusplus > 201402 && __cpp_lib_chrono >= 201510
  1232 +# define HAS_CHRONO_ROUNDING 1
  1233 +# elif defined(_LIBCPP_VERSION) && __cplusplus > 201402 && _LIBCPP_VERSION >= 3800
  1234 +# define HAS_CHRONO_ROUNDING 1
  1235 +# else
  1236 +# define HAS_CHRONO_ROUNDING 0
  1237 +# endif
  1238 +#endif // HAS_CHRONO_ROUNDING
  1239 +
  1240 +#if HAS_CHRONO_ROUNDING == 0
  1241 +
  1242 +// round down
  1243 +template <class To, class Rep, class Period>
  1244 +CONSTCD14
  1245 +inline
  1246 +typename std::enable_if
  1247 +<
  1248 + detail::no_overflow<Period, typename To::period>::value,
  1249 + To
  1250 +>::type
  1251 +floor(const std::chrono::duration<Rep, Period>& d)
  1252 +{
  1253 + auto t = trunc<To>(d);
  1254 + if (t > d)
  1255 + return t - To{1};
  1256 + return t;
  1257 +}
  1258 +
  1259 +template <class To, class Rep, class Period>
  1260 +CONSTCD14
  1261 +inline
  1262 +typename std::enable_if
  1263 +<
  1264 + !detail::no_overflow<Period, typename To::period>::value,
  1265 + To
  1266 +>::type
  1267 +floor(const std::chrono::duration<Rep, Period>& d)
  1268 +{
  1269 + using rep = typename std::common_type<Rep, typename To::rep>::type;
  1270 + return floor<To>(floor<std::chrono::duration<rep>>(d));
  1271 +}
  1272 +
  1273 +// round to nearest, to even on tie
  1274 +template <class To, class Rep, class Period>
  1275 +CONSTCD14
  1276 +inline
  1277 +To
  1278 +round(const std::chrono::duration<Rep, Period>& d)
  1279 +{
  1280 + auto t0 = floor<To>(d);
  1281 + auto t1 = t0 + To{1};
  1282 + if (t1 == To{0} && t0 < To{0})
  1283 + t1 = -t1;
  1284 + auto diff0 = d - t0;
  1285 + auto diff1 = t1 - d;
  1286 + if (diff0 == diff1)
  1287 + {
  1288 + if (t0 - trunc<To>(t0/2)*2 == To{0})
  1289 + return t0;
  1290 + return t1;
  1291 + }
  1292 + if (diff0 < diff1)
  1293 + return t0;
  1294 + return t1;
  1295 +}
  1296 +
  1297 +// round up
  1298 +template <class To, class Rep, class Period>
  1299 +CONSTCD14
  1300 +inline
  1301 +To
  1302 +ceil(const std::chrono::duration<Rep, Period>& d)
  1303 +{
  1304 + auto t = trunc<To>(d);
  1305 + if (t < d)
  1306 + return t + To{1};
  1307 + return t;
  1308 +}
  1309 +
  1310 +template <class Rep, class Period,
  1311 + class = typename std::enable_if
  1312 + <
  1313 + std::numeric_limits<Rep>::is_signed
  1314 + >::type>
  1315 +CONSTCD11
  1316 +std::chrono::duration<Rep, Period>
  1317 +abs(std::chrono::duration<Rep, Period> d)
  1318 +{
  1319 + return d >= d.zero() ? d : -d;
  1320 +}
  1321 +
  1322 +// round down
  1323 +template <class To, class Clock, class FromDuration>
  1324 +CONSTCD11
  1325 +inline
  1326 +std::chrono::time_point<Clock, To>
  1327 +floor(const std::chrono::time_point<Clock, FromDuration>& tp)
  1328 +{
  1329 + using std::chrono::time_point;
  1330 + return time_point<Clock, To>{date::floor<To>(tp.time_since_epoch())};
  1331 +}
  1332 +
  1333 +// round to nearest, to even on tie
  1334 +template <class To, class Clock, class FromDuration>
  1335 +CONSTCD11
  1336 +inline
  1337 +std::chrono::time_point<Clock, To>
  1338 +round(const std::chrono::time_point<Clock, FromDuration>& tp)
  1339 +{
  1340 + using std::chrono::time_point;
  1341 + return time_point<Clock, To>{round<To>(tp.time_since_epoch())};
  1342 +}
  1343 +
  1344 +// round up
  1345 +template <class To, class Clock, class FromDuration>
  1346 +CONSTCD11
  1347 +inline
  1348 +std::chrono::time_point<Clock, To>
  1349 +ceil(const std::chrono::time_point<Clock, FromDuration>& tp)
  1350 +{
  1351 + using std::chrono::time_point;
  1352 + return time_point<Clock, To>{ceil<To>(tp.time_since_epoch())};
  1353 +}
  1354 +
  1355 +#else // HAS_CHRONO_ROUNDING == 1
  1356 +
  1357 +using std::chrono::floor;
  1358 +using std::chrono::ceil;
  1359 +using std::chrono::round;
  1360 +using std::chrono::abs;
  1361 +
  1362 +#endif // HAS_CHRONO_ROUNDING
  1363 +
  1364 +namespace detail
  1365 +{
  1366 +
  1367 +template <class To, class Rep, class Period>
  1368 +CONSTCD14
  1369 +inline
  1370 +typename std::enable_if
  1371 +<
  1372 + !std::chrono::treat_as_floating_point<typename To::rep>::value,
  1373 + To
  1374 +>::type
  1375 +round_i(const std::chrono::duration<Rep, Period>& d)
  1376 +{
  1377 + return round<To>(d);
  1378 +}
  1379 +
  1380 +template <class To, class Rep, class Period>
  1381 +CONSTCD14
  1382 +inline
  1383 +typename std::enable_if
  1384 +<
  1385 + std::chrono::treat_as_floating_point<typename To::rep>::value,
  1386 + To
  1387 +>::type
  1388 +round_i(const std::chrono::duration<Rep, Period>& d)
  1389 +{
  1390 + return d;
  1391 +}
  1392 +
  1393 +template <class To, class Clock, class FromDuration>
  1394 +CONSTCD11
  1395 +inline
  1396 +std::chrono::time_point<Clock, To>
  1397 +round_i(const std::chrono::time_point<Clock, FromDuration>& tp)
  1398 +{
  1399 + using std::chrono::time_point;
  1400 + return time_point<Clock, To>{round_i<To>(tp.time_since_epoch())};
  1401 +}
  1402 +
  1403 +} // detail
  1404 +
  1405 +// trunc towards zero
  1406 +template <class To, class Clock, class FromDuration>
  1407 +CONSTCD11
  1408 +inline
  1409 +std::chrono::time_point<Clock, To>
  1410 +trunc(const std::chrono::time_point<Clock, FromDuration>& tp)
  1411 +{
  1412 + using std::chrono::time_point;
  1413 + return time_point<Clock, To>{trunc<To>(tp.time_since_epoch())};
  1414 +}
  1415 +
  1416 +// day
  1417 +
  1418 +CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast<decltype(d_)>(d)) {}
  1419 +CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;}
  1420 +CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;}
  1421 +CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;}
  1422 +CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;}
  1423 +CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;}
  1424 +CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;}
  1425 +CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;}
  1426 +CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 31;}
  1427 +
  1428 +CONSTCD11
  1429 +inline
  1430 +bool
  1431 +operator==(const day& x, const day& y) NOEXCEPT
  1432 +{
  1433 + return static_cast<unsigned>(x) == static_cast<unsigned>(y);
  1434 +}
  1435 +
  1436 +CONSTCD11
  1437 +inline
  1438 +bool
  1439 +operator!=(const day& x, const day& y) NOEXCEPT
  1440 +{
  1441 + return !(x == y);
  1442 +}
  1443 +
  1444 +CONSTCD11
  1445 +inline
  1446 +bool
  1447 +operator<(const day& x, const day& y) NOEXCEPT
  1448 +{
  1449 + return static_cast<unsigned>(x) < static_cast<unsigned>(y);
  1450 +}
  1451 +
  1452 +CONSTCD11
  1453 +inline
  1454 +bool
  1455 +operator>(const day& x, const day& y) NOEXCEPT
  1456 +{
  1457 + return y < x;
  1458 +}
  1459 +
  1460 +CONSTCD11
  1461 +inline
  1462 +bool
  1463 +operator<=(const day& x, const day& y) NOEXCEPT
  1464 +{
  1465 + return !(y < x);
  1466 +}
  1467 +
  1468 +CONSTCD11
  1469 +inline
  1470 +bool
  1471 +operator>=(const day& x, const day& y) NOEXCEPT
  1472 +{
  1473 + return !(x < y);
  1474 +}
  1475 +
  1476 +CONSTCD11
  1477 +inline
  1478 +days
  1479 +operator-(const day& x, const day& y) NOEXCEPT
  1480 +{
  1481 + return days{static_cast<days::rep>(static_cast<unsigned>(x)
  1482 + - static_cast<unsigned>(y))};
  1483 +}
  1484 +
  1485 +CONSTCD11
  1486 +inline
  1487 +day
  1488 +operator+(const day& x, const days& y) NOEXCEPT
  1489 +{
  1490 + return day{static_cast<unsigned>(x) + static_cast<unsigned>(y.count())};
  1491 +}
  1492 +
  1493 +CONSTCD11
  1494 +inline
  1495 +day
  1496 +operator+(const days& x, const day& y) NOEXCEPT
  1497 +{
  1498 + return y + x;
  1499 +}
  1500 +
  1501 +CONSTCD11
  1502 +inline
  1503 +day
  1504 +operator-(const day& x, const days& y) NOEXCEPT
  1505 +{
  1506 + return x + -y;
  1507 +}
  1508 +
  1509 +namespace detail
  1510 +{
  1511 +
  1512 +template<class CharT, class Traits>
  1513 +std::basic_ostream<CharT, Traits>&
  1514 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const day& d)
  1515 +{
  1516 + detail::save_ostream<CharT, Traits> _(os);
  1517 + os.fill('0');
  1518 + os.flags(std::ios::dec | std::ios::right);
  1519 + os.width(2);
  1520 + os << static_cast<unsigned>(d);
  1521 + return os;
  1522 +}
  1523 +
  1524 +} // namespace detail
  1525 +
  1526 +template<class CharT, class Traits>
  1527 +inline
  1528 +std::basic_ostream<CharT, Traits>&
  1529 +operator<<(std::basic_ostream<CharT, Traits>& os, const day& d)
  1530 +{
  1531 + detail::low_level_fmt(os, d);
  1532 + if (!d.ok())
  1533 + os << " is not a valid day";
  1534 + return os;
  1535 +}
  1536 +
  1537 +// month
  1538 +
  1539 +CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast<decltype(m_)>(m)) {}
  1540 +CONSTCD14 inline month& month::operator++() NOEXCEPT {*this += months{1}; return *this;}
  1541 +CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;}
  1542 +CONSTCD14 inline month& month::operator--() NOEXCEPT {*this -= months{1}; return *this;}
  1543 +CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;}
  1544 +
  1545 +CONSTCD14
  1546 +inline
  1547 +month&
  1548 +month::operator+=(const months& m) NOEXCEPT
  1549 +{
  1550 + *this = *this + m;
  1551 + return *this;
  1552 +}
  1553 +
  1554 +CONSTCD14
  1555 +inline
  1556 +month&
  1557 +month::operator-=(const months& m) NOEXCEPT
  1558 +{
  1559 + *this = *this - m;
  1560 + return *this;
  1561 +}
  1562 +
  1563 +CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;}
  1564 +CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;}
  1565 +
  1566 +CONSTCD11
  1567 +inline
  1568 +bool
  1569 +operator==(const month& x, const month& y) NOEXCEPT
  1570 +{
  1571 + return static_cast<unsigned>(x) == static_cast<unsigned>(y);
  1572 +}
  1573 +
  1574 +CONSTCD11
  1575 +inline
  1576 +bool
  1577 +operator!=(const month& x, const month& y) NOEXCEPT
  1578 +{
  1579 + return !(x == y);
  1580 +}
  1581 +
  1582 +CONSTCD11
  1583 +inline
  1584 +bool
  1585 +operator<(const month& x, const month& y) NOEXCEPT
  1586 +{
  1587 + return static_cast<unsigned>(x) < static_cast<unsigned>(y);
  1588 +}
  1589 +
  1590 +CONSTCD11
  1591 +inline
  1592 +bool
  1593 +operator>(const month& x, const month& y) NOEXCEPT
  1594 +{
  1595 + return y < x;
  1596 +}
  1597 +
  1598 +CONSTCD11
  1599 +inline
  1600 +bool
  1601 +operator<=(const month& x, const month& y) NOEXCEPT
  1602 +{
  1603 + return !(y < x);
  1604 +}
  1605 +
  1606 +CONSTCD11
  1607 +inline
  1608 +bool
  1609 +operator>=(const month& x, const month& y) NOEXCEPT
  1610 +{
  1611 + return !(x < y);
  1612 +}
  1613 +
  1614 +CONSTCD14
  1615 +inline
  1616 +months
  1617 +operator-(const month& x, const month& y) NOEXCEPT
  1618 +{
  1619 + auto const d = static_cast<unsigned>(x) - static_cast<unsigned>(y);
  1620 + return months(d <= 11 ? d : d + 12);
  1621 +}
  1622 +
  1623 +CONSTCD14
  1624 +inline
  1625 +month
  1626 +operator+(const month& x, const months& y) NOEXCEPT
  1627 +{
  1628 + auto const mu = static_cast<long long>(static_cast<unsigned>(x)) + y.count() - 1;
  1629 + auto const yr = (mu >= 0 ? mu : mu-11) / 12;
  1630 + return month{static_cast<unsigned>(mu - yr * 12 + 1)};
  1631 +}
  1632 +
  1633 +CONSTCD14
  1634 +inline
  1635 +month
  1636 +operator+(const months& x, const month& y) NOEXCEPT
  1637 +{
  1638 + return y + x;
  1639 +}
  1640 +
  1641 +CONSTCD14
  1642 +inline
  1643 +month
  1644 +operator-(const month& x, const months& y) NOEXCEPT
  1645 +{
  1646 + return x + -y;
  1647 +}
  1648 +
  1649 +namespace detail
  1650 +{
  1651 +
  1652 +template<class CharT, class Traits>
  1653 +std::basic_ostream<CharT, Traits>&
  1654 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const month& m)
  1655 +{
  1656 + if (m.ok())
  1657 + {
  1658 + CharT fmt[] = {'%', 'b', 0};
  1659 + os << format(os.getloc(), fmt, m);
  1660 + }
  1661 + else
  1662 + os << static_cast<unsigned>(m);
  1663 + return os;
  1664 +}
  1665 +
  1666 +} // namespace detail
  1667 +
  1668 +template<class CharT, class Traits>
  1669 +inline
  1670 +std::basic_ostream<CharT, Traits>&
  1671 +operator<<(std::basic_ostream<CharT, Traits>& os, const month& m)
  1672 +{
  1673 + detail::low_level_fmt(os, m);
  1674 + if (!m.ok())
  1675 + os << " is not a valid month";
  1676 + return os;
  1677 +}
  1678 +
  1679 +// year
  1680 +
  1681 +CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast<decltype(y_)>(y)) {}
  1682 +CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;}
  1683 +CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;}
  1684 +CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;}
  1685 +CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;}
  1686 +CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;}
  1687 +CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;}
  1688 +CONSTCD11 inline year year::operator-() const NOEXCEPT {return year{-y_};}
  1689 +CONSTCD11 inline year year::operator+() const NOEXCEPT {return *this;}
  1690 +
  1691 +CONSTCD11
  1692 +inline
  1693 +bool
  1694 +year::is_leap() const NOEXCEPT
  1695 +{
  1696 + return y_ % 4 == 0 && (y_ % 100 != 0 || y_ % 400 == 0);
  1697 +}
  1698 +
  1699 +CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;}
  1700 +
  1701 +CONSTCD11
  1702 +inline
  1703 +bool
  1704 +year::ok() const NOEXCEPT
  1705 +{
  1706 + return y_ != std::numeric_limits<short>::min();
  1707 +}
  1708 +
  1709 +CONSTCD11
  1710 +inline
  1711 +bool
  1712 +operator==(const year& x, const year& y) NOEXCEPT
  1713 +{
  1714 + return static_cast<int>(x) == static_cast<int>(y);
  1715 +}
  1716 +
  1717 +CONSTCD11
  1718 +inline
  1719 +bool
  1720 +operator!=(const year& x, const year& y) NOEXCEPT
  1721 +{
  1722 + return !(x == y);
  1723 +}
  1724 +
  1725 +CONSTCD11
  1726 +inline
  1727 +bool
  1728 +operator<(const year& x, const year& y) NOEXCEPT
  1729 +{
  1730 + return static_cast<int>(x) < static_cast<int>(y);
  1731 +}
  1732 +
  1733 +CONSTCD11
  1734 +inline
  1735 +bool
  1736 +operator>(const year& x, const year& y) NOEXCEPT
  1737 +{
  1738 + return y < x;
  1739 +}
  1740 +
  1741 +CONSTCD11
  1742 +inline
  1743 +bool
  1744 +operator<=(const year& x, const year& y) NOEXCEPT
  1745 +{
  1746 + return !(y < x);
  1747 +}
  1748 +
  1749 +CONSTCD11
  1750 +inline
  1751 +bool
  1752 +operator>=(const year& x, const year& y) NOEXCEPT
  1753 +{
  1754 + return !(x < y);
  1755 +}
  1756 +
  1757 +CONSTCD11
  1758 +inline
  1759 +years
  1760 +operator-(const year& x, const year& y) NOEXCEPT
  1761 +{
  1762 + return years{static_cast<int>(x) - static_cast<int>(y)};
  1763 +}
  1764 +
  1765 +CONSTCD11
  1766 +inline
  1767 +year
  1768 +operator+(const year& x, const years& y) NOEXCEPT
  1769 +{
  1770 + return year{static_cast<int>(x) + y.count()};
  1771 +}
  1772 +
  1773 +CONSTCD11
  1774 +inline
  1775 +year
  1776 +operator+(const years& x, const year& y) NOEXCEPT
  1777 +{
  1778 + return y + x;
  1779 +}
  1780 +
  1781 +CONSTCD11
  1782 +inline
  1783 +year
  1784 +operator-(const year& x, const years& y) NOEXCEPT
  1785 +{
  1786 + return year{static_cast<int>(x) - y.count()};
  1787 +}
  1788 +
  1789 +namespace detail
  1790 +{
  1791 +
  1792 +template<class CharT, class Traits>
  1793 +std::basic_ostream<CharT, Traits>&
  1794 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const year& y)
  1795 +{
  1796 + detail::save_ostream<CharT, Traits> _(os);
  1797 + os.fill('0');
  1798 + os.flags(std::ios::dec | std::ios::internal);
  1799 + os.width(4 + (y < year{0}));
  1800 + os.imbue(std::locale::classic());
  1801 + os << static_cast<int>(y);
  1802 + return os;
  1803 +}
  1804 +
  1805 +} // namespace detail
  1806 +
  1807 +template<class CharT, class Traits>
  1808 +inline
  1809 +std::basic_ostream<CharT, Traits>&
  1810 +operator<<(std::basic_ostream<CharT, Traits>& os, const year& y)
  1811 +{
  1812 + detail::low_level_fmt(os, y);
  1813 + if (!y.ok())
  1814 + os << " is not a valid year";
  1815 + return os;
  1816 +}
  1817 +
  1818 +// weekday
  1819 +
  1820 +CONSTCD14
  1821 +inline
  1822 +unsigned char
  1823 +weekday::weekday_from_days(int z) NOEXCEPT
  1824 +{
  1825 + auto u = static_cast<unsigned>(z);
  1826 + return static_cast<unsigned char>(z >= -4 ? (u+4) % 7 : u % 7);
  1827 +}
  1828 +
  1829 +CONSTCD11
  1830 +inline
  1831 +weekday::weekday(unsigned wd) NOEXCEPT
  1832 + : wd_(static_cast<decltype(wd_)>(wd != 7 ? wd : 0))
  1833 + {}
  1834 +
  1835 +CONSTCD14
  1836 +inline
  1837 +weekday::weekday(const sys_days& dp) NOEXCEPT
  1838 + : wd_(weekday_from_days(dp.time_since_epoch().count()))
  1839 + {}
  1840 +
  1841 +CONSTCD14
  1842 +inline
  1843 +weekday::weekday(const local_days& dp) NOEXCEPT
  1844 + : wd_(weekday_from_days(dp.time_since_epoch().count()))
  1845 + {}
  1846 +
  1847 +CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {*this += days{1}; return *this;}
  1848 +CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;}
  1849 +CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {*this -= days{1}; return *this;}
  1850 +CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;}
  1851 +
  1852 +CONSTCD14
  1853 +inline
  1854 +weekday&
  1855 +weekday::operator+=(const days& d) NOEXCEPT
  1856 +{
  1857 + *this = *this + d;
  1858 + return *this;
  1859 +}
  1860 +
  1861 +CONSTCD14
  1862 +inline
  1863 +weekday&
  1864 +weekday::operator-=(const days& d) NOEXCEPT
  1865 +{
  1866 + *this = *this - d;
  1867 + return *this;
  1868 +}
  1869 +
  1870 +CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;}
  1871 +
  1872 +CONSTCD11
  1873 +inline
  1874 +unsigned weekday::c_encoding() const NOEXCEPT
  1875 +{
  1876 + return unsigned{wd_};
  1877 +}
  1878 +
  1879 +CONSTCD11
  1880 +inline
  1881 +unsigned weekday::iso_encoding() const NOEXCEPT
  1882 +{
  1883 + return unsigned{((wd_ == 0u) ? 7u : wd_)};
  1884 +}
  1885 +
  1886 +CONSTCD11
  1887 +inline
  1888 +bool
  1889 +operator==(const weekday& x, const weekday& y) NOEXCEPT
  1890 +{
  1891 + return x.wd_ == y.wd_;
  1892 +}
  1893 +
  1894 +CONSTCD11
  1895 +inline
  1896 +bool
  1897 +operator!=(const weekday& x, const weekday& y) NOEXCEPT
  1898 +{
  1899 + return !(x == y);
  1900 +}
  1901 +
  1902 +CONSTCD14
  1903 +inline
  1904 +days
  1905 +operator-(const weekday& x, const weekday& y) NOEXCEPT
  1906 +{
  1907 + auto const wdu = x.wd_ - y.wd_;
  1908 + auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7;
  1909 + return days{wdu - wk * 7};
  1910 +}
  1911 +
  1912 +CONSTCD14
  1913 +inline
  1914 +weekday
  1915 +operator+(const weekday& x, const days& y) NOEXCEPT
  1916 +{
  1917 + auto const wdu = static_cast<long long>(static_cast<unsigned>(x.wd_)) + y.count();
  1918 + auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7;
  1919 + return weekday{static_cast<unsigned>(wdu - wk * 7)};
  1920 +}
  1921 +
  1922 +CONSTCD14
  1923 +inline
  1924 +weekday
  1925 +operator+(const days& x, const weekday& y) NOEXCEPT
  1926 +{
  1927 + return y + x;
  1928 +}
  1929 +
  1930 +CONSTCD14
  1931 +inline
  1932 +weekday
  1933 +operator-(const weekday& x, const days& y) NOEXCEPT
  1934 +{
  1935 + return x + -y;
  1936 +}
  1937 +
  1938 +namespace detail
  1939 +{
  1940 +
  1941 +template<class CharT, class Traits>
  1942 +std::basic_ostream<CharT, Traits>&
  1943 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const weekday& wd)
  1944 +{
  1945 + if (wd.ok())
  1946 + {
  1947 + CharT fmt[] = {'%', 'a', 0};
  1948 + os << format(fmt, wd);
  1949 + }
  1950 + else
  1951 + os << wd.c_encoding();
  1952 + return os;
  1953 +}
  1954 +
  1955 +} // namespace detail
  1956 +
  1957 +template<class CharT, class Traits>
  1958 +inline
  1959 +std::basic_ostream<CharT, Traits>&
  1960 +operator<<(std::basic_ostream<CharT, Traits>& os, const weekday& wd)
  1961 +{
  1962 + detail::low_level_fmt(os, wd);
  1963 + if (!wd.ok())
  1964 + os << " is not a valid weekday";
  1965 + return os;
  1966 +}
  1967 +
  1968 +#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
  1969 +inline namespace literals
  1970 +{
  1971 +
  1972 +CONSTCD11
  1973 +inline
  1974 +date::day
  1975 +operator "" _d(unsigned long long d) NOEXCEPT
  1976 +{
  1977 + return date::day{static_cast<unsigned>(d)};
  1978 +}
  1979 +
  1980 +CONSTCD11
  1981 +inline
  1982 +date::year
  1983 +operator "" _y(unsigned long long y) NOEXCEPT
  1984 +{
  1985 + return date::year(static_cast<int>(y));
  1986 +}
  1987 +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900)
  1988 +
  1989 +CONSTDATA date::last_spec last{};
  1990 +
  1991 +CONSTDATA date::month jan{1};
  1992 +CONSTDATA date::month feb{2};
  1993 +CONSTDATA date::month mar{3};
  1994 +CONSTDATA date::month apr{4};
  1995 +CONSTDATA date::month may{5};
  1996 +CONSTDATA date::month jun{6};
  1997 +CONSTDATA date::month jul{7};
  1998 +CONSTDATA date::month aug{8};
  1999 +CONSTDATA date::month sep{9};
  2000 +CONSTDATA date::month oct{10};
  2001 +CONSTDATA date::month nov{11};
  2002 +CONSTDATA date::month dec{12};
  2003 +
  2004 +CONSTDATA date::weekday sun{0u};
  2005 +CONSTDATA date::weekday mon{1u};
  2006 +CONSTDATA date::weekday tue{2u};
  2007 +CONSTDATA date::weekday wed{3u};
  2008 +CONSTDATA date::weekday thu{4u};
  2009 +CONSTDATA date::weekday fri{5u};
  2010 +CONSTDATA date::weekday sat{6u};
  2011 +
  2012 +#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
  2013 +} // inline namespace literals
  2014 +#endif
  2015 +
  2016 +CONSTDATA date::month January{1};
  2017 +CONSTDATA date::month February{2};
  2018 +CONSTDATA date::month March{3};
  2019 +CONSTDATA date::month April{4};
  2020 +CONSTDATA date::month May{5};
  2021 +CONSTDATA date::month June{6};
  2022 +CONSTDATA date::month July{7};
  2023 +CONSTDATA date::month August{8};
  2024 +CONSTDATA date::month September{9};
  2025 +CONSTDATA date::month October{10};
  2026 +CONSTDATA date::month November{11};
  2027 +CONSTDATA date::month December{12};
  2028 +
  2029 +CONSTDATA date::weekday Monday{1};
  2030 +CONSTDATA date::weekday Tuesday{2};
  2031 +CONSTDATA date::weekday Wednesday{3};
  2032 +CONSTDATA date::weekday Thursday{4};
  2033 +CONSTDATA date::weekday Friday{5};
  2034 +CONSTDATA date::weekday Saturday{6};
  2035 +CONSTDATA date::weekday Sunday{7};
  2036 +
  2037 +// weekday_indexed
  2038 +
  2039 +CONSTCD11
  2040 +inline
  2041 +weekday
  2042 +weekday_indexed::weekday() const NOEXCEPT
  2043 +{
  2044 + return date::weekday{static_cast<unsigned>(wd_)};
  2045 +}
  2046 +
  2047 +CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;}
  2048 +
  2049 +CONSTCD11
  2050 +inline
  2051 +bool
  2052 +weekday_indexed::ok() const NOEXCEPT
  2053 +{
  2054 + return weekday().ok() && 1 <= index_ && index_ <= 5;
  2055 +}
  2056 +
  2057 +#ifdef __GNUC__
  2058 +# pragma GCC diagnostic push
  2059 +# pragma GCC diagnostic ignored "-Wconversion"
  2060 +#endif // __GNUC__
  2061 +
  2062 +CONSTCD11
  2063 +inline
  2064 +weekday_indexed::weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT
  2065 + : wd_(static_cast<decltype(wd_)>(static_cast<unsigned>(wd.wd_)))
  2066 + , index_(static_cast<decltype(index_)>(index))
  2067 + {}
  2068 +
  2069 +#ifdef __GNUC__
  2070 +# pragma GCC diagnostic pop
  2071 +#endif // __GNUC__
  2072 +
  2073 +namespace detail
  2074 +{
  2075 +
  2076 +template<class CharT, class Traits>
  2077 +std::basic_ostream<CharT, Traits>&
  2078 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const weekday_indexed& wdi)
  2079 +{
  2080 + return low_level_fmt(os, wdi.weekday()) << '[' << wdi.index() << ']';
  2081 +}
  2082 +
  2083 +} // namespace detail
  2084 +
  2085 +template<class CharT, class Traits>
  2086 +inline
  2087 +std::basic_ostream<CharT, Traits>&
  2088 +operator<<(std::basic_ostream<CharT, Traits>& os, const weekday_indexed& wdi)
  2089 +{
  2090 + detail::low_level_fmt(os, wdi);
  2091 + if (!wdi.ok())
  2092 + os << " is not a valid weekday_indexed";
  2093 + return os;
  2094 +}
  2095 +
  2096 +CONSTCD11
  2097 +inline
  2098 +weekday_indexed
  2099 +weekday::operator[](unsigned index) const NOEXCEPT
  2100 +{
  2101 + return {*this, index};
  2102 +}
  2103 +
  2104 +CONSTCD11
  2105 +inline
  2106 +bool
  2107 +operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT
  2108 +{
  2109 + return x.weekday() == y.weekday() && x.index() == y.index();
  2110 +}
  2111 +
  2112 +CONSTCD11
  2113 +inline
  2114 +bool
  2115 +operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT
  2116 +{
  2117 + return !(x == y);
  2118 +}
  2119 +
  2120 +// weekday_last
  2121 +
  2122 +CONSTCD11 inline date::weekday weekday_last::weekday() const NOEXCEPT {return wd_;}
  2123 +CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();}
  2124 +CONSTCD11 inline weekday_last::weekday_last(const date::weekday& wd) NOEXCEPT : wd_(wd) {}
  2125 +
  2126 +CONSTCD11
  2127 +inline
  2128 +bool
  2129 +operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT
  2130 +{
  2131 + return x.weekday() == y.weekday();
  2132 +}
  2133 +
  2134 +CONSTCD11
  2135 +inline
  2136 +bool
  2137 +operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT
  2138 +{
  2139 + return !(x == y);
  2140 +}
  2141 +
  2142 +namespace detail
  2143 +{
  2144 +
  2145 +template<class CharT, class Traits>
  2146 +std::basic_ostream<CharT, Traits>&
  2147 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const weekday_last& wdl)
  2148 +{
  2149 + return low_level_fmt(os, wdl.weekday()) << "[last]";
  2150 +}
  2151 +
  2152 +} // namespace detail
  2153 +
  2154 +template<class CharT, class Traits>
  2155 +inline
  2156 +std::basic_ostream<CharT, Traits>&
  2157 +operator<<(std::basic_ostream<CharT, Traits>& os, const weekday_last& wdl)
  2158 +{
  2159 + detail::low_level_fmt(os, wdl);
  2160 + if (!wdl.ok())
  2161 + os << " is not a valid weekday_last";
  2162 + return os;
  2163 +}
  2164 +
  2165 +CONSTCD11
  2166 +inline
  2167 +weekday_last
  2168 +weekday::operator[](last_spec) const NOEXCEPT
  2169 +{
  2170 + return weekday_last{*this};
  2171 +}
  2172 +
  2173 +// year_month
  2174 +
  2175 +CONSTCD11
  2176 +inline
  2177 +year_month::year_month(const date::year& y, const date::month& m) NOEXCEPT
  2178 + : y_(y)
  2179 + , m_(m)
  2180 + {}
  2181 +
  2182 +CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;}
  2183 +CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;}
  2184 +CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();}
  2185 +
  2186 +template<class>
  2187 +CONSTCD14
  2188 +inline
  2189 +year_month&
  2190 +year_month::operator+=(const months& dm) NOEXCEPT
  2191 +{
  2192 + *this = *this + dm;
  2193 + return *this;
  2194 +}
  2195 +
  2196 +template<class>
  2197 +CONSTCD14
  2198 +inline
  2199 +year_month&
  2200 +year_month::operator-=(const months& dm) NOEXCEPT
  2201 +{
  2202 + *this = *this - dm;
  2203 + return *this;
  2204 +}
  2205 +
  2206 +CONSTCD14
  2207 +inline
  2208 +year_month&
  2209 +year_month::operator+=(const years& dy) NOEXCEPT
  2210 +{
  2211 + *this = *this + dy;
  2212 + return *this;
  2213 +}
  2214 +
  2215 +CONSTCD14
  2216 +inline
  2217 +year_month&
  2218 +year_month::operator-=(const years& dy) NOEXCEPT
  2219 +{
  2220 + *this = *this - dy;
  2221 + return *this;
  2222 +}
  2223 +
  2224 +CONSTCD11
  2225 +inline
  2226 +bool
  2227 +operator==(const year_month& x, const year_month& y) NOEXCEPT
  2228 +{
  2229 + return x.year() == y.year() && x.month() == y.month();
  2230 +}
  2231 +
  2232 +CONSTCD11
  2233 +inline
  2234 +bool
  2235 +operator!=(const year_month& x, const year_month& y) NOEXCEPT
  2236 +{
  2237 + return !(x == y);
  2238 +}
  2239 +
  2240 +CONSTCD11
  2241 +inline
  2242 +bool
  2243 +operator<(const year_month& x, const year_month& y) NOEXCEPT
  2244 +{
  2245 + return x.year() < y.year() ? true
  2246 + : (x.year() > y.year() ? false
  2247 + : (x.month() < y.month()));
  2248 +}
  2249 +
  2250 +CONSTCD11
  2251 +inline
  2252 +bool
  2253 +operator>(const year_month& x, const year_month& y) NOEXCEPT
  2254 +{
  2255 + return y < x;
  2256 +}
  2257 +
  2258 +CONSTCD11
  2259 +inline
  2260 +bool
  2261 +operator<=(const year_month& x, const year_month& y) NOEXCEPT
  2262 +{
  2263 + return !(y < x);
  2264 +}
  2265 +
  2266 +CONSTCD11
  2267 +inline
  2268 +bool
  2269 +operator>=(const year_month& x, const year_month& y) NOEXCEPT
  2270 +{
  2271 + return !(x < y);
  2272 +}
  2273 +
  2274 +template<class>
  2275 +CONSTCD14
  2276 +inline
  2277 +year_month
  2278 +operator+(const year_month& ym, const months& dm) NOEXCEPT
  2279 +{
  2280 + auto dmi = static_cast<int>(static_cast<unsigned>(ym.month())) - 1 + dm.count();
  2281 + auto dy = (dmi >= 0 ? dmi : dmi-11) / 12;
  2282 + dmi = dmi - dy * 12 + 1;
  2283 + return (ym.year() + years(dy)) / month(static_cast<unsigned>(dmi));
  2284 +}
  2285 +
  2286 +template<class>
  2287 +CONSTCD14
  2288 +inline
  2289 +year_month
  2290 +operator+(const months& dm, const year_month& ym) NOEXCEPT
  2291 +{
  2292 + return ym + dm;
  2293 +}
  2294 +
  2295 +template<class>
  2296 +CONSTCD14
  2297 +inline
  2298 +year_month
  2299 +operator-(const year_month& ym, const months& dm) NOEXCEPT
  2300 +{
  2301 + return ym + -dm;
  2302 +}
  2303 +
  2304 +CONSTCD11
  2305 +inline
  2306 +months
  2307 +operator-(const year_month& x, const year_month& y) NOEXCEPT
  2308 +{
  2309 + return (x.year() - y.year()) +
  2310 + months(static_cast<unsigned>(x.month()) - static_cast<unsigned>(y.month()));
  2311 +}
  2312 +
  2313 +CONSTCD11
  2314 +inline
  2315 +year_month
  2316 +operator+(const year_month& ym, const years& dy) NOEXCEPT
  2317 +{
  2318 + return (ym.year() + dy) / ym.month();
  2319 +}
  2320 +
  2321 +CONSTCD11
  2322 +inline
  2323 +year_month
  2324 +operator+(const years& dy, const year_month& ym) NOEXCEPT
  2325 +{
  2326 + return ym + dy;
  2327 +}
  2328 +
  2329 +CONSTCD11
  2330 +inline
  2331 +year_month
  2332 +operator-(const year_month& ym, const years& dy) NOEXCEPT
  2333 +{
  2334 + return ym + -dy;
  2335 +}
  2336 +
  2337 +namespace detail
  2338 +{
  2339 +
  2340 +template<class CharT, class Traits>
  2341 +std::basic_ostream<CharT, Traits>&
  2342 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const year_month& ym)
  2343 +{
  2344 + low_level_fmt(os, ym.year()) << '/';
  2345 + return low_level_fmt(os, ym.month());
  2346 +}
  2347 +
  2348 +} // namespace detail
  2349 +
  2350 +template<class CharT, class Traits>
  2351 +inline
  2352 +std::basic_ostream<CharT, Traits>&
  2353 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month& ym)
  2354 +{
  2355 + detail::low_level_fmt(os, ym);
  2356 + if (!ym.ok())
  2357 + os << " is not a valid year_month";
  2358 + return os;
  2359 +}
  2360 +
  2361 +// month_day
  2362 +
  2363 +CONSTCD11
  2364 +inline
  2365 +month_day::month_day(const date::month& m, const date::day& d) NOEXCEPT
  2366 + : m_(m)
  2367 + , d_(d)
  2368 + {}
  2369 +
  2370 +CONSTCD11 inline date::month month_day::month() const NOEXCEPT {return m_;}
  2371 +CONSTCD11 inline date::day month_day::day() const NOEXCEPT {return d_;}
  2372 +
  2373 +CONSTCD14
  2374 +inline
  2375 +bool
  2376 +month_day::ok() const NOEXCEPT
  2377 +{
  2378 + CONSTDATA date::day d[] =
  2379 + {
  2380 + date::day(31), date::day(29), date::day(31),
  2381 + date::day(30), date::day(31), date::day(30),
  2382 + date::day(31), date::day(31), date::day(30),
  2383 + date::day(31), date::day(30), date::day(31)
  2384 + };
  2385 + return m_.ok() && date::day{1} <= d_ && d_ <= d[static_cast<unsigned>(m_)-1];
  2386 +}
  2387 +
  2388 +CONSTCD11
  2389 +inline
  2390 +bool
  2391 +operator==(const month_day& x, const month_day& y) NOEXCEPT
  2392 +{
  2393 + return x.month() == y.month() && x.day() == y.day();
  2394 +}
  2395 +
  2396 +CONSTCD11
  2397 +inline
  2398 +bool
  2399 +operator!=(const month_day& x, const month_day& y) NOEXCEPT
  2400 +{
  2401 + return !(x == y);
  2402 +}
  2403 +
  2404 +CONSTCD11
  2405 +inline
  2406 +bool
  2407 +operator<(const month_day& x, const month_day& y) NOEXCEPT
  2408 +{
  2409 + return x.month() < y.month() ? true
  2410 + : (x.month() > y.month() ? false
  2411 + : (x.day() < y.day()));
  2412 +}
  2413 +
  2414 +CONSTCD11
  2415 +inline
  2416 +bool
  2417 +operator>(const month_day& x, const month_day& y) NOEXCEPT
  2418 +{
  2419 + return y < x;
  2420 +}
  2421 +
  2422 +CONSTCD11
  2423 +inline
  2424 +bool
  2425 +operator<=(const month_day& x, const month_day& y) NOEXCEPT
  2426 +{
  2427 + return !(y < x);
  2428 +}
  2429 +
  2430 +CONSTCD11
  2431 +inline
  2432 +bool
  2433 +operator>=(const month_day& x, const month_day& y) NOEXCEPT
  2434 +{
  2435 + return !(x < y);
  2436 +}
  2437 +
  2438 +namespace detail
  2439 +{
  2440 +
  2441 +template<class CharT, class Traits>
  2442 +std::basic_ostream<CharT, Traits>&
  2443 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const month_day& md)
  2444 +{
  2445 + low_level_fmt(os, md.month()) << '/';
  2446 + return low_level_fmt(os, md.day());
  2447 +}
  2448 +
  2449 +} // namespace detail
  2450 +
  2451 +template<class CharT, class Traits>
  2452 +inline
  2453 +std::basic_ostream<CharT, Traits>&
  2454 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_day& md)
  2455 +{
  2456 + detail::low_level_fmt(os, md);
  2457 + if (!md.ok())
  2458 + os << " is not a valid month_day";
  2459 + return os;
  2460 +}
  2461 +
  2462 +// month_day_last
  2463 +
  2464 +CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;}
  2465 +CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();}
  2466 +CONSTCD11 inline month_day_last::month_day_last(const date::month& m) NOEXCEPT : m_(m) {}
  2467 +
  2468 +CONSTCD11
  2469 +inline
  2470 +bool
  2471 +operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT
  2472 +{
  2473 + return x.month() == y.month();
  2474 +}
  2475 +
  2476 +CONSTCD11
  2477 +inline
  2478 +bool
  2479 +operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT
  2480 +{
  2481 + return !(x == y);
  2482 +}
  2483 +
  2484 +CONSTCD11
  2485 +inline
  2486 +bool
  2487 +operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT
  2488 +{
  2489 + return x.month() < y.month();
  2490 +}
  2491 +
  2492 +CONSTCD11
  2493 +inline
  2494 +bool
  2495 +operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT
  2496 +{
  2497 + return y < x;
  2498 +}
  2499 +
  2500 +CONSTCD11
  2501 +inline
  2502 +bool
  2503 +operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT
  2504 +{
  2505 + return !(y < x);
  2506 +}
  2507 +
  2508 +CONSTCD11
  2509 +inline
  2510 +bool
  2511 +operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT
  2512 +{
  2513 + return !(x < y);
  2514 +}
  2515 +
  2516 +namespace detail
  2517 +{
  2518 +
  2519 +template<class CharT, class Traits>
  2520 +std::basic_ostream<CharT, Traits>&
  2521 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const month_day_last& mdl)
  2522 +{
  2523 + return low_level_fmt(os, mdl.month()) << "/last";
  2524 +}
  2525 +
  2526 +} // namespace detail
  2527 +
  2528 +template<class CharT, class Traits>
  2529 +inline
  2530 +std::basic_ostream<CharT, Traits>&
  2531 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_day_last& mdl)
  2532 +{
  2533 + detail::low_level_fmt(os, mdl);
  2534 + if (!mdl.ok())
  2535 + os << " is not a valid month_day_last";
  2536 + return os;
  2537 +}
  2538 +
  2539 +// month_weekday
  2540 +
  2541 +CONSTCD11
  2542 +inline
  2543 +month_weekday::month_weekday(const date::month& m,
  2544 + const date::weekday_indexed& wdi) NOEXCEPT
  2545 + : m_(m)
  2546 + , wdi_(wdi)
  2547 + {}
  2548 +
  2549 +CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;}
  2550 +
  2551 +CONSTCD11
  2552 +inline
  2553 +weekday_indexed
  2554 +month_weekday::weekday_indexed() const NOEXCEPT
  2555 +{
  2556 + return wdi_;
  2557 +}
  2558 +
  2559 +CONSTCD11
  2560 +inline
  2561 +bool
  2562 +month_weekday::ok() const NOEXCEPT
  2563 +{
  2564 + return m_.ok() && wdi_.ok();
  2565 +}
  2566 +
  2567 +CONSTCD11
  2568 +inline
  2569 +bool
  2570 +operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT
  2571 +{
  2572 + return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed();
  2573 +}
  2574 +
  2575 +CONSTCD11
  2576 +inline
  2577 +bool
  2578 +operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT
  2579 +{
  2580 + return !(x == y);
  2581 +}
  2582 +
  2583 +namespace detail
  2584 +{
  2585 +
  2586 +template<class CharT, class Traits>
  2587 +std::basic_ostream<CharT, Traits>&
  2588 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const month_weekday& mwd)
  2589 +{
  2590 + low_level_fmt(os, mwd.month()) << '/';
  2591 + return low_level_fmt(os, mwd.weekday_indexed());
  2592 +}
  2593 +
  2594 +} // namespace detail
  2595 +
  2596 +template<class CharT, class Traits>
  2597 +inline
  2598 +std::basic_ostream<CharT, Traits>&
  2599 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_weekday& mwd)
  2600 +{
  2601 + detail::low_level_fmt(os, mwd);
  2602 + if (!mwd.ok())
  2603 + os << " is not a valid month_weekday";
  2604 + return os;
  2605 +}
  2606 +
  2607 +// month_weekday_last
  2608 +
  2609 +CONSTCD11
  2610 +inline
  2611 +month_weekday_last::month_weekday_last(const date::month& m,
  2612 + const date::weekday_last& wdl) NOEXCEPT
  2613 + : m_(m)
  2614 + , wdl_(wdl)
  2615 + {}
  2616 +
  2617 +CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;}
  2618 +
  2619 +CONSTCD11
  2620 +inline
  2621 +weekday_last
  2622 +month_weekday_last::weekday_last() const NOEXCEPT
  2623 +{
  2624 + return wdl_;
  2625 +}
  2626 +
  2627 +CONSTCD11
  2628 +inline
  2629 +bool
  2630 +month_weekday_last::ok() const NOEXCEPT
  2631 +{
  2632 + return m_.ok() && wdl_.ok();
  2633 +}
  2634 +
  2635 +CONSTCD11
  2636 +inline
  2637 +bool
  2638 +operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT
  2639 +{
  2640 + return x.month() == y.month() && x.weekday_last() == y.weekday_last();
  2641 +}
  2642 +
  2643 +CONSTCD11
  2644 +inline
  2645 +bool
  2646 +operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT
  2647 +{
  2648 + return !(x == y);
  2649 +}
  2650 +
  2651 +namespace detail
  2652 +{
  2653 +
  2654 +template<class CharT, class Traits>
  2655 +std::basic_ostream<CharT, Traits>&
  2656 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const month_weekday_last& mwdl)
  2657 +{
  2658 + low_level_fmt(os, mwdl.month()) << '/';
  2659 + return low_level_fmt(os, mwdl.weekday_last());
  2660 +}
  2661 +
  2662 +} // namespace detail
  2663 +
  2664 +template<class CharT, class Traits>
  2665 +inline
  2666 +std::basic_ostream<CharT, Traits>&
  2667 +operator<<(std::basic_ostream<CharT, Traits>& os, const month_weekday_last& mwdl)
  2668 +{
  2669 + detail::low_level_fmt(os, mwdl);
  2670 + if (!mwdl.ok())
  2671 + os << " is not a valid month_weekday_last";
  2672 + return os;
  2673 +}
  2674 +
  2675 +// year_month_day_last
  2676 +
  2677 +CONSTCD11
  2678 +inline
  2679 +year_month_day_last::year_month_day_last(const date::year& y,
  2680 + const date::month_day_last& mdl) NOEXCEPT
  2681 + : y_(y)
  2682 + , mdl_(mdl)
  2683 + {}
  2684 +
  2685 +template<class>
  2686 +CONSTCD14
  2687 +inline
  2688 +year_month_day_last&
  2689 +year_month_day_last::operator+=(const months& m) NOEXCEPT
  2690 +{
  2691 + *this = *this + m;
  2692 + return *this;
  2693 +}
  2694 +
  2695 +template<class>
  2696 +CONSTCD14
  2697 +inline
  2698 +year_month_day_last&
  2699 +year_month_day_last::operator-=(const months& m) NOEXCEPT
  2700 +{
  2701 + *this = *this - m;
  2702 + return *this;
  2703 +}
  2704 +
  2705 +CONSTCD14
  2706 +inline
  2707 +year_month_day_last&
  2708 +year_month_day_last::operator+=(const years& y) NOEXCEPT
  2709 +{
  2710 + *this = *this + y;
  2711 + return *this;
  2712 +}
  2713 +
  2714 +CONSTCD14
  2715 +inline
  2716 +year_month_day_last&
  2717 +year_month_day_last::operator-=(const years& y) NOEXCEPT
  2718 +{
  2719 + *this = *this - y;
  2720 + return *this;
  2721 +}
  2722 +
  2723 +CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;}
  2724 +CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();}
  2725 +
  2726 +CONSTCD11
  2727 +inline
  2728 +month_day_last
  2729 +year_month_day_last::month_day_last() const NOEXCEPT
  2730 +{
  2731 + return mdl_;
  2732 +}
  2733 +
  2734 +CONSTCD14
  2735 +inline
  2736 +day
  2737 +year_month_day_last::day() const NOEXCEPT
  2738 +{
  2739 + CONSTDATA date::day d[] =
  2740 + {
  2741 + date::day(31), date::day(28), date::day(31),
  2742 + date::day(30), date::day(31), date::day(30),
  2743 + date::day(31), date::day(31), date::day(30),
  2744 + date::day(31), date::day(30), date::day(31)
  2745 + };
  2746 + return (month() != February || !y_.is_leap()) && mdl_.ok() ?
  2747 + d[static_cast<unsigned>(month()) - 1] : date::day{29};
  2748 +}
  2749 +
  2750 +CONSTCD14
  2751 +inline
  2752 +year_month_day_last::operator sys_days() const NOEXCEPT
  2753 +{
  2754 + return sys_days(year()/month()/day());
  2755 +}
  2756 +
  2757 +CONSTCD14
  2758 +inline
  2759 +year_month_day_last::operator local_days() const NOEXCEPT
  2760 +{
  2761 + return local_days(year()/month()/day());
  2762 +}
  2763 +
  2764 +CONSTCD11
  2765 +inline
  2766 +bool
  2767 +year_month_day_last::ok() const NOEXCEPT
  2768 +{
  2769 + return y_.ok() && mdl_.ok();
  2770 +}
  2771 +
  2772 +CONSTCD11
  2773 +inline
  2774 +bool
  2775 +operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT
  2776 +{
  2777 + return x.year() == y.year() && x.month_day_last() == y.month_day_last();
  2778 +}
  2779 +
  2780 +CONSTCD11
  2781 +inline
  2782 +bool
  2783 +operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT
  2784 +{
  2785 + return !(x == y);
  2786 +}
  2787 +
  2788 +CONSTCD11
  2789 +inline
  2790 +bool
  2791 +operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT
  2792 +{
  2793 + return x.year() < y.year() ? true
  2794 + : (x.year() > y.year() ? false
  2795 + : (x.month_day_last() < y.month_day_last()));
  2796 +}
  2797 +
  2798 +CONSTCD11
  2799 +inline
  2800 +bool
  2801 +operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT
  2802 +{
  2803 + return y < x;
  2804 +}
  2805 +
  2806 +CONSTCD11
  2807 +inline
  2808 +bool
  2809 +operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT
  2810 +{
  2811 + return !(y < x);
  2812 +}
  2813 +
  2814 +CONSTCD11
  2815 +inline
  2816 +bool
  2817 +operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT
  2818 +{
  2819 + return !(x < y);
  2820 +}
  2821 +
  2822 +namespace detail
  2823 +{
  2824 +
  2825 +template<class CharT, class Traits>
  2826 +std::basic_ostream<CharT, Traits>&
  2827 +low_level_fmt(std::basic_ostream<CharT, Traits>& os, const year_month_day_last& ymdl)
  2828 +{
  2829 + low_level_fmt(os, ymdl.year()) << '/';
  2830 + return low_level_fmt(os, ymdl.month_day_last());
  2831 +}
  2832 +
  2833 +} // namespace detail
  2834 +
  2835 +template<class CharT, class Traits>
  2836 +inline
  2837 +std::basic_ostream<CharT, Traits>&
  2838 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_day_last& ymdl)
  2839 +{
  2840 + detail::low_level_fmt(os, ymdl);
  2841 + if (!ymdl.ok())
  2842 + os << " is not a valid year_month_day_last";
  2843 + return os;
  2844 +}
  2845 +
  2846 +template<class>
  2847 +CONSTCD14
  2848 +inline
  2849 +year_month_day_last
  2850 +operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT
  2851 +{
  2852 + return (ymdl.year() / ymdl.month() + dm) / last;
  2853 +}
  2854 +
  2855 +template<class>
  2856 +CONSTCD14
  2857 +inline
  2858 +year_month_day_last
  2859 +operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT
  2860 +{
  2861 + return ymdl + dm;
  2862 +}
  2863 +
  2864 +template<class>
  2865 +CONSTCD14
  2866 +inline
  2867 +year_month_day_last
  2868 +operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT
  2869 +{
  2870 + return ymdl + (-dm);
  2871 +}
  2872 +
  2873 +CONSTCD11
  2874 +inline
  2875 +year_month_day_last
  2876 +operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT
  2877 +{
  2878 + return {ymdl.year()+dy, ymdl.month_day_last()};
  2879 +}
  2880 +
  2881 +CONSTCD11
  2882 +inline
  2883 +year_month_day_last
  2884 +operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT
  2885 +{
  2886 + return ymdl + dy;
  2887 +}
  2888 +
  2889 +CONSTCD11
  2890 +inline
  2891 +year_month_day_last
  2892 +operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT
  2893 +{
  2894 + return ymdl + (-dy);
  2895 +}
  2896 +
  2897 +// year_month_day
  2898 +
  2899 +CONSTCD11
  2900 +inline
  2901 +year_month_day::year_month_day(const date::year& y, const date::month& m,
  2902 + const date::day& d) NOEXCEPT
  2903 + : y_(y)
  2904 + , m_(m)
  2905 + , d_(d)
  2906 + {}
  2907 +
  2908 +CONSTCD14
  2909 +inline
  2910 +year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT
  2911 + : y_(ymdl.year())
  2912 + , m_(ymdl.month())
  2913 + , d_(ymdl.day())
  2914 + {}
  2915 +
  2916 +CONSTCD14
  2917 +inline
  2918 +year_month_day::year_month_day(sys_days dp) NOEXCEPT
  2919 + : year_month_day(from_days(dp.time_since_epoch()))
  2920 + {}
  2921 +
  2922 +CONSTCD14
  2923 +inline
  2924 +year_month_day::year_month_day(local_days dp) NOEXCEPT
  2925 + : year_month_day(from_days(dp.time_since_epoch()))
  2926 + {}
  2927 +
  2928 +CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;}
  2929 +CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;}
  2930 +CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;}
  2931 +
  2932 +template<class>
  2933 +CONSTCD14
  2934 +inline
  2935 +year_month_day&
  2936 +year_month_day::operator+=(const months& m) NOEXCEPT
  2937 +{
  2938 + *this = *this + m;
  2939 + return *this;
  2940 +}
  2941 +
  2942 +template<class>
  2943 +CONSTCD14
  2944 +inline
  2945 +year_month_day&
  2946 +year_month_day::operator-=(const months& m) NOEXCEPT
  2947 +{
  2948 + *this = *this - m;
  2949 + return *this;
  2950 +}
  2951 +
  2952 +CONSTCD14
  2953 +inline
  2954 +year_month_day&
  2955 +year_month_day::operator+=(const years& y) NOEXCEPT
  2956 +{
  2957 + *this = *this + y;
  2958 + return *this;
  2959 +}
  2960 +
  2961 +CONSTCD14
  2962 +inline
  2963 +year_month_day&
  2964 +year_month_day::operator-=(const years& y) NOEXCEPT
  2965 +{
  2966 + *this = *this - y;
  2967 + return *this;
  2968 +}
  2969 +
  2970 +CONSTCD14
  2971 +inline
  2972 +days
  2973 +year_month_day::to_days() const NOEXCEPT
  2974 +{
  2975 + static_assert(std::numeric_limits<unsigned>::digits >= 18,
  2976 + "This algorithm has not been ported to a 16 bit unsigned integer");
  2977 + static_assert(std::numeric_limits<int>::digits >= 20,
  2978 + "This algorithm has not been ported to a 16 bit signed integer");
  2979 + auto const y = static_cast<int>(y_) - (m_ <= February);
  2980 + auto const m = static_cast<unsigned>(m_);
  2981 + auto const d = static_cast<unsigned>(d_);
  2982 + auto const era = (y >= 0 ? y : y-399) / 400;
  2983 + auto const yoe = static_cast<unsigned>(y - era * 400); // [0, 399]
  2984 + auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365]
  2985 + auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096]
  2986 + return days{era * 146097 + static_cast<int>(doe) - 719468};
  2987 +}
  2988 +
  2989 +CONSTCD14
  2990 +inline
  2991 +year_month_day::operator sys_days() const NOEXCEPT
  2992 +{
  2993 + return sys_days{to_days()};
  2994 +}
  2995 +
  2996 +CONSTCD14
  2997 +inline
  2998 +year_month_day::operator local_days() const NOEXCEPT
  2999 +{
  3000 + return local_days{to_days()};
  3001 +}
  3002 +
  3003 +CONSTCD14
  3004 +inline
  3005 +bool
  3006 +year_month_day::ok() const NOEXCEPT
  3007 +{
  3008 + if (!(y_.ok() && m_.ok()))
  3009 + return false;
  3010 + return date::day{1} <= d_ && d_ <= (y_ / m_ / last).day();
  3011 +}
  3012 +
  3013 +CONSTCD11
  3014 +inline
  3015 +bool
  3016 +operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT
  3017 +{
  3018 + return x.year() == y.year() && x.month() == y.month() && x.day() == y.day();
  3019 +}
  3020 +
  3021 +CONSTCD11
  3022 +inline
  3023 +bool
  3024 +operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT
  3025 +{
  3026 + return !(x == y);
  3027 +}
  3028 +
  3029 +CONSTCD11
  3030 +inline
  3031 +bool
  3032 +operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT
  3033 +{
  3034 + return x.year() < y.year() ? true
  3035 + : (x.year() > y.year() ? false
  3036 + : (x.month() < y.month() ? true
  3037 + : (x.month() > y.month() ? false
  3038 + : (x.day() < y.day()))));
  3039 +}
  3040 +
  3041 +CONSTCD11
  3042 +inline
  3043 +bool
  3044 +operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT
  3045 +{
  3046 + return y < x;
  3047 +}
  3048 +
  3049 +CONSTCD11
  3050 +inline
  3051 +bool
  3052 +operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT
  3053 +{
  3054 + return !(y < x);
  3055 +}
  3056 +
  3057 +CONSTCD11
  3058 +inline
  3059 +bool
  3060 +operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT
  3061 +{
  3062 + return !(x < y);
  3063 +}
  3064 +
  3065 +template<class CharT, class Traits>
  3066 +inline
  3067 +std::basic_ostream<CharT, Traits>&
  3068 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_day& ymd)
  3069 +{
  3070 + detail::save_ostream<CharT, Traits> _(os);
  3071 + os.fill('0');
  3072 + os.flags(std::ios::dec | std::ios::right);
  3073 + os.imbue(std::locale::classic());
  3074 + os << static_cast<int>(ymd.year()) << '-';
  3075 + os.width(2);
  3076 + os << static_cast<unsigned>(ymd.month()) << '-';
  3077 + os.width(2);
  3078 + os << static_cast<unsigned>(ymd.day());
  3079 + if (!ymd.ok())
  3080 + os << " is not a valid year_month_day";
  3081 + return os;
  3082 +}
  3083 +
  3084 +CONSTCD14
  3085 +inline
  3086 +year_month_day
  3087 +year_month_day::from_days(days dp) NOEXCEPT
  3088 +{
  3089 + static_assert(std::numeric_limits<unsigned>::digits >= 18,
  3090 + "This algorithm has not been ported to a 16 bit unsigned integer");
  3091 + static_assert(std::numeric_limits<int>::digits >= 20,
  3092 + "This algorithm has not been ported to a 16 bit signed integer");
  3093 + auto const z = dp.count() + 719468;
  3094 + auto const era = (z >= 0 ? z : z - 146096) / 146097;
  3095 + auto const doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
  3096 + auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399]
  3097 + auto const y = static_cast<days::rep>(yoe) + era * 400;
  3098 + auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365]
  3099 + auto const mp = (5*doy + 2)/153; // [0, 11]
  3100 + auto const d = doy - (153*mp+2)/5 + 1; // [1, 31]
  3101 + auto const m = mp < 10 ? mp+3 : mp-9; // [1, 12]
  3102 + return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)};
  3103 +}
  3104 +
  3105 +template<class>
  3106 +CONSTCD14
  3107 +inline
  3108 +year_month_day
  3109 +operator+(const year_month_day& ymd, const months& dm) NOEXCEPT
  3110 +{
  3111 + return (ymd.year() / ymd.month() + dm) / ymd.day();
  3112 +}
  3113 +
  3114 +template<class>
  3115 +CONSTCD14
  3116 +inline
  3117 +year_month_day
  3118 +operator+(const months& dm, const year_month_day& ymd) NOEXCEPT
  3119 +{
  3120 + return ymd + dm;
  3121 +}
  3122 +
  3123 +template<class>
  3124 +CONSTCD14
  3125 +inline
  3126 +year_month_day
  3127 +operator-(const year_month_day& ymd, const months& dm) NOEXCEPT
  3128 +{
  3129 + return ymd + (-dm);
  3130 +}
  3131 +
  3132 +CONSTCD11
  3133 +inline
  3134 +year_month_day
  3135 +operator+(const year_month_day& ymd, const years& dy) NOEXCEPT
  3136 +{
  3137 + return (ymd.year() + dy) / ymd.month() / ymd.day();
  3138 +}
  3139 +
  3140 +CONSTCD11
  3141 +inline
  3142 +year_month_day
  3143 +operator+(const years& dy, const year_month_day& ymd) NOEXCEPT
  3144 +{
  3145 + return ymd + dy;
  3146 +}
  3147 +
  3148 +CONSTCD11
  3149 +inline
  3150 +year_month_day
  3151 +operator-(const year_month_day& ymd, const years& dy) NOEXCEPT
  3152 +{
  3153 + return ymd + (-dy);
  3154 +}
  3155 +
  3156 +// year_month_weekday
  3157 +
  3158 +CONSTCD11
  3159 +inline
  3160 +year_month_weekday::year_month_weekday(const date::year& y, const date::month& m,
  3161 + const date::weekday_indexed& wdi)
  3162 + NOEXCEPT
  3163 + : y_(y)
  3164 + , m_(m)
  3165 + , wdi_(wdi)
  3166 + {}
  3167 +
  3168 +CONSTCD14
  3169 +inline
  3170 +year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT
  3171 + : year_month_weekday(from_days(dp.time_since_epoch()))
  3172 + {}
  3173 +
  3174 +CONSTCD14
  3175 +inline
  3176 +year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT
  3177 + : year_month_weekday(from_days(dp.time_since_epoch()))
  3178 + {}
  3179 +
  3180 +template<class>
  3181 +CONSTCD14
  3182 +inline
  3183 +year_month_weekday&
  3184 +year_month_weekday::operator+=(const months& m) NOEXCEPT
  3185 +{
  3186 + *this = *this + m;
  3187 + return *this;
  3188 +}
  3189 +
  3190 +template<class>
  3191 +CONSTCD14
  3192 +inline
  3193 +year_month_weekday&
  3194 +year_month_weekday::operator-=(const months& m) NOEXCEPT
  3195 +{
  3196 + *this = *this - m;
  3197 + return *this;
  3198 +}
  3199 +
  3200 +CONSTCD14
  3201 +inline
  3202 +year_month_weekday&
  3203 +year_month_weekday::operator+=(const years& y) NOEXCEPT
  3204 +{
  3205 + *this = *this + y;
  3206 + return *this;
  3207 +}
  3208 +
  3209 +CONSTCD14
  3210 +inline
  3211 +year_month_weekday&
  3212 +year_month_weekday::operator-=(const years& y) NOEXCEPT
  3213 +{
  3214 + *this = *this - y;
  3215 + return *this;
  3216 +}
  3217 +
  3218 +CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;}
  3219 +CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;}
  3220 +
  3221 +CONSTCD11
  3222 +inline
  3223 +weekday
  3224 +year_month_weekday::weekday() const NOEXCEPT
  3225 +{
  3226 + return wdi_.weekday();
  3227 +}
  3228 +
  3229 +CONSTCD11
  3230 +inline
  3231 +unsigned
  3232 +year_month_weekday::index() const NOEXCEPT
  3233 +{
  3234 + return wdi_.index();
  3235 +}
  3236 +
  3237 +CONSTCD11
  3238 +inline
  3239 +weekday_indexed
  3240 +year_month_weekday::weekday_indexed() const NOEXCEPT
  3241 +{
  3242 + return wdi_;
  3243 +}
  3244 +
  3245 +CONSTCD14
  3246 +inline
  3247 +year_month_weekday::operator sys_days() const NOEXCEPT
  3248 +{
  3249 + return sys_days{to_days()};
  3250 +}
  3251 +
  3252 +CONSTCD14
  3253 +inline
  3254 +year_month_weekday::operator local_days() const NOEXCEPT
  3255 +{
  3256 + return local_days{to_days()};
  3257 +}
  3258 +
  3259 +CONSTCD14
  3260 +inline
  3261 +bool
  3262 +year_month_weekday::ok() const NOEXCEPT
  3263 +{
  3264 + if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1)
  3265 + return false;
  3266 + if (wdi_.index() <= 4)
  3267 + return true;
  3268 + auto d2 = wdi_.weekday() - date::weekday(static_cast<sys_days>(y_/m_/1)) +
  3269 + days((wdi_.index()-1)*7 + 1);
  3270 + return static_cast<unsigned>(d2.count()) <= static_cast<unsigned>((y_/m_/last).day());
  3271 +}
  3272 +
  3273 +CONSTCD14
  3274 +inline
  3275 +year_month_weekday
  3276 +year_month_weekday::from_days(days d) NOEXCEPT
  3277 +{
  3278 + sys_days dp{d};
  3279 + auto const wd = date::weekday(dp);
  3280 + auto const ymd = year_month_day(dp);
  3281 + return {ymd.year(), ymd.month(), wd[(static_cast<unsigned>(ymd.day())-1)/7+1]};
  3282 +}
  3283 +
  3284 +CONSTCD14
  3285 +inline
  3286 +days
  3287 +year_month_weekday::to_days() const NOEXCEPT
  3288 +{
  3289 + auto d = sys_days(y_/m_/1);
  3290 + return (d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7})
  3291 + ).time_since_epoch();
  3292 +}
  3293 +
  3294 +CONSTCD11
  3295 +inline
  3296 +bool
  3297 +operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT
  3298 +{
  3299 + return x.year() == y.year() && x.month() == y.month() &&
  3300 + x.weekday_indexed() == y.weekday_indexed();
  3301 +}
  3302 +
  3303 +CONSTCD11
  3304 +inline
  3305 +bool
  3306 +operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT
  3307 +{
  3308 + return !(x == y);
  3309 +}
  3310 +
  3311 +template<class CharT, class Traits>
  3312 +inline
  3313 +std::basic_ostream<CharT, Traits>&
  3314 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_weekday& ymwdi)
  3315 +{
  3316 + detail::low_level_fmt(os, ymwdi.year()) << '/';
  3317 + detail::low_level_fmt(os, ymwdi.month()) << '/';
  3318 + detail::low_level_fmt(os, ymwdi.weekday_indexed());
  3319 + if (!ymwdi.ok())
  3320 + os << " is not a valid year_month_weekday";
  3321 + return os;
  3322 +}
  3323 +
  3324 +template<class>
  3325 +CONSTCD14
  3326 +inline
  3327 +year_month_weekday
  3328 +operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT
  3329 +{
  3330 + return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed();
  3331 +}
  3332 +
  3333 +template<class>
  3334 +CONSTCD14
  3335 +inline
  3336 +year_month_weekday
  3337 +operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT
  3338 +{
  3339 + return ymwd + dm;
  3340 +}
  3341 +
  3342 +template<class>
  3343 +CONSTCD14
  3344 +inline
  3345 +year_month_weekday
  3346 +operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT
  3347 +{
  3348 + return ymwd + (-dm);
  3349 +}
  3350 +
  3351 +CONSTCD11
  3352 +inline
  3353 +year_month_weekday
  3354 +operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT
  3355 +{
  3356 + return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()};
  3357 +}
  3358 +
  3359 +CONSTCD11
  3360 +inline
  3361 +year_month_weekday
  3362 +operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT
  3363 +{
  3364 + return ymwd + dy;
  3365 +}
  3366 +
  3367 +CONSTCD11
  3368 +inline
  3369 +year_month_weekday
  3370 +operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT
  3371 +{
  3372 + return ymwd + (-dy);
  3373 +}
  3374 +
  3375 +// year_month_weekday_last
  3376 +
  3377 +CONSTCD11
  3378 +inline
  3379 +year_month_weekday_last::year_month_weekday_last(const date::year& y,
  3380 + const date::month& m,
  3381 + const date::weekday_last& wdl) NOEXCEPT
  3382 + : y_(y)
  3383 + , m_(m)
  3384 + , wdl_(wdl)
  3385 + {}
  3386 +
  3387 +template<class>
  3388 +CONSTCD14
  3389 +inline
  3390 +year_month_weekday_last&
  3391 +year_month_weekday_last::operator+=(const months& m) NOEXCEPT
  3392 +{
  3393 + *this = *this + m;
  3394 + return *this;
  3395 +}
  3396 +
  3397 +template<class>
  3398 +CONSTCD14
  3399 +inline
  3400 +year_month_weekday_last&
  3401 +year_month_weekday_last::operator-=(const months& m) NOEXCEPT
  3402 +{
  3403 + *this = *this - m;
  3404 + return *this;
  3405 +}
  3406 +
  3407 +CONSTCD14
  3408 +inline
  3409 +year_month_weekday_last&
  3410 +year_month_weekday_last::operator+=(const years& y) NOEXCEPT
  3411 +{
  3412 + *this = *this + y;
  3413 + return *this;
  3414 +}
  3415 +
  3416 +CONSTCD14
  3417 +inline
  3418 +year_month_weekday_last&
  3419 +year_month_weekday_last::operator-=(const years& y) NOEXCEPT
  3420 +{
  3421 + *this = *this - y;
  3422 + return *this;
  3423 +}
  3424 +
  3425 +CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;}
  3426 +CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;}
  3427 +
  3428 +CONSTCD11
  3429 +inline
  3430 +weekday
  3431 +year_month_weekday_last::weekday() const NOEXCEPT
  3432 +{
  3433 + return wdl_.weekday();
  3434 +}
  3435 +
  3436 +CONSTCD11
  3437 +inline
  3438 +weekday_last
  3439 +year_month_weekday_last::weekday_last() const NOEXCEPT
  3440 +{
  3441 + return wdl_;
  3442 +}
  3443 +
  3444 +CONSTCD14
  3445 +inline
  3446 +year_month_weekday_last::operator sys_days() const NOEXCEPT
  3447 +{
  3448 + return sys_days{to_days()};
  3449 +}
  3450 +
  3451 +CONSTCD14
  3452 +inline
  3453 +year_month_weekday_last::operator local_days() const NOEXCEPT
  3454 +{
  3455 + return local_days{to_days()};
  3456 +}
  3457 +
  3458 +CONSTCD11
  3459 +inline
  3460 +bool
  3461 +year_month_weekday_last::ok() const NOEXCEPT
  3462 +{
  3463 + return y_.ok() && m_.ok() && wdl_.ok();
  3464 +}
  3465 +
  3466 +CONSTCD14
  3467 +inline
  3468 +days
  3469 +year_month_weekday_last::to_days() const NOEXCEPT
  3470 +{
  3471 + auto const d = sys_days(y_/m_/last);
  3472 + return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch();
  3473 +}
  3474 +
  3475 +CONSTCD11
  3476 +inline
  3477 +bool
  3478 +operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT
  3479 +{
  3480 + return x.year() == y.year() && x.month() == y.month() &&
  3481 + x.weekday_last() == y.weekday_last();
  3482 +}
  3483 +
  3484 +CONSTCD11
  3485 +inline
  3486 +bool
  3487 +operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT
  3488 +{
  3489 + return !(x == y);
  3490 +}
  3491 +
  3492 +template<class CharT, class Traits>
  3493 +inline
  3494 +std::basic_ostream<CharT, Traits>&
  3495 +operator<<(std::basic_ostream<CharT, Traits>& os, const year_month_weekday_last& ymwdl)
  3496 +{
  3497 + detail::low_level_fmt(os, ymwdl.year()) << '/';
  3498 + detail::low_level_fmt(os, ymwdl.month()) << '/';
  3499 + detail::low_level_fmt(os, ymwdl.weekday_last());
  3500 + if (!ymwdl.ok())
  3501 + os << " is not a valid year_month_weekday_last";
  3502 + return os;
  3503 +}
  3504 +
  3505 +template<class>
  3506 +CONSTCD14
  3507 +inline
  3508 +year_month_weekday_last
  3509 +operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT
  3510 +{
  3511 + return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last();
  3512 +}
  3513 +
  3514 +template<class>
  3515 +CONSTCD14
  3516 +inline
  3517 +year_month_weekday_last
  3518 +operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT
  3519 +{
  3520 + return ymwdl + dm;
  3521 +}
  3522 +
  3523 +template<class>
  3524 +CONSTCD14
  3525 +inline
  3526 +year_month_weekday_last
  3527 +operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT
  3528 +{
  3529 + return ymwdl + (-dm);
  3530 +}
  3531 +
  3532 +CONSTCD11
  3533 +inline
  3534 +year_month_weekday_last
  3535 +operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT
  3536 +{
  3537 + return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()};
  3538 +}
  3539 +
  3540 +CONSTCD11
  3541 +inline
  3542 +year_month_weekday_last
  3543 +operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT
  3544 +{
  3545 + return ymwdl + dy;
  3546 +}
  3547 +
  3548 +CONSTCD11
  3549 +inline
  3550 +year_month_weekday_last
  3551 +operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT
  3552 +{
  3553 + return ymwdl + (-dy);
  3554 +}
  3555 +
  3556 +// year_month from operator/()
  3557 +
  3558 +CONSTCD11
  3559 +inline
  3560 +year_month
  3561 +operator/(const year& y, const month& m) NOEXCEPT
  3562 +{
  3563 + return {y, m};
  3564 +}
  3565 +
  3566 +CONSTCD11
  3567 +inline
  3568 +year_month
  3569 +operator/(const year& y, int m) NOEXCEPT
  3570 +{
  3571 + return y / month(static_cast<unsigned>(m));
  3572 +}
  3573 +
  3574 +// month_day from operator/()
  3575 +
  3576 +CONSTCD11
  3577 +inline
  3578 +month_day
  3579 +operator/(const month& m, const day& d) NOEXCEPT
  3580 +{
  3581 + return {m, d};
  3582 +}
  3583 +
  3584 +CONSTCD11
  3585 +inline
  3586 +month_day
  3587 +operator/(const day& d, const month& m) NOEXCEPT
  3588 +{
  3589 + return m / d;
  3590 +}
  3591 +
  3592 +CONSTCD11
  3593 +inline
  3594 +month_day
  3595 +operator/(const month& m, int d) NOEXCEPT
  3596 +{
  3597 + return m / day(static_cast<unsigned>(d));
  3598 +}
  3599 +
  3600 +CONSTCD11
  3601 +inline
  3602 +month_day
  3603 +operator/(int m, const day& d) NOEXCEPT
  3604 +{
  3605 + return month(static_cast<unsigned>(m)) / d;
  3606 +}
  3607 +
  3608 +CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;}
  3609 +
  3610 +// month_day_last from operator/()
  3611 +
  3612 +CONSTCD11
  3613 +inline
  3614 +month_day_last
  3615 +operator/(const month& m, last_spec) NOEXCEPT
  3616 +{
  3617 + return month_day_last{m};
  3618 +}
  3619 +
  3620 +CONSTCD11
  3621 +inline
  3622 +month_day_last
  3623 +operator/(last_spec, const month& m) NOEXCEPT
  3624 +{
  3625 + return m/last;
  3626 +}
  3627 +
  3628 +CONSTCD11
  3629 +inline
  3630 +month_day_last
  3631 +operator/(int m, last_spec) NOEXCEPT
  3632 +{
  3633 + return month(static_cast<unsigned>(m))/last;
  3634 +}
  3635 +
  3636 +CONSTCD11
  3637 +inline
  3638 +month_day_last
  3639 +operator/(last_spec, int m) NOEXCEPT
  3640 +{
  3641 + return m/last;
  3642 +}
  3643 +
  3644 +// month_weekday from operator/()
  3645 +
  3646 +CONSTCD11
  3647 +inline
  3648 +month_weekday
  3649 +operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT
  3650 +{
  3651 + return {m, wdi};
  3652 +}
  3653 +
  3654 +CONSTCD11
  3655 +inline
  3656 +month_weekday
  3657 +operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT
  3658 +{
  3659 + return m / wdi;
  3660 +}
  3661 +
  3662 +CONSTCD11
  3663 +inline
  3664 +month_weekday
  3665 +operator/(int m, const weekday_indexed& wdi) NOEXCEPT
  3666 +{
  3667 + return month(static_cast<unsigned>(m)) / wdi;
  3668 +}
  3669 +
  3670 +CONSTCD11
  3671 +inline
  3672 +month_weekday
  3673 +operator/(const weekday_indexed& wdi, int m) NOEXCEPT
  3674 +{
  3675 + return m / wdi;
  3676 +}
  3677 +
  3678 +// month_weekday_last from operator/()
  3679 +
  3680 +CONSTCD11
  3681 +inline
  3682 +month_weekday_last
  3683 +operator/(const month& m, const weekday_last& wdl) NOEXCEPT
  3684 +{
  3685 + return {m, wdl};
  3686 +}
  3687 +
  3688 +CONSTCD11
  3689 +inline
  3690 +month_weekday_last
  3691 +operator/(const weekday_last& wdl, const month& m) NOEXCEPT
  3692 +{
  3693 + return m / wdl;
  3694 +}
  3695 +
  3696 +CONSTCD11
  3697 +inline
  3698 +month_weekday_last
  3699 +operator/(int m, const weekday_last& wdl) NOEXCEPT
  3700 +{
  3701 + return month(static_cast<unsigned>(m)) / wdl;
  3702 +}
  3703 +
  3704 +CONSTCD11
  3705 +inline
  3706 +month_weekday_last
  3707 +operator/(const weekday_last& wdl, int m) NOEXCEPT
  3708 +{
  3709 + return m / wdl;
  3710 +}
  3711 +
  3712 +// year_month_day from operator/()
  3713 +
  3714 +CONSTCD11
  3715 +inline
  3716 +year_month_day
  3717 +operator/(const year_month& ym, const day& d) NOEXCEPT
  3718 +{
  3719 + return {ym.year(), ym.month(), d};
  3720 +}
  3721 +
  3722 +CONSTCD11
  3723 +inline
  3724 +year_month_day
  3725 +operator/(const year_month& ym, int d) NOEXCEPT
  3726 +{
  3727 + return ym / day(static_cast<unsigned>(d));
  3728 +}
  3729 +
  3730 +CONSTCD11
  3731 +inline
  3732 +year_month_day
  3733 +operator/(const year& y, const month_day& md) NOEXCEPT
  3734 +{
  3735 + return y / md.month() / md.day();
  3736 +}
  3737 +
  3738 +CONSTCD11
  3739 +inline
  3740 +year_month_day
  3741 +operator/(int y, const month_day& md) NOEXCEPT
  3742 +{
  3743 + return year(y) / md;
  3744 +}
  3745 +
  3746 +CONSTCD11
  3747 +inline
  3748 +year_month_day
  3749 +operator/(const month_day& md, const year& y) NOEXCEPT
  3750 +{
  3751 + return y / md;
  3752 +}
  3753 +
  3754 +CONSTCD11
  3755 +inline
  3756 +year_month_day
  3757 +operator/(const month_day& md, int y) NOEXCEPT
  3758 +{
  3759 + return year(y) / md;
  3760 +}
  3761 +
  3762 +// year_month_day_last from operator/()
  3763 +
  3764 +CONSTCD11
  3765 +inline
  3766 +year_month_day_last
  3767 +operator/(const year_month& ym, last_spec) NOEXCEPT
  3768 +{
  3769 + return {ym.year(), month_day_last{ym.month()}};
  3770 +}
  3771 +
  3772 +CONSTCD11
  3773 +inline
  3774 +year_month_day_last
  3775 +operator/(const year& y, const month_day_last& mdl) NOEXCEPT
  3776 +{
  3777 + return {y, mdl};
  3778 +}
  3779 +
  3780 +CONSTCD11
  3781 +inline
  3782 +year_month_day_last
  3783 +operator/(int y, const month_day_last& mdl) NOEXCEPT
  3784 +{
  3785 + return year(y) / mdl;
  3786 +}
  3787 +
  3788 +CONSTCD11
  3789 +inline
  3790 +year_month_day_last
  3791 +operator/(const month_day_last& mdl, const year& y) NOEXCEPT
  3792 +{
  3793 + return y / mdl;
  3794 +}
  3795 +
  3796 +CONSTCD11
  3797 +inline
  3798 +year_month_day_last
  3799 +operator/(const month_day_last& mdl, int y) NOEXCEPT
  3800 +{
  3801 + return year(y) / mdl;
  3802 +}
  3803 +
  3804 +// year_month_weekday from operator/()
  3805 +
  3806 +CONSTCD11
  3807 +inline
  3808 +year_month_weekday
  3809 +operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT
  3810 +{
  3811 + return {ym.year(), ym.month(), wdi};
  3812 +}
  3813 +
  3814 +CONSTCD11
  3815 +inline
  3816 +year_month_weekday
  3817 +operator/(const year& y, const month_weekday& mwd) NOEXCEPT
  3818 +{
  3819 + return {y, mwd.month(), mwd.weekday_indexed()};
  3820 +}
  3821 +
  3822 +CONSTCD11
  3823 +inline
  3824 +year_month_weekday
  3825 +operator/(int y, const month_weekday& mwd) NOEXCEPT
  3826 +{
  3827 + return year(y) / mwd;
  3828 +}
  3829 +
  3830 +CONSTCD11
  3831 +inline
  3832 +year_month_weekday
  3833 +operator/(const month_weekday& mwd, const year& y) NOEXCEPT
  3834 +{
  3835 + return y / mwd;
  3836 +}
  3837 +
  3838 +CONSTCD11
  3839 +inline
  3840 +year_month_weekday
  3841 +operator/(const month_weekday& mwd, int y) NOEXCEPT
  3842 +{
  3843 + return year(y) / mwd;
  3844 +}
  3845 +
  3846 +// year_month_weekday_last from operator/()
  3847 +
  3848 +CONSTCD11
  3849 +inline
  3850 +year_month_weekday_last
  3851 +operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT
  3852 +{
  3853 + return {ym.year(), ym.month(), wdl};
  3854 +}
  3855 +
  3856 +CONSTCD11
  3857 +inline
  3858 +year_month_weekday_last
  3859 +operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT
  3860 +{
  3861 + return {y, mwdl.month(), mwdl.weekday_last()};
  3862 +}
  3863 +
  3864 +CONSTCD11
  3865 +inline
  3866 +year_month_weekday_last
  3867 +operator/(int y, const month_weekday_last& mwdl) NOEXCEPT
  3868 +{
  3869 + return year(y) / mwdl;
  3870 +}
  3871 +
  3872 +CONSTCD11
  3873 +inline
  3874 +year_month_weekday_last
  3875 +operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT
  3876 +{
  3877 + return y / mwdl;
  3878 +}
  3879 +
  3880 +CONSTCD11
  3881 +inline
  3882 +year_month_weekday_last
  3883 +operator/(const month_weekday_last& mwdl, int y) NOEXCEPT
  3884 +{
  3885 + return year(y) / mwdl;
  3886 +}
  3887 +
  3888 +template <class Duration>
  3889 +struct fields;
  3890 +
  3891 +template <class CharT, class Traits, class Duration>
  3892 +std::basic_ostream<CharT, Traits>&
  3893 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
  3894 + const fields<Duration>& fds, const std::string* abbrev = nullptr,
  3895 + const std::chrono::seconds* offset_sec = nullptr);
  3896 +
  3897 +template <class CharT, class Traits, class Duration, class Alloc>
  3898 +std::basic_istream<CharT, Traits>&
  3899 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,
  3900 + fields<Duration>& fds, std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  3901 + std::chrono::minutes* offset = nullptr);
  3902 +
  3903 +// hh_mm_ss
  3904 +
  3905 +namespace detail
  3906 +{
  3907 +
  3908 +struct undocumented {explicit undocumented() = default;};
  3909 +
  3910 +// width<n>::value is the number of fractional decimal digits in 1/n
  3911 +// width<0>::value and width<1>::value are defined to be 0
  3912 +// If 1/n takes more than 18 fractional decimal digits,
  3913 +// the result is truncated to 19.
  3914 +// Example: width<2>::value == 1
  3915 +// Example: width<3>::value == 19
  3916 +// Example: width<4>::value == 2
  3917 +// Example: width<10>::value == 1
  3918 +// Example: width<1000>::value == 3
  3919 +template <std::uint64_t n, std::uint64_t d, unsigned w = 0,
  3920 + bool should_continue = n%d != 0 && (w < 19)>
  3921 +struct width
  3922 +{
  3923 + static_assert(d > 0, "width called with zero denominator");
  3924 + static CONSTDATA unsigned value = 1 + width<n%d*10, d, w+1>::value;
  3925 +};
  3926 +
  3927 +template <std::uint64_t n, std::uint64_t d, unsigned w>
  3928 +struct width<n, d, w, false>
  3929 +{
  3930 + static CONSTDATA unsigned value = 0;
  3931 +};
  3932 +
  3933 +template <unsigned exp>
  3934 +struct static_pow10
  3935 +{
  3936 +private:
  3937 + static CONSTDATA std::uint64_t h = static_pow10<exp/2>::value;
  3938 +public:
  3939 + static CONSTDATA std::uint64_t value = h * h * (exp % 2 ? 10 : 1);
  3940 +};
  3941 +
  3942 +template <>
  3943 +struct static_pow10<0>
  3944 +{
  3945 + static CONSTDATA std::uint64_t value = 1;
  3946 +};
  3947 +
  3948 +template <class Duration>
  3949 +class decimal_format_seconds
  3950 +{
  3951 + using CT = typename std::common_type<Duration, std::chrono::seconds>::type;
  3952 + using rep = typename CT::rep;
  3953 + static unsigned CONSTDATA trial_width =
  3954 + detail::width<CT::period::num, CT::period::den>::value;
  3955 +public:
  3956 + static unsigned CONSTDATA width = trial_width < 19 ? trial_width : 6u;
  3957 + using precision = std::chrono::duration<rep,
  3958 + std::ratio<1, static_pow10<width>::value>>;
  3959 +
  3960 +private:
  3961 + std::chrono::seconds s_;
  3962 + precision sub_s_;
  3963 +
  3964 +public:
  3965 + CONSTCD11 decimal_format_seconds()
  3966 + : s_()
  3967 + , sub_s_()
  3968 + {}
  3969 +
  3970 + CONSTCD11 explicit decimal_format_seconds(const Duration& d) NOEXCEPT
  3971 + : s_(std::chrono::duration_cast<std::chrono::seconds>(d))
  3972 + , sub_s_(std::chrono::duration_cast<precision>(d - s_))
  3973 + {}
  3974 +
  3975 + CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;}
  3976 + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;}
  3977 + CONSTCD11 precision subseconds() const NOEXCEPT {return sub_s_;}
  3978 +
  3979 + CONSTCD14 precision to_duration() const NOEXCEPT
  3980 + {
  3981 + return s_ + sub_s_;
  3982 + }
  3983 +
  3984 + CONSTCD11 bool in_conventional_range() const NOEXCEPT
  3985 + {
  3986 + return sub_s_ < std::chrono::seconds{1} && s_ < std::chrono::minutes{1};
  3987 + }
  3988 +
  3989 + template <class CharT, class Traits>
  3990 + friend
  3991 + std::basic_ostream<CharT, Traits>&
  3992 + operator<<(std::basic_ostream<CharT, Traits>& os, const decimal_format_seconds& x)
  3993 + {
  3994 + return x.print(os, std::chrono::treat_as_floating_point<rep>{});
  3995 + }
  3996 +
  3997 + template <class CharT, class Traits>
  3998 + std::basic_ostream<CharT, Traits>&
  3999 + print(std::basic_ostream<CharT, Traits>& os, std::true_type) const
  4000 + {
  4001 + date::detail::save_ostream<CharT, Traits> _(os);
  4002 + std::chrono::duration<rep> d = s_ + sub_s_;
  4003 + if (d < std::chrono::seconds{10})
  4004 + os << '0';
  4005 + os.precision(width+6);
  4006 + os << std::fixed << d.count();
  4007 + return os;
  4008 + }
  4009 +
  4010 + template <class CharT, class Traits>
  4011 + std::basic_ostream<CharT, Traits>&
  4012 + print(std::basic_ostream<CharT, Traits>& os, std::false_type) const
  4013 + {
  4014 + date::detail::save_ostream<CharT, Traits> _(os);
  4015 + os.fill('0');
  4016 + os.flags(std::ios::dec | std::ios::right);
  4017 + os.width(2);
  4018 + os << s_.count();
  4019 + if (width > 0)
  4020 + {
  4021 +#if !ONLY_C_LOCALE
  4022 + os << std::use_facet<std::numpunct<CharT>>(os.getloc()).decimal_point();
  4023 +#else
  4024 + os << '.';
  4025 +#endif
  4026 + date::detail::save_ostream<CharT, Traits> _s(os);
  4027 + os.imbue(std::locale::classic());
  4028 + os.width(width);
  4029 + os << sub_s_.count();
  4030 + }
  4031 + return os;
  4032 + }
  4033 +};
  4034 +
  4035 +template <class Rep, class Period>
  4036 +inline
  4037 +CONSTCD11
  4038 +typename std::enable_if
  4039 + <
  4040 + std::numeric_limits<Rep>::is_signed,
  4041 + std::chrono::duration<Rep, Period>
  4042 + >::type
  4043 +abs(std::chrono::duration<Rep, Period> d)
  4044 +{
  4045 + return d >= d.zero() ? +d : -d;
  4046 +}
  4047 +
  4048 +template <class Rep, class Period>
  4049 +inline
  4050 +CONSTCD11
  4051 +typename std::enable_if
  4052 + <
  4053 + !std::numeric_limits<Rep>::is_signed,
  4054 + std::chrono::duration<Rep, Period>
  4055 + >::type
  4056 +abs(std::chrono::duration<Rep, Period> d)
  4057 +{
  4058 + return d;
  4059 +}
  4060 +
  4061 +} // namespace detail
  4062 +
  4063 +template <class Duration>
  4064 +class hh_mm_ss
  4065 +{
  4066 + using dfs = detail::decimal_format_seconds<typename std::common_type<Duration,
  4067 + std::chrono::seconds>::type>;
  4068 +
  4069 + std::chrono::hours h_;
  4070 + std::chrono::minutes m_;
  4071 + dfs s_;
  4072 + bool neg_;
  4073 +
  4074 +public:
  4075 + static unsigned CONSTDATA fractional_width = dfs::width;
  4076 + using precision = typename dfs::precision;
  4077 +
  4078 + CONSTCD11 hh_mm_ss() NOEXCEPT
  4079 + : hh_mm_ss(Duration::zero())
  4080 + {}
  4081 +
  4082 + CONSTCD11 explicit hh_mm_ss(Duration d) NOEXCEPT
  4083 + : h_(std::chrono::duration_cast<std::chrono::hours>(detail::abs(d)))
  4084 + , m_(std::chrono::duration_cast<std::chrono::minutes>(detail::abs(d)) - h_)
  4085 + , s_(detail::abs(d) - h_ - m_)
  4086 + , neg_(d < Duration::zero())
  4087 + {}
  4088 +
  4089 + CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;}
  4090 + CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;}
  4091 + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_.seconds();}
  4092 + CONSTCD14 std::chrono::seconds&
  4093 + seconds(detail::undocumented) NOEXCEPT {return s_.seconds();}
  4094 + CONSTCD11 precision subseconds() const NOEXCEPT {return s_.subseconds();}
  4095 + CONSTCD11 bool is_negative() const NOEXCEPT {return neg_;}
  4096 +
  4097 + CONSTCD11 explicit operator precision() const NOEXCEPT {return to_duration();}
  4098 + CONSTCD11 precision to_duration() const NOEXCEPT
  4099 + {return (s_.to_duration() + m_ + h_) * (1-2*neg_);}
  4100 +
  4101 + CONSTCD11 bool in_conventional_range() const NOEXCEPT
  4102 + {
  4103 + return !neg_ && h_ < days{1} && m_ < std::chrono::hours{1} &&
  4104 + s_.in_conventional_range();
  4105 + }
  4106 +
  4107 +private:
  4108 +
  4109 + template <class charT, class traits>
  4110 + friend
  4111 + std::basic_ostream<charT, traits>&
  4112 + operator<<(std::basic_ostream<charT, traits>& os, hh_mm_ss const& tod)
  4113 + {
  4114 + if (tod.is_negative())
  4115 + os << '-';
  4116 + if (tod.h_ < std::chrono::hours{10})
  4117 + os << '0';
  4118 + os << tod.h_.count() << ':';
  4119 + if (tod.m_ < std::chrono::minutes{10})
  4120 + os << '0';
  4121 + os << tod.m_.count() << ':' << tod.s_;
  4122 + return os;
  4123 + }
  4124 +
  4125 + template <class CharT, class Traits, class Duration2>
  4126 + friend
  4127 + std::basic_ostream<CharT, Traits>&
  4128 + date::to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
  4129 + const fields<Duration2>& fds, const std::string* abbrev,
  4130 + const std::chrono::seconds* offset_sec);
  4131 +
  4132 + template <class CharT, class Traits, class Duration2, class Alloc>
  4133 + friend
  4134 + std::basic_istream<CharT, Traits>&
  4135 + date::from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,
  4136 + fields<Duration2>& fds,
  4137 + std::basic_string<CharT, Traits, Alloc>* abbrev, std::chrono::minutes* offset);
  4138 +};
  4139 +
  4140 +inline
  4141 +CONSTCD14
  4142 +bool
  4143 +is_am(std::chrono::hours const& h) NOEXCEPT
  4144 +{
  4145 + using std::chrono::hours;
  4146 + return hours{0} <= h && h < hours{12};
  4147 +}
  4148 +
  4149 +inline
  4150 +CONSTCD14
  4151 +bool
  4152 +is_pm(std::chrono::hours const& h) NOEXCEPT
  4153 +{
  4154 + using std::chrono::hours;
  4155 + return hours{12} <= h && h < hours{24};
  4156 +}
  4157 +
  4158 +inline
  4159 +CONSTCD14
  4160 +std::chrono::hours
  4161 +make12(std::chrono::hours h) NOEXCEPT
  4162 +{
  4163 + using std::chrono::hours;
  4164 + if (h < hours{12})
  4165 + {
  4166 + if (h == hours{0})
  4167 + h = hours{12};
  4168 + }
  4169 + else
  4170 + {
  4171 + if (h != hours{12})
  4172 + h = h - hours{12};
  4173 + }
  4174 + return h;
  4175 +}
  4176 +
  4177 +inline
  4178 +CONSTCD14
  4179 +std::chrono::hours
  4180 +make24(std::chrono::hours h, bool is_pm) NOEXCEPT
  4181 +{
  4182 + using std::chrono::hours;
  4183 + if (is_pm)
  4184 + {
  4185 + if (h != hours{12})
  4186 + h = h + hours{12};
  4187 + }
  4188 + else if (h == hours{12})
  4189 + h = hours{0};
  4190 + return h;
  4191 +}
  4192 +
  4193 +template <class Duration>
  4194 +using time_of_day = hh_mm_ss<Duration>;
  4195 +
  4196 +template <class Rep, class Period>
  4197 +CONSTCD11
  4198 +inline
  4199 +hh_mm_ss<std::chrono::duration<Rep, Period>>
  4200 +make_time(const std::chrono::duration<Rep, Period>& d)
  4201 +{
  4202 + return hh_mm_ss<std::chrono::duration<Rep, Period>>(d);
  4203 +}
  4204 +
  4205 +template <class CharT, class Traits, class Duration>
  4206 +inline
  4207 +typename std::enable_if
  4208 +<
  4209 + std::ratio_less<typename Duration::period, days::period>::value
  4210 + , std::basic_ostream<CharT, Traits>&
  4211 +>::type
  4212 +operator<<(std::basic_ostream<CharT, Traits>& os, const sys_time<Duration>& tp)
  4213 +{
  4214 + auto const dp = date::floor<days>(tp);
  4215 + return os << year_month_day(dp) << ' ' << make_time(tp-dp);
  4216 +}
  4217 +
  4218 +template <class CharT, class Traits>
  4219 +inline
  4220 +std::basic_ostream<CharT, Traits>&
  4221 +operator<<(std::basic_ostream<CharT, Traits>& os, const sys_days& dp)
  4222 +{
  4223 + return os << year_month_day(dp);
  4224 +}
  4225 +
  4226 +template <class CharT, class Traits, class Duration>
  4227 +inline
  4228 +std::basic_ostream<CharT, Traits>&
  4229 +operator<<(std::basic_ostream<CharT, Traits>& os, const local_time<Duration>& ut)
  4230 +{
  4231 + return (os << sys_time<Duration>{ut.time_since_epoch()});
  4232 +}
  4233 +
  4234 +namespace detail
  4235 +{
  4236 +
  4237 +template <class CharT, std::size_t N>
  4238 +class string_literal;
  4239 +
  4240 +template <class CharT1, class CharT2, std::size_t N1, std::size_t N2>
  4241 +inline
  4242 +CONSTCD14
  4243 +string_literal<typename std::conditional<sizeof(CharT2) <= sizeof(CharT1), CharT1, CharT2>::type,
  4244 + N1 + N2 - 1>
  4245 +operator+(const string_literal<CharT1, N1>& x, const string_literal<CharT2, N2>& y) NOEXCEPT;
  4246 +
  4247 +template <class CharT, std::size_t N>
  4248 +class string_literal
  4249 +{
  4250 + CharT p_[N];
  4251 +
  4252 + CONSTCD11 string_literal() NOEXCEPT
  4253 + : p_{}
  4254 + {}
  4255 +
  4256 +public:
  4257 + using const_iterator = const CharT*;
  4258 +
  4259 + string_literal(string_literal const&) = default;
  4260 + string_literal& operator=(string_literal const&) = delete;
  4261 +
  4262 + template <std::size_t N1 = 2,
  4263 + class = typename std::enable_if<N1 == N>::type>
  4264 + CONSTCD11 string_literal(CharT c) NOEXCEPT
  4265 + : p_{c}
  4266 + {
  4267 + }
  4268 +
  4269 + template <std::size_t N1 = 3,
  4270 + class = typename std::enable_if<N1 == N>::type>
  4271 + CONSTCD11 string_literal(CharT c1, CharT c2) NOEXCEPT
  4272 + : p_{c1, c2}
  4273 + {
  4274 + }
  4275 +
  4276 + template <std::size_t N1 = 4,
  4277 + class = typename std::enable_if<N1 == N>::type>
  4278 + CONSTCD11 string_literal(CharT c1, CharT c2, CharT c3) NOEXCEPT
  4279 + : p_{c1, c2, c3}
  4280 + {
  4281 + }
  4282 +
  4283 + CONSTCD14 string_literal(const CharT(&a)[N]) NOEXCEPT
  4284 + : p_{}
  4285 + {
  4286 + for (std::size_t i = 0; i < N; ++i)
  4287 + p_[i] = a[i];
  4288 + }
  4289 +
  4290 + template <class U = CharT,
  4291 + class = typename std::enable_if<(1 < sizeof(U))>::type>
  4292 + CONSTCD14 string_literal(const char(&a)[N]) NOEXCEPT
  4293 + : p_{}
  4294 + {
  4295 + for (std::size_t i = 0; i < N; ++i)
  4296 + p_[i] = a[i];
  4297 + }
  4298 +
  4299 + template <class CharT2,
  4300 + class = typename std::enable_if<!std::is_same<CharT2, CharT>::value>::type>
  4301 + CONSTCD14 string_literal(string_literal<CharT2, N> const& a) NOEXCEPT
  4302 + : p_{}
  4303 + {
  4304 + for (std::size_t i = 0; i < N; ++i)
  4305 + p_[i] = a[i];
  4306 + }
  4307 +
  4308 + CONSTCD11 const CharT* data() const NOEXCEPT {return p_;}
  4309 + CONSTCD11 std::size_t size() const NOEXCEPT {return N-1;}
  4310 +
  4311 + CONSTCD11 const_iterator begin() const NOEXCEPT {return p_;}
  4312 + CONSTCD11 const_iterator end() const NOEXCEPT {return p_ + N-1;}
  4313 +
  4314 + CONSTCD11 CharT const& operator[](std::size_t n) const NOEXCEPT
  4315 + {
  4316 + return p_[n];
  4317 + }
  4318 +
  4319 + template <class Traits>
  4320 + friend
  4321 + std::basic_ostream<CharT, Traits>&
  4322 + operator<<(std::basic_ostream<CharT, Traits>& os, const string_literal& s)
  4323 + {
  4324 + return os << s.p_;
  4325 + }
  4326 +
  4327 + template <class CharT1, class CharT2, std::size_t N1, std::size_t N2>
  4328 + friend
  4329 + CONSTCD14
  4330 + string_literal<typename std::conditional<sizeof(CharT2) <= sizeof(CharT1), CharT1, CharT2>::type,
  4331 + N1 + N2 - 1>
  4332 + operator+(const string_literal<CharT1, N1>& x, const string_literal<CharT2, N2>& y) NOEXCEPT;
  4333 +};
  4334 +
  4335 +template <class CharT>
  4336 +CONSTCD11
  4337 +inline
  4338 +string_literal<CharT, 3>
  4339 +operator+(const string_literal<CharT, 2>& x, const string_literal<CharT, 2>& y) NOEXCEPT
  4340 +{
  4341 + return string_literal<CharT, 3>(x[0], y[0]);
  4342 +}
  4343 +
  4344 +template <class CharT>
  4345 +CONSTCD11
  4346 +inline
  4347 +string_literal<CharT, 4>
  4348 +operator+(const string_literal<CharT, 3>& x, const string_literal<CharT, 2>& y) NOEXCEPT
  4349 +{
  4350 + return string_literal<CharT, 4>(x[0], x[1], y[0]);
  4351 +}
  4352 +
  4353 +template <class CharT1, class CharT2, std::size_t N1, std::size_t N2>
  4354 +CONSTCD14
  4355 +inline
  4356 +string_literal<typename std::conditional<sizeof(CharT2) <= sizeof(CharT1), CharT1, CharT2>::type,
  4357 + N1 + N2 - 1>
  4358 +operator+(const string_literal<CharT1, N1>& x, const string_literal<CharT2, N2>& y) NOEXCEPT
  4359 +{
  4360 + using CT = typename std::conditional<sizeof(CharT2) <= sizeof(CharT1), CharT1, CharT2>::type;
  4361 +
  4362 + string_literal<CT, N1 + N2 - 1> r;
  4363 + std::size_t i = 0;
  4364 + for (; i < N1-1; ++i)
  4365 + r.p_[i] = CT(x.p_[i]);
  4366 + for (std::size_t j = 0; j < N2; ++j, ++i)
  4367 + r.p_[i] = CT(y.p_[j]);
  4368 +
  4369 + return r;
  4370 +}
  4371 +
  4372 +
  4373 +template <class CharT, class Traits, class Alloc, std::size_t N>
  4374 +inline
  4375 +std::basic_string<CharT, Traits, Alloc>
  4376 +operator+(std::basic_string<CharT, Traits, Alloc> x, const string_literal<CharT, N>& y)
  4377 +{
  4378 + x.append(y.data(), y.size());
  4379 + return x;
  4380 +}
  4381 +
  4382 +#if __cplusplus >= 201402 && (!defined(__EDG_VERSION__) || __EDG_VERSION__ > 411) \
  4383 + && (!defined(__SUNPRO_CC) || __SUNPRO_CC > 0x5150)
  4384 +
  4385 +template <class CharT,
  4386 + class = std::enable_if_t<std::is_same<CharT, char>::value ||
  4387 + std::is_same<CharT, wchar_t>::value ||
  4388 + std::is_same<CharT, char16_t>::value ||
  4389 + std::is_same<CharT, char32_t>::value>>
  4390 +CONSTCD14
  4391 +inline
  4392 +string_literal<CharT, 2>
  4393 +msl(CharT c) NOEXCEPT
  4394 +{
  4395 + return string_literal<CharT, 2>{c};
  4396 +}
  4397 +
  4398 +CONSTCD14
  4399 +inline
  4400 +std::size_t
  4401 +to_string_len(std::intmax_t i)
  4402 +{
  4403 + std::size_t r = 0;
  4404 + do
  4405 + {
  4406 + i /= 10;
  4407 + ++r;
  4408 + } while (i > 0);
  4409 + return r;
  4410 +}
  4411 +
  4412 +template <std::intmax_t N>
  4413 +CONSTCD14
  4414 +inline
  4415 +std::enable_if_t
  4416 +<
  4417 + N < 10,
  4418 + string_literal<char, to_string_len(N)+1>
  4419 +>
  4420 +msl() NOEXCEPT
  4421 +{
  4422 + return msl(char(N % 10 + '0'));
  4423 +}
  4424 +
  4425 +template <std::intmax_t N>
  4426 +CONSTCD14
  4427 +inline
  4428 +std::enable_if_t
  4429 +<
  4430 + 10 <= N,
  4431 + string_literal<char, to_string_len(N)+1>
  4432 +>
  4433 +msl() NOEXCEPT
  4434 +{
  4435 + return msl<N/10>() + msl(char(N % 10 + '0'));
  4436 +}
  4437 +
  4438 +template <class CharT, std::intmax_t N, std::intmax_t D>
  4439 +CONSTCD14
  4440 +inline
  4441 +std::enable_if_t
  4442 +<
  4443 + std::ratio<N, D>::type::den != 1,
  4444 + string_literal<CharT, to_string_len(std::ratio<N, D>::type::num) +
  4445 + to_string_len(std::ratio<N, D>::type::den) + 4>
  4446 +>
  4447 +msl(std::ratio<N, D>) NOEXCEPT
  4448 +{
  4449 + using R = typename std::ratio<N, D>::type;
  4450 + return msl(CharT{'['}) + msl<R::num>() + msl(CharT{'/'}) +
  4451 + msl<R::den>() + msl(CharT{']'});
  4452 +}
  4453 +
  4454 +template <class CharT, std::intmax_t N, std::intmax_t D>
  4455 +CONSTCD14
  4456 +inline
  4457 +std::enable_if_t
  4458 +<
  4459 + std::ratio<N, D>::type::den == 1,
  4460 + string_literal<CharT, to_string_len(std::ratio<N, D>::type::num) + 3>
  4461 +>
  4462 +msl(std::ratio<N, D>) NOEXCEPT
  4463 +{
  4464 + using R = typename std::ratio<N, D>::type;
  4465 + return msl(CharT{'['}) + msl<R::num>() + msl(CharT{']'});
  4466 +}
  4467 +
  4468 +
  4469 +#else // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411)
  4470 +
  4471 +inline
  4472 +std::string
  4473 +to_string(std::uint64_t x)
  4474 +{
  4475 + return std::to_string(x);
  4476 +}
  4477 +
  4478 +template <class CharT>
  4479 +inline
  4480 +std::basic_string<CharT>
  4481 +to_string(std::uint64_t x)
  4482 +{
  4483 + auto y = std::to_string(x);
  4484 + return std::basic_string<CharT>(y.begin(), y.end());
  4485 +}
  4486 +
  4487 +template <class CharT, std::intmax_t N, std::intmax_t D>
  4488 +inline
  4489 +typename std::enable_if
  4490 +<
  4491 + std::ratio<N, D>::type::den != 1,
  4492 + std::basic_string<CharT>
  4493 +>::type
  4494 +msl(std::ratio<N, D>)
  4495 +{
  4496 + using R = typename std::ratio<N, D>::type;
  4497 + return std::basic_string<CharT>(1, '[') + to_string<CharT>(R::num) + CharT{'/'} +
  4498 + to_string<CharT>(R::den) + CharT{']'};
  4499 +}
  4500 +
  4501 +template <class CharT, std::intmax_t N, std::intmax_t D>
  4502 +inline
  4503 +typename std::enable_if
  4504 +<
  4505 + std::ratio<N, D>::type::den == 1,
  4506 + std::basic_string<CharT>
  4507 +>::type
  4508 +msl(std::ratio<N, D>)
  4509 +{
  4510 + using R = typename std::ratio<N, D>::type;
  4511 + return std::basic_string<CharT>(1, '[') + to_string<CharT>(R::num) + CharT{']'};
  4512 +}
  4513 +
  4514 +#endif // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411)
  4515 +
  4516 +template <class CharT>
  4517 +CONSTCD11
  4518 +inline
  4519 +string_literal<CharT, 2>
  4520 +msl(std::atto) NOEXCEPT
  4521 +{
  4522 + return string_literal<CharT, 2>{'a'};
  4523 +}
  4524 +
  4525 +template <class CharT>
  4526 +CONSTCD11
  4527 +inline
  4528 +string_literal<CharT, 2>
  4529 +msl(std::femto) NOEXCEPT
  4530 +{
  4531 + return string_literal<CharT, 2>{'f'};
  4532 +}
  4533 +
  4534 +template <class CharT>
  4535 +CONSTCD11
  4536 +inline
  4537 +string_literal<CharT, 2>
  4538 +msl(std::pico) NOEXCEPT
  4539 +{
  4540 + return string_literal<CharT, 2>{'p'};
  4541 +}
  4542 +
  4543 +template <class CharT>
  4544 +CONSTCD11
  4545 +inline
  4546 +string_literal<CharT, 2>
  4547 +msl(std::nano) NOEXCEPT
  4548 +{
  4549 + return string_literal<CharT, 2>{'n'};
  4550 +}
  4551 +
  4552 +template <class CharT>
  4553 +CONSTCD11
  4554 +inline
  4555 +typename std::enable_if
  4556 +<
  4557 + std::is_same<CharT, char>::value,
  4558 + string_literal<char, 3>
  4559 +>::type
  4560 +msl(std::micro) NOEXCEPT
  4561 +{
  4562 + return string_literal<char, 3>{'\xC2', '\xB5'};
  4563 +}
  4564 +
  4565 +template <class CharT>
  4566 +CONSTCD11
  4567 +inline
  4568 +typename std::enable_if
  4569 +<
  4570 + !std::is_same<CharT, char>::value,
  4571 + string_literal<CharT, 2>
  4572 +>::type
  4573 +msl(std::micro) NOEXCEPT
  4574 +{
  4575 + return string_literal<CharT, 2>{CharT{static_cast<unsigned char>('\xB5')}};
  4576 +}
  4577 +
  4578 +template <class CharT>
  4579 +CONSTCD11
  4580 +inline
  4581 +string_literal<CharT, 2>
  4582 +msl(std::milli) NOEXCEPT
  4583 +{
  4584 + return string_literal<CharT, 2>{'m'};
  4585 +}
  4586 +
  4587 +template <class CharT>
  4588 +CONSTCD11
  4589 +inline
  4590 +string_literal<CharT, 2>
  4591 +msl(std::centi) NOEXCEPT
  4592 +{
  4593 + return string_literal<CharT, 2>{'c'};
  4594 +}
  4595 +
  4596 +template <class CharT>
  4597 +CONSTCD11
  4598 +inline
  4599 +string_literal<CharT, 3>
  4600 +msl(std::deca) NOEXCEPT
  4601 +{
  4602 + return string_literal<CharT, 3>{'d', 'a'};
  4603 +}
  4604 +
  4605 +template <class CharT>
  4606 +CONSTCD11
  4607 +inline
  4608 +string_literal<CharT, 2>
  4609 +msl(std::deci) NOEXCEPT
  4610 +{
  4611 + return string_literal<CharT, 2>{'d'};
  4612 +}
  4613 +
  4614 +template <class CharT>
  4615 +CONSTCD11
  4616 +inline
  4617 +string_literal<CharT, 2>
  4618 +msl(std::hecto) NOEXCEPT
  4619 +{
  4620 + return string_literal<CharT, 2>{'h'};
  4621 +}
  4622 +
  4623 +template <class CharT>
  4624 +CONSTCD11
  4625 +inline
  4626 +string_literal<CharT, 2>
  4627 +msl(std::kilo) NOEXCEPT
  4628 +{
  4629 + return string_literal<CharT, 2>{'k'};
  4630 +}
  4631 +
  4632 +template <class CharT>
  4633 +CONSTCD11
  4634 +inline
  4635 +string_literal<CharT, 2>
  4636 +msl(std::mega) NOEXCEPT
  4637 +{
  4638 + return string_literal<CharT, 2>{'M'};
  4639 +}
  4640 +
  4641 +template <class CharT>
  4642 +CONSTCD11
  4643 +inline
  4644 +string_literal<CharT, 2>
  4645 +msl(std::giga) NOEXCEPT
  4646 +{
  4647 + return string_literal<CharT, 2>{'G'};
  4648 +}
  4649 +
  4650 +template <class CharT>
  4651 +CONSTCD11
  4652 +inline
  4653 +string_literal<CharT, 2>
  4654 +msl(std::tera) NOEXCEPT
  4655 +{
  4656 + return string_literal<CharT, 2>{'T'};
  4657 +}
  4658 +
  4659 +template <class CharT>
  4660 +CONSTCD11
  4661 +inline
  4662 +string_literal<CharT, 2>
  4663 +msl(std::peta) NOEXCEPT
  4664 +{
  4665 + return string_literal<CharT, 2>{'P'};
  4666 +}
  4667 +
  4668 +template <class CharT>
  4669 +CONSTCD11
  4670 +inline
  4671 +string_literal<CharT, 2>
  4672 +msl(std::exa) NOEXCEPT
  4673 +{
  4674 + return string_literal<CharT, 2>{'E'};
  4675 +}
  4676 +
  4677 +template <class CharT, class Period>
  4678 +CONSTCD11
  4679 +inline
  4680 +auto
  4681 +get_units(Period p)
  4682 + -> decltype(msl<CharT>(p) + string_literal<CharT, 2>{'s'})
  4683 +{
  4684 + return msl<CharT>(p) + string_literal<CharT, 2>{'s'};
  4685 +}
  4686 +
  4687 +template <class CharT>
  4688 +CONSTCD11
  4689 +inline
  4690 +string_literal<CharT, 2>
  4691 +get_units(std::ratio<1>)
  4692 +{
  4693 + return string_literal<CharT, 2>{'s'};
  4694 +}
  4695 +
  4696 +template <class CharT>
  4697 +CONSTCD11
  4698 +inline
  4699 +string_literal<CharT, 2>
  4700 +get_units(std::ratio<3600>)
  4701 +{
  4702 + return string_literal<CharT, 2>{'h'};
  4703 +}
  4704 +
  4705 +template <class CharT>
  4706 +CONSTCD11
  4707 +inline
  4708 +string_literal<CharT, 4>
  4709 +get_units(std::ratio<60>)
  4710 +{
  4711 + return string_literal<CharT, 4>{'m', 'i', 'n'};
  4712 +}
  4713 +
  4714 +template <class CharT>
  4715 +CONSTCD11
  4716 +inline
  4717 +string_literal<CharT, 2>
  4718 +get_units(std::ratio<86400>)
  4719 +{
  4720 + return string_literal<CharT, 2>{'d'};
  4721 +}
  4722 +
  4723 +template <class CharT, class Traits = std::char_traits<CharT>>
  4724 +struct make_string;
  4725 +
  4726 +template <>
  4727 +struct make_string<char>
  4728 +{
  4729 + template <class Rep>
  4730 + static
  4731 + std::string
  4732 + from(Rep n)
  4733 + {
  4734 + return std::to_string(n);
  4735 + }
  4736 +};
  4737 +
  4738 +template <class Traits>
  4739 +struct make_string<char, Traits>
  4740 +{
  4741 + template <class Rep>
  4742 + static
  4743 + std::basic_string<char, Traits>
  4744 + from(Rep n)
  4745 + {
  4746 + auto s = std::to_string(n);
  4747 + return std::basic_string<char, Traits>(s.begin(), s.end());
  4748 + }
  4749 +};
  4750 +
  4751 +template <>
  4752 +struct make_string<wchar_t>
  4753 +{
  4754 + template <class Rep>
  4755 + static
  4756 + std::wstring
  4757 + from(Rep n)
  4758 + {
  4759 + return std::to_wstring(n);
  4760 + }
  4761 +};
  4762 +
  4763 +template <class Traits>
  4764 +struct make_string<wchar_t, Traits>
  4765 +{
  4766 + template <class Rep>
  4767 + static
  4768 + std::basic_string<wchar_t, Traits>
  4769 + from(Rep n)
  4770 + {
  4771 + auto s = std::to_wstring(n);
  4772 + return std::basic_string<wchar_t, Traits>(s.begin(), s.end());
  4773 + }
  4774 +};
  4775 +
  4776 +} // namespace detail
  4777 +
  4778 +// to_stream
  4779 +
  4780 +CONSTDATA year nanyear{-32768};
  4781 +
  4782 +template <class Duration>
  4783 +struct fields
  4784 +{
  4785 + year_month_day ymd{nanyear/0/0};
  4786 + weekday wd{8u};
  4787 + hh_mm_ss<Duration> tod{};
  4788 + bool has_tod = false;
  4789 +
  4790 +#if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ <= 409)
  4791 + fields() : ymd{nanyear/0/0}, wd{8u}, tod{}, has_tod{false} {}
  4792 +#else
  4793 + fields() = default;
  4794 +#endif
  4795 +
  4796 + fields(year_month_day ymd_) : ymd(ymd_) {}
  4797 + fields(weekday wd_) : wd(wd_) {}
  4798 + fields(hh_mm_ss<Duration> tod_) : tod(tod_), has_tod(true) {}
  4799 +
  4800 + fields(year_month_day ymd_, weekday wd_) : ymd(ymd_), wd(wd_) {}
  4801 + fields(year_month_day ymd_, hh_mm_ss<Duration> tod_) : ymd(ymd_), tod(tod_),
  4802 + has_tod(true) {}
  4803 +
  4804 + fields(weekday wd_, hh_mm_ss<Duration> tod_) : wd(wd_), tod(tod_), has_tod(true) {}
  4805 +
  4806 + fields(year_month_day ymd_, weekday wd_, hh_mm_ss<Duration> tod_)
  4807 + : ymd(ymd_)
  4808 + , wd(wd_)
  4809 + , tod(tod_)
  4810 + , has_tod(true)
  4811 + {}
  4812 +};
  4813 +
  4814 +namespace detail
  4815 +{
  4816 +
  4817 +template <class CharT, class Traits, class Duration>
  4818 +unsigned
  4819 +extract_weekday(std::basic_ostream<CharT, Traits>& os, const fields<Duration>& fds)
  4820 +{
  4821 + if (!fds.ymd.ok() && !fds.wd.ok())
  4822 + {
  4823 + // fds does not contain a valid weekday
  4824 + os.setstate(std::ios::failbit);
  4825 + return 8;
  4826 + }
  4827 + weekday wd;
  4828 + if (fds.ymd.ok())
  4829 + {
  4830 + wd = weekday{sys_days(fds.ymd)};
  4831 + if (fds.wd.ok() && wd != fds.wd)
  4832 + {
  4833 + // fds.ymd and fds.wd are inconsistent
  4834 + os.setstate(std::ios::failbit);
  4835 + return 8;
  4836 + }
  4837 + }
  4838 + else
  4839 + wd = fds.wd;
  4840 + return static_cast<unsigned>((wd - Sunday).count());
  4841 +}
  4842 +
  4843 +template <class CharT, class Traits, class Duration>
  4844 +unsigned
  4845 +extract_month(std::basic_ostream<CharT, Traits>& os, const fields<Duration>& fds)
  4846 +{
  4847 + if (!fds.ymd.month().ok())
  4848 + {
  4849 + // fds does not contain a valid month
  4850 + os.setstate(std::ios::failbit);
  4851 + return 0;
  4852 + }
  4853 + return static_cast<unsigned>(fds.ymd.month());
  4854 +}
  4855 +
  4856 +} // namespace detail
  4857 +
  4858 +#if ONLY_C_LOCALE
  4859 +
  4860 +namespace detail
  4861 +{
  4862 +
  4863 +inline
  4864 +std::pair<const std::string*, const std::string*>
  4865 +weekday_names()
  4866 +{
  4867 + static const std::string nm[] =
  4868 + {
  4869 + "Sunday",
  4870 + "Monday",
  4871 + "Tuesday",
  4872 + "Wednesday",
  4873 + "Thursday",
  4874 + "Friday",
  4875 + "Saturday",
  4876 + "Sun",
  4877 + "Mon",
  4878 + "Tue",
  4879 + "Wed",
  4880 + "Thu",
  4881 + "Fri",
  4882 + "Sat"
  4883 + };
  4884 + return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0]));
  4885 +}
  4886 +
  4887 +inline
  4888 +std::pair<const std::string*, const std::string*>
  4889 +month_names()
  4890 +{
  4891 + static const std::string nm[] =
  4892 + {
  4893 + "January",
  4894 + "February",
  4895 + "March",
  4896 + "April",
  4897 + "May",
  4898 + "June",
  4899 + "July",
  4900 + "August",
  4901 + "September",
  4902 + "October",
  4903 + "November",
  4904 + "December",
  4905 + "Jan",
  4906 + "Feb",
  4907 + "Mar",
  4908 + "Apr",
  4909 + "May",
  4910 + "Jun",
  4911 + "Jul",
  4912 + "Aug",
  4913 + "Sep",
  4914 + "Oct",
  4915 + "Nov",
  4916 + "Dec"
  4917 + };
  4918 + return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0]));
  4919 +}
  4920 +
  4921 +inline
  4922 +std::pair<const std::string*, const std::string*>
  4923 +ampm_names()
  4924 +{
  4925 + static const std::string nm[] =
  4926 + {
  4927 + "AM",
  4928 + "PM"
  4929 + };
  4930 + return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0]));
  4931 +}
  4932 +
  4933 +template <class CharT, class Traits, class FwdIter>
  4934 +FwdIter
  4935 +scan_keyword(std::basic_istream<CharT, Traits>& is, FwdIter kb, FwdIter ke)
  4936 +{
  4937 + size_t nkw = static_cast<size_t>(std::distance(kb, ke));
  4938 + const unsigned char doesnt_match = '\0';
  4939 + const unsigned char might_match = '\1';
  4940 + const unsigned char does_match = '\2';
  4941 + unsigned char statbuf[100];
  4942 + unsigned char* status = statbuf;
  4943 + std::unique_ptr<unsigned char, void(*)(void*)> stat_hold(0, free);
  4944 + if (nkw > sizeof(statbuf))
  4945 + {
  4946 + status = (unsigned char*)std::malloc(nkw);
  4947 + if (status == nullptr)
  4948 + throw std::bad_alloc();
  4949 + stat_hold.reset(status);
  4950 + }
  4951 + size_t n_might_match = nkw; // At this point, any keyword might match
  4952 + size_t n_does_match = 0; // but none of them definitely do
  4953 + // Initialize all statuses to might_match, except for "" keywords are does_match
  4954 + unsigned char* st = status;
  4955 + for (auto ky = kb; ky != ke; ++ky, ++st)
  4956 + {
  4957 + if (!ky->empty())
  4958 + *st = might_match;
  4959 + else
  4960 + {
  4961 + *st = does_match;
  4962 + --n_might_match;
  4963 + ++n_does_match;
  4964 + }
  4965 + }
  4966 + // While there might be a match, test keywords against the next CharT
  4967 + for (size_t indx = 0; is && n_might_match > 0; ++indx)
  4968 + {
  4969 + // Peek at the next CharT but don't consume it
  4970 + auto ic = is.peek();
  4971 + if (ic == EOF)
  4972 + {
  4973 + is.setstate(std::ios::eofbit);
  4974 + break;
  4975 + }
  4976 + auto c = static_cast<char>(toupper(static_cast<unsigned char>(ic)));
  4977 + bool consume = false;
  4978 + // For each keyword which might match, see if the indx character is c
  4979 + // If a match if found, consume c
  4980 + // If a match is found, and that is the last character in the keyword,
  4981 + // then that keyword matches.
  4982 + // If the keyword doesn't match this character, then change the keyword
  4983 + // to doesn't match
  4984 + st = status;
  4985 + for (auto ky = kb; ky != ke; ++ky, ++st)
  4986 + {
  4987 + if (*st == might_match)
  4988 + {
  4989 + if (c == static_cast<char>(toupper(static_cast<unsigned char>((*ky)[indx]))))
  4990 + {
  4991 + consume = true;
  4992 + if (ky->size() == indx+1)
  4993 + {
  4994 + *st = does_match;
  4995 + --n_might_match;
  4996 + ++n_does_match;
  4997 + }
  4998 + }
  4999 + else
  5000 + {
  5001 + *st = doesnt_match;
  5002 + --n_might_match;
  5003 + }
  5004 + }
  5005 + }
  5006 + // consume if we matched a character
  5007 + if (consume)
  5008 + {
  5009 + (void)is.get();
  5010 + // If we consumed a character and there might be a matched keyword that
  5011 + // was marked matched on a previous iteration, then such keywords
  5012 + // are now marked as not matching.
  5013 + if (n_might_match + n_does_match > 1)
  5014 + {
  5015 + st = status;
  5016 + for (auto ky = kb; ky != ke; ++ky, ++st)
  5017 + {
  5018 + if (*st == does_match && ky->size() != indx+1)
  5019 + {
  5020 + *st = doesnt_match;
  5021 + --n_does_match;
  5022 + }
  5023 + }
  5024 + }
  5025 + }
  5026 + }
  5027 + // We've exited the loop because we hit eof and/or we have no more "might matches".
  5028 + // Return the first matching result
  5029 + for (st = status; kb != ke; ++kb, ++st)
  5030 + if (*st == does_match)
  5031 + break;
  5032 + if (kb == ke)
  5033 + is.setstate(std::ios::failbit);
  5034 + return kb;
  5035 +}
  5036 +
  5037 +} // namespace detail
  5038 +
  5039 +#endif // ONLY_C_LOCALE
  5040 +
  5041 +template <class CharT, class Traits, class Duration>
  5042 +std::basic_ostream<CharT, Traits>&
  5043 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
  5044 + const fields<Duration>& fds, const std::string* abbrev,
  5045 + const std::chrono::seconds* offset_sec)
  5046 +{
  5047 +#if ONLY_C_LOCALE
  5048 + using detail::weekday_names;
  5049 + using detail::month_names;
  5050 + using detail::ampm_names;
  5051 +#endif
  5052 + using detail::save_ostream;
  5053 + using detail::get_units;
  5054 + using detail::extract_weekday;
  5055 + using detail::extract_month;
  5056 + using std::ios;
  5057 + using std::chrono::duration_cast;
  5058 + using std::chrono::seconds;
  5059 + using std::chrono::minutes;
  5060 + using std::chrono::hours;
  5061 + date::detail::save_ostream<CharT, Traits> ss(os);
  5062 + os.fill(' ');
  5063 + os.flags(std::ios::skipws | std::ios::dec);
  5064 + os.width(0);
  5065 + tm tm{};
  5066 + bool insert_negative = fds.has_tod && fds.tod.to_duration() < Duration::zero();
  5067 +#if !ONLY_C_LOCALE
  5068 + auto& facet = std::use_facet<std::time_put<CharT>>(os.getloc());
  5069 +#endif
  5070 + const CharT* command = nullptr;
  5071 + CharT modified = CharT{};
  5072 + for (; *fmt; ++fmt)
  5073 + {
  5074 + switch (*fmt)
  5075 + {
  5076 + case 'a':
  5077 + case 'A':
  5078 + if (command)
  5079 + {
  5080 + if (modified == CharT{})
  5081 + {
  5082 + tm.tm_wday = static_cast<int>(extract_weekday(os, fds));
  5083 + if (os.fail())
  5084 + return os;
  5085 +#if !ONLY_C_LOCALE
  5086 + const CharT f[] = {'%', *fmt};
  5087 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5088 +#else // ONLY_C_LOCALE
  5089 + os << weekday_names().first[tm.tm_wday+7*(*fmt == 'a')];
  5090 +#endif // ONLY_C_LOCALE
  5091 + }
  5092 + else
  5093 + {
  5094 + os << CharT{'%'} << modified << *fmt;
  5095 + modified = CharT{};
  5096 + }
  5097 + command = nullptr;
  5098 + }
  5099 + else
  5100 + os << *fmt;
  5101 + break;
  5102 + case 'b':
  5103 + case 'B':
  5104 + case 'h':
  5105 + if (command)
  5106 + {
  5107 + if (modified == CharT{})
  5108 + {
  5109 + tm.tm_mon = static_cast<int>(extract_month(os, fds)) - 1;
  5110 +#if !ONLY_C_LOCALE
  5111 + const CharT f[] = {'%', *fmt};
  5112 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5113 +#else // ONLY_C_LOCALE
  5114 + os << month_names().first[tm.tm_mon+12*(*fmt != 'B')];
  5115 +#endif // ONLY_C_LOCALE
  5116 + }
  5117 + else
  5118 + {
  5119 + os << CharT{'%'} << modified << *fmt;
  5120 + modified = CharT{};
  5121 + }
  5122 + command = nullptr;
  5123 + }
  5124 + else
  5125 + os << *fmt;
  5126 + break;
  5127 + case 'c':
  5128 + case 'x':
  5129 + if (command)
  5130 + {
  5131 + if (modified == CharT{'O'})
  5132 + os << CharT{'%'} << modified << *fmt;
  5133 + else
  5134 + {
  5135 + if (!fds.ymd.ok())
  5136 + os.setstate(std::ios::failbit);
  5137 + if (*fmt == 'c' && !fds.has_tod)
  5138 + os.setstate(std::ios::failbit);
  5139 +#if !ONLY_C_LOCALE
  5140 + tm = std::tm{};
  5141 + auto const& ymd = fds.ymd;
  5142 + auto ld = local_days(ymd);
  5143 + if (*fmt == 'c')
  5144 + {
  5145 + tm.tm_sec = static_cast<int>(fds.tod.seconds().count());
  5146 + tm.tm_min = static_cast<int>(fds.tod.minutes().count());
  5147 + tm.tm_hour = static_cast<int>(fds.tod.hours().count());
  5148 + }
  5149 + tm.tm_mday = static_cast<int>(static_cast<unsigned>(ymd.day()));
  5150 + tm.tm_mon = static_cast<int>(extract_month(os, fds) - 1);
  5151 + tm.tm_year = static_cast<int>(ymd.year()) - 1900;
  5152 + tm.tm_wday = static_cast<int>(extract_weekday(os, fds));
  5153 + if (os.fail())
  5154 + return os;
  5155 + tm.tm_yday = static_cast<int>((ld - local_days(ymd.year()/1/1)).count());
  5156 + CharT f[3] = {'%'};
  5157 + auto fe = std::begin(f) + 1;
  5158 + if (modified == CharT{'E'})
  5159 + *fe++ = modified;
  5160 + *fe++ = *fmt;
  5161 + facet.put(os, os, os.fill(), &tm, std::begin(f), fe);
  5162 +#else // ONLY_C_LOCALE
  5163 + if (*fmt == 'c')
  5164 + {
  5165 + auto wd = static_cast<int>(extract_weekday(os, fds));
  5166 + os << weekday_names().first[static_cast<unsigned>(wd)+7]
  5167 + << ' ';
  5168 + os << month_names().first[extract_month(os, fds)-1+12] << ' ';
  5169 + auto d = static_cast<int>(static_cast<unsigned>(fds.ymd.day()));
  5170 + if (d < 10)
  5171 + os << ' ';
  5172 + os << d << ' '
  5173 + << make_time(duration_cast<seconds>(fds.tod.to_duration()))
  5174 + << ' ' << fds.ymd.year();
  5175 +
  5176 + }
  5177 + else // *fmt == 'x'
  5178 + {
  5179 + auto const& ymd = fds.ymd;
  5180 + save_ostream<CharT, Traits> _(os);
  5181 + os.fill('0');
  5182 + os.flags(std::ios::dec | std::ios::right);
  5183 + os.width(2);
  5184 + os << static_cast<unsigned>(ymd.month()) << CharT{'/'};
  5185 + os.width(2);
  5186 + os << static_cast<unsigned>(ymd.day()) << CharT{'/'};
  5187 + os.width(2);
  5188 + os << static_cast<int>(ymd.year()) % 100;
  5189 + }
  5190 +#endif // ONLY_C_LOCALE
  5191 + }
  5192 + command = nullptr;
  5193 + modified = CharT{};
  5194 + }
  5195 + else
  5196 + os << *fmt;
  5197 + break;
  5198 + case 'C':
  5199 + if (command)
  5200 + {
  5201 + if (modified == CharT{'O'})
  5202 + os << CharT{'%'} << modified << *fmt;
  5203 + else
  5204 + {
  5205 + if (!fds.ymd.year().ok())
  5206 + os.setstate(std::ios::failbit);
  5207 + auto y = static_cast<int>(fds.ymd.year());
  5208 +#if !ONLY_C_LOCALE
  5209 + if (modified == CharT{})
  5210 +#endif
  5211 + {
  5212 + save_ostream<CharT, Traits> _(os);
  5213 + os.fill('0');
  5214 + os.flags(std::ios::dec | std::ios::right);
  5215 + if (y >= 0)
  5216 + {
  5217 + os.width(2);
  5218 + os << y/100;
  5219 + }
  5220 + else
  5221 + {
  5222 + os << CharT{'-'};
  5223 + os.width(2);
  5224 + os << -(y-99)/100;
  5225 + }
  5226 + }
  5227 +#if !ONLY_C_LOCALE
  5228 + else if (modified == CharT{'E'})
  5229 + {
  5230 + tm.tm_year = y - 1900;
  5231 + CharT f[3] = {'%', 'E', 'C'};
  5232 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5233 + }
  5234 +#endif
  5235 + }
  5236 + command = nullptr;
  5237 + modified = CharT{};
  5238 + }
  5239 + else
  5240 + os << *fmt;
  5241 + break;
  5242 + case 'd':
  5243 + case 'e':
  5244 + if (command)
  5245 + {
  5246 + if (modified == CharT{'E'})
  5247 + os << CharT{'%'} << modified << *fmt;
  5248 + else
  5249 + {
  5250 + if (!fds.ymd.day().ok())
  5251 + os.setstate(std::ios::failbit);
  5252 + auto d = static_cast<int>(static_cast<unsigned>(fds.ymd.day()));
  5253 +#if !ONLY_C_LOCALE
  5254 + if (modified == CharT{})
  5255 +#endif
  5256 + {
  5257 + save_ostream<CharT, Traits> _(os);
  5258 + if (*fmt == CharT{'d'})
  5259 + os.fill('0');
  5260 + else
  5261 + os.fill(' ');
  5262 + os.flags(std::ios::dec | std::ios::right);
  5263 + os.width(2);
  5264 + os << d;
  5265 + }
  5266 +#if !ONLY_C_LOCALE
  5267 + else if (modified == CharT{'O'})
  5268 + {
  5269 + tm.tm_mday = d;
  5270 + CharT f[3] = {'%', 'O', *fmt};
  5271 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5272 + }
  5273 +#endif
  5274 + }
  5275 + command = nullptr;
  5276 + modified = CharT{};
  5277 + }
  5278 + else
  5279 + os << *fmt;
  5280 + break;
  5281 + case 'D':
  5282 + if (command)
  5283 + {
  5284 + if (modified == CharT{})
  5285 + {
  5286 + if (!fds.ymd.ok())
  5287 + os.setstate(std::ios::failbit);
  5288 + auto const& ymd = fds.ymd;
  5289 + save_ostream<CharT, Traits> _(os);
  5290 + os.fill('0');
  5291 + os.flags(std::ios::dec | std::ios::right);
  5292 + os.width(2);
  5293 + os << static_cast<unsigned>(ymd.month()) << CharT{'/'};
  5294 + os.width(2);
  5295 + os << static_cast<unsigned>(ymd.day()) << CharT{'/'};
  5296 + os.width(2);
  5297 + os << static_cast<int>(ymd.year()) % 100;
  5298 + }
  5299 + else
  5300 + {
  5301 + os << CharT{'%'} << modified << *fmt;
  5302 + modified = CharT{};
  5303 + }
  5304 + command = nullptr;
  5305 + }
  5306 + else
  5307 + os << *fmt;
  5308 + break;
  5309 + case 'F':
  5310 + if (command)
  5311 + {
  5312 + if (modified == CharT{})
  5313 + {
  5314 + if (!fds.ymd.ok())
  5315 + os.setstate(std::ios::failbit);
  5316 + auto const& ymd = fds.ymd;
  5317 + save_ostream<CharT, Traits> _(os);
  5318 + os.imbue(std::locale::classic());
  5319 + os.fill('0');
  5320 + os.flags(std::ios::dec | std::ios::right);
  5321 + os.width(4);
  5322 + os << static_cast<int>(ymd.year()) << CharT{'-'};
  5323 + os.width(2);
  5324 + os << static_cast<unsigned>(ymd.month()) << CharT{'-'};
  5325 + os.width(2);
  5326 + os << static_cast<unsigned>(ymd.day());
  5327 + }
  5328 + else
  5329 + {
  5330 + os << CharT{'%'} << modified << *fmt;
  5331 + modified = CharT{};
  5332 + }
  5333 + command = nullptr;
  5334 + }
  5335 + else
  5336 + os << *fmt;
  5337 + break;
  5338 + case 'g':
  5339 + case 'G':
  5340 + if (command)
  5341 + {
  5342 + if (modified == CharT{})
  5343 + {
  5344 + if (!fds.ymd.ok())
  5345 + os.setstate(std::ios::failbit);
  5346 + auto ld = local_days(fds.ymd);
  5347 + auto y = year_month_day{ld + days{3}}.year();
  5348 + auto start = local_days((y-years{1})/December/Thursday[last]) +
  5349 + (Monday-Thursday);
  5350 + if (ld < start)
  5351 + --y;
  5352 + if (*fmt == CharT{'G'})
  5353 + os << y;
  5354 + else
  5355 + {
  5356 + save_ostream<CharT, Traits> _(os);
  5357 + os.fill('0');
  5358 + os.flags(std::ios::dec | std::ios::right);
  5359 + os.width(2);
  5360 + os << std::abs(static_cast<int>(y)) % 100;
  5361 + }
  5362 + }
  5363 + else
  5364 + {
  5365 + os << CharT{'%'} << modified << *fmt;
  5366 + modified = CharT{};
  5367 + }
  5368 + command = nullptr;
  5369 + }
  5370 + else
  5371 + os << *fmt;
  5372 + break;
  5373 + case 'H':
  5374 + case 'I':
  5375 + if (command)
  5376 + {
  5377 + if (modified == CharT{'E'})
  5378 + os << CharT{'%'} << modified << *fmt;
  5379 + else
  5380 + {
  5381 + if (!fds.has_tod)
  5382 + os.setstate(std::ios::failbit);
  5383 + if (insert_negative)
  5384 + {
  5385 + os << '-';
  5386 + insert_negative = false;
  5387 + }
  5388 + auto hms = fds.tod;
  5389 +#if !ONLY_C_LOCALE
  5390 + if (modified == CharT{})
  5391 +#endif
  5392 + {
  5393 + auto h = *fmt == CharT{'I'} ? date::make12(hms.hours()) : hms.hours();
  5394 + if (h < hours{10})
  5395 + os << CharT{'0'};
  5396 + os << h.count();
  5397 + }
  5398 +#if !ONLY_C_LOCALE
  5399 + else if (modified == CharT{'O'})
  5400 + {
  5401 + const CharT f[] = {'%', modified, *fmt};
  5402 + tm.tm_hour = static_cast<int>(hms.hours().count());
  5403 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5404 + }
  5405 +#endif
  5406 + }
  5407 + modified = CharT{};
  5408 + command = nullptr;
  5409 + }
  5410 + else
  5411 + os << *fmt;
  5412 + break;
  5413 + case 'j':
  5414 + if (command)
  5415 + {
  5416 + if (modified == CharT{})
  5417 + {
  5418 + if (fds.ymd.ok() || fds.has_tod)
  5419 + {
  5420 + days doy;
  5421 + if (fds.ymd.ok())
  5422 + {
  5423 + auto ld = local_days(fds.ymd);
  5424 + auto y = fds.ymd.year();
  5425 + doy = ld - local_days(y/January/1) + days{1};
  5426 + }
  5427 + else
  5428 + {
  5429 + doy = duration_cast<days>(fds.tod.to_duration());
  5430 + }
  5431 + save_ostream<CharT, Traits> _(os);
  5432 + os.fill('0');
  5433 + os.flags(std::ios::dec | std::ios::right);
  5434 + os.width(3);
  5435 + os << doy.count();
  5436 + }
  5437 + else
  5438 + {
  5439 + os.setstate(std::ios::failbit);
  5440 + }
  5441 + }
  5442 + else
  5443 + {
  5444 + os << CharT{'%'} << modified << *fmt;
  5445 + modified = CharT{};
  5446 + }
  5447 + command = nullptr;
  5448 + }
  5449 + else
  5450 + os << *fmt;
  5451 + break;
  5452 + case 'm':
  5453 + if (command)
  5454 + {
  5455 + if (modified == CharT{'E'})
  5456 + os << CharT{'%'} << modified << *fmt;
  5457 + else
  5458 + {
  5459 + if (!fds.ymd.month().ok())
  5460 + os.setstate(std::ios::failbit);
  5461 + auto m = static_cast<unsigned>(fds.ymd.month());
  5462 +#if !ONLY_C_LOCALE
  5463 + if (modified == CharT{})
  5464 +#endif
  5465 + {
  5466 + if (m < 10)
  5467 + os << CharT{'0'};
  5468 + os << m;
  5469 + }
  5470 +#if !ONLY_C_LOCALE
  5471 + else if (modified == CharT{'O'})
  5472 + {
  5473 + const CharT f[] = {'%', modified, *fmt};
  5474 + tm.tm_mon = static_cast<int>(m-1);
  5475 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5476 + }
  5477 +#endif
  5478 + }
  5479 + modified = CharT{};
  5480 + command = nullptr;
  5481 + }
  5482 + else
  5483 + os << *fmt;
  5484 + break;
  5485 + case 'M':
  5486 + if (command)
  5487 + {
  5488 + if (modified == CharT{'E'})
  5489 + os << CharT{'%'} << modified << *fmt;
  5490 + else
  5491 + {
  5492 + if (!fds.has_tod)
  5493 + os.setstate(std::ios::failbit);
  5494 + if (insert_negative)
  5495 + {
  5496 + os << '-';
  5497 + insert_negative = false;
  5498 + }
  5499 +#if !ONLY_C_LOCALE
  5500 + if (modified == CharT{})
  5501 +#endif
  5502 + {
  5503 + if (fds.tod.minutes() < minutes{10})
  5504 + os << CharT{'0'};
  5505 + os << fds.tod.minutes().count();
  5506 + }
  5507 +#if !ONLY_C_LOCALE
  5508 + else if (modified == CharT{'O'})
  5509 + {
  5510 + const CharT f[] = {'%', modified, *fmt};
  5511 + tm.tm_min = static_cast<int>(fds.tod.minutes().count());
  5512 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5513 + }
  5514 +#endif
  5515 + }
  5516 + modified = CharT{};
  5517 + command = nullptr;
  5518 + }
  5519 + else
  5520 + os << *fmt;
  5521 + break;
  5522 + case 'n':
  5523 + if (command)
  5524 + {
  5525 + if (modified == CharT{})
  5526 + os << CharT{'\n'};
  5527 + else
  5528 + {
  5529 + os << CharT{'%'} << modified << *fmt;
  5530 + modified = CharT{};
  5531 + }
  5532 + command = nullptr;
  5533 + }
  5534 + else
  5535 + os << *fmt;
  5536 + break;
  5537 + case 'p':
  5538 + if (command)
  5539 + {
  5540 + if (modified == CharT{})
  5541 + {
  5542 + if (!fds.has_tod)
  5543 + os.setstate(std::ios::failbit);
  5544 +#if !ONLY_C_LOCALE
  5545 + const CharT f[] = {'%', *fmt};
  5546 + tm.tm_hour = static_cast<int>(fds.tod.hours().count());
  5547 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5548 +#else
  5549 + if (date::is_am(fds.tod.hours()))
  5550 + os << ampm_names().first[0];
  5551 + else
  5552 + os << ampm_names().first[1];
  5553 +#endif
  5554 + }
  5555 + else
  5556 + {
  5557 + os << CharT{'%'} << modified << *fmt;
  5558 + }
  5559 + modified = CharT{};
  5560 + command = nullptr;
  5561 + }
  5562 + else
  5563 + os << *fmt;
  5564 + break;
  5565 + case 'Q':
  5566 + case 'q':
  5567 + if (command)
  5568 + {
  5569 + if (modified == CharT{})
  5570 + {
  5571 + if (!fds.has_tod)
  5572 + os.setstate(std::ios::failbit);
  5573 + auto d = fds.tod.to_duration();
  5574 + if (*fmt == 'q')
  5575 + os << get_units<CharT>(typename decltype(d)::period::type{});
  5576 + else
  5577 + os << d.count();
  5578 + }
  5579 + else
  5580 + {
  5581 + os << CharT{'%'} << modified << *fmt;
  5582 + }
  5583 + modified = CharT{};
  5584 + command = nullptr;
  5585 + }
  5586 + else
  5587 + os << *fmt;
  5588 + break;
  5589 + case 'r':
  5590 + if (command)
  5591 + {
  5592 + if (modified == CharT{})
  5593 + {
  5594 + if (!fds.has_tod)
  5595 + os.setstate(std::ios::failbit);
  5596 +#if !ONLY_C_LOCALE
  5597 + const CharT f[] = {'%', *fmt};
  5598 + tm.tm_hour = static_cast<int>(fds.tod.hours().count());
  5599 + tm.tm_min = static_cast<int>(fds.tod.minutes().count());
  5600 + tm.tm_sec = static_cast<int>(fds.tod.seconds().count());
  5601 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5602 +#else
  5603 + hh_mm_ss<seconds> tod(duration_cast<seconds>(fds.tod.to_duration()));
  5604 + save_ostream<CharT, Traits> _(os);
  5605 + os.fill('0');
  5606 + os.width(2);
  5607 + os << date::make12(tod.hours()).count() << CharT{':'};
  5608 + os.width(2);
  5609 + os << tod.minutes().count() << CharT{':'};
  5610 + os.width(2);
  5611 + os << tod.seconds().count() << CharT{' '};
  5612 + if (date::is_am(tod.hours()))
  5613 + os << ampm_names().first[0];
  5614 + else
  5615 + os << ampm_names().first[1];
  5616 +#endif
  5617 + }
  5618 + else
  5619 + {
  5620 + os << CharT{'%'} << modified << *fmt;
  5621 + }
  5622 + modified = CharT{};
  5623 + command = nullptr;
  5624 + }
  5625 + else
  5626 + os << *fmt;
  5627 + break;
  5628 + case 'R':
  5629 + if (command)
  5630 + {
  5631 + if (modified == CharT{})
  5632 + {
  5633 + if (!fds.has_tod)
  5634 + os.setstate(std::ios::failbit);
  5635 + if (fds.tod.hours() < hours{10})
  5636 + os << CharT{'0'};
  5637 + os << fds.tod.hours().count() << CharT{':'};
  5638 + if (fds.tod.minutes() < minutes{10})
  5639 + os << CharT{'0'};
  5640 + os << fds.tod.minutes().count();
  5641 + }
  5642 + else
  5643 + {
  5644 + os << CharT{'%'} << modified << *fmt;
  5645 + modified = CharT{};
  5646 + }
  5647 + command = nullptr;
  5648 + }
  5649 + else
  5650 + os << *fmt;
  5651 + break;
  5652 + case 'S':
  5653 + if (command)
  5654 + {
  5655 + if (modified == CharT{'E'})
  5656 + os << CharT{'%'} << modified << *fmt;
  5657 + else
  5658 + {
  5659 + if (!fds.has_tod)
  5660 + os.setstate(std::ios::failbit);
  5661 + if (insert_negative)
  5662 + {
  5663 + os << '-';
  5664 + insert_negative = false;
  5665 + }
  5666 +#if !ONLY_C_LOCALE
  5667 + if (modified == CharT{})
  5668 +#endif
  5669 + {
  5670 + os << fds.tod.s_;
  5671 + }
  5672 +#if !ONLY_C_LOCALE
  5673 + else if (modified == CharT{'O'})
  5674 + {
  5675 + const CharT f[] = {'%', modified, *fmt};
  5676 + tm.tm_sec = static_cast<int>(fds.tod.s_.seconds().count());
  5677 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5678 + }
  5679 +#endif
  5680 + }
  5681 + modified = CharT{};
  5682 + command = nullptr;
  5683 + }
  5684 + else
  5685 + os << *fmt;
  5686 + break;
  5687 + case 't':
  5688 + if (command)
  5689 + {
  5690 + if (modified == CharT{})
  5691 + os << CharT{'\t'};
  5692 + else
  5693 + {
  5694 + os << CharT{'%'} << modified << *fmt;
  5695 + modified = CharT{};
  5696 + }
  5697 + command = nullptr;
  5698 + }
  5699 + else
  5700 + os << *fmt;
  5701 + break;
  5702 + case 'T':
  5703 + if (command)
  5704 + {
  5705 + if (modified == CharT{})
  5706 + {
  5707 + if (!fds.has_tod)
  5708 + os.setstate(std::ios::failbit);
  5709 + os << fds.tod;
  5710 + }
  5711 + else
  5712 + {
  5713 + os << CharT{'%'} << modified << *fmt;
  5714 + modified = CharT{};
  5715 + }
  5716 + command = nullptr;
  5717 + }
  5718 + else
  5719 + os << *fmt;
  5720 + break;
  5721 + case 'u':
  5722 + if (command)
  5723 + {
  5724 + if (modified == CharT{'E'})
  5725 + os << CharT{'%'} << modified << *fmt;
  5726 + else
  5727 + {
  5728 + auto wd = extract_weekday(os, fds);
  5729 +#if !ONLY_C_LOCALE
  5730 + if (modified == CharT{})
  5731 +#endif
  5732 + {
  5733 + os << (wd != 0 ? wd : 7u);
  5734 + }
  5735 +#if !ONLY_C_LOCALE
  5736 + else if (modified == CharT{'O'})
  5737 + {
  5738 + const CharT f[] = {'%', modified, *fmt};
  5739 + tm.tm_wday = static_cast<int>(wd);
  5740 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5741 + }
  5742 +#endif
  5743 + }
  5744 + modified = CharT{};
  5745 + command = nullptr;
  5746 + }
  5747 + else
  5748 + os << *fmt;
  5749 + break;
  5750 + case 'U':
  5751 + if (command)
  5752 + {
  5753 + if (modified == CharT{'E'})
  5754 + os << CharT{'%'} << modified << *fmt;
  5755 + else
  5756 + {
  5757 + auto const& ymd = fds.ymd;
  5758 + if (!ymd.ok())
  5759 + os.setstate(std::ios::failbit);
  5760 + auto ld = local_days(ymd);
  5761 +#if !ONLY_C_LOCALE
  5762 + if (modified == CharT{})
  5763 +#endif
  5764 + {
  5765 + auto st = local_days(Sunday[1]/January/ymd.year());
  5766 + if (ld < st)
  5767 + os << CharT{'0'} << CharT{'0'};
  5768 + else
  5769 + {
  5770 + auto wn = duration_cast<weeks>(ld - st).count() + 1;
  5771 + if (wn < 10)
  5772 + os << CharT{'0'};
  5773 + os << wn;
  5774 + }
  5775 + }
  5776 + #if !ONLY_C_LOCALE
  5777 + else if (modified == CharT{'O'})
  5778 + {
  5779 + const CharT f[] = {'%', modified, *fmt};
  5780 + tm.tm_year = static_cast<int>(ymd.year()) - 1900;
  5781 + tm.tm_wday = static_cast<int>(extract_weekday(os, fds));
  5782 + if (os.fail())
  5783 + return os;
  5784 + tm.tm_yday = static_cast<int>((ld - local_days(ymd.year()/1/1)).count());
  5785 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5786 + }
  5787 +#endif
  5788 + }
  5789 + modified = CharT{};
  5790 + command = nullptr;
  5791 + }
  5792 + else
  5793 + os << *fmt;
  5794 + break;
  5795 + case 'V':
  5796 + if (command)
  5797 + {
  5798 + if (modified == CharT{'E'})
  5799 + os << CharT{'%'} << modified << *fmt;
  5800 + else
  5801 + {
  5802 + if (!fds.ymd.ok())
  5803 + os.setstate(std::ios::failbit);
  5804 + auto ld = local_days(fds.ymd);
  5805 +#if !ONLY_C_LOCALE
  5806 + if (modified == CharT{})
  5807 +#endif
  5808 + {
  5809 + auto y = year_month_day{ld + days{3}}.year();
  5810 + auto st = local_days((y-years{1})/12/Thursday[last]) +
  5811 + (Monday-Thursday);
  5812 + if (ld < st)
  5813 + {
  5814 + --y;
  5815 + st = local_days((y - years{1})/12/Thursday[last]) +
  5816 + (Monday-Thursday);
  5817 + }
  5818 + auto wn = duration_cast<weeks>(ld - st).count() + 1;
  5819 + if (wn < 10)
  5820 + os << CharT{'0'};
  5821 + os << wn;
  5822 + }
  5823 +#if !ONLY_C_LOCALE
  5824 + else if (modified == CharT{'O'})
  5825 + {
  5826 + const CharT f[] = {'%', modified, *fmt};
  5827 + auto const& ymd = fds.ymd;
  5828 + tm.tm_year = static_cast<int>(ymd.year()) - 1900;
  5829 + tm.tm_wday = static_cast<int>(extract_weekday(os, fds));
  5830 + if (os.fail())
  5831 + return os;
  5832 + tm.tm_yday = static_cast<int>((ld - local_days(ymd.year()/1/1)).count());
  5833 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5834 + }
  5835 +#endif
  5836 + }
  5837 + modified = CharT{};
  5838 + command = nullptr;
  5839 + }
  5840 + else
  5841 + os << *fmt;
  5842 + break;
  5843 + case 'w':
  5844 + if (command)
  5845 + {
  5846 + auto wd = extract_weekday(os, fds);
  5847 + if (os.fail())
  5848 + return os;
  5849 +#if !ONLY_C_LOCALE
  5850 + if (modified == CharT{})
  5851 +#else
  5852 + if (modified != CharT{'E'})
  5853 +#endif
  5854 + {
  5855 + os << wd;
  5856 + }
  5857 +#if !ONLY_C_LOCALE
  5858 + else if (modified == CharT{'O'})
  5859 + {
  5860 + const CharT f[] = {'%', modified, *fmt};
  5861 + tm.tm_wday = static_cast<int>(wd);
  5862 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5863 + }
  5864 +#endif
  5865 + else
  5866 + {
  5867 + os << CharT{'%'} << modified << *fmt;
  5868 + }
  5869 + modified = CharT{};
  5870 + command = nullptr;
  5871 + }
  5872 + else
  5873 + os << *fmt;
  5874 + break;
  5875 + case 'W':
  5876 + if (command)
  5877 + {
  5878 + if (modified == CharT{'E'})
  5879 + os << CharT{'%'} << modified << *fmt;
  5880 + else
  5881 + {
  5882 + auto const& ymd = fds.ymd;
  5883 + if (!ymd.ok())
  5884 + os.setstate(std::ios::failbit);
  5885 + auto ld = local_days(ymd);
  5886 +#if !ONLY_C_LOCALE
  5887 + if (modified == CharT{})
  5888 +#endif
  5889 + {
  5890 + auto st = local_days(Monday[1]/January/ymd.year());
  5891 + if (ld < st)
  5892 + os << CharT{'0'} << CharT{'0'};
  5893 + else
  5894 + {
  5895 + auto wn = duration_cast<weeks>(ld - st).count() + 1;
  5896 + if (wn < 10)
  5897 + os << CharT{'0'};
  5898 + os << wn;
  5899 + }
  5900 + }
  5901 +#if !ONLY_C_LOCALE
  5902 + else if (modified == CharT{'O'})
  5903 + {
  5904 + const CharT f[] = {'%', modified, *fmt};
  5905 + tm.tm_year = static_cast<int>(ymd.year()) - 1900;
  5906 + tm.tm_wday = static_cast<int>(extract_weekday(os, fds));
  5907 + if (os.fail())
  5908 + return os;
  5909 + tm.tm_yday = static_cast<int>((ld - local_days(ymd.year()/1/1)).count());
  5910 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5911 + }
  5912 +#endif
  5913 + }
  5914 + modified = CharT{};
  5915 + command = nullptr;
  5916 + }
  5917 + else
  5918 + os << *fmt;
  5919 + break;
  5920 + case 'X':
  5921 + if (command)
  5922 + {
  5923 + if (modified == CharT{'O'})
  5924 + os << CharT{'%'} << modified << *fmt;
  5925 + else
  5926 + {
  5927 + if (!fds.has_tod)
  5928 + os.setstate(std::ios::failbit);
  5929 +#if !ONLY_C_LOCALE
  5930 + tm = std::tm{};
  5931 + tm.tm_sec = static_cast<int>(fds.tod.seconds().count());
  5932 + tm.tm_min = static_cast<int>(fds.tod.minutes().count());
  5933 + tm.tm_hour = static_cast<int>(fds.tod.hours().count());
  5934 + CharT f[3] = {'%'};
  5935 + auto fe = std::begin(f) + 1;
  5936 + if (modified == CharT{'E'})
  5937 + *fe++ = modified;
  5938 + *fe++ = *fmt;
  5939 + facet.put(os, os, os.fill(), &tm, std::begin(f), fe);
  5940 +#else
  5941 + os << fds.tod;
  5942 +#endif
  5943 + }
  5944 + command = nullptr;
  5945 + modified = CharT{};
  5946 + }
  5947 + else
  5948 + os << *fmt;
  5949 + break;
  5950 + case 'y':
  5951 + if (command)
  5952 + {
  5953 + if (!fds.ymd.year().ok())
  5954 + os.setstate(std::ios::failbit);
  5955 + auto y = static_cast<int>(fds.ymd.year());
  5956 +#if !ONLY_C_LOCALE
  5957 + if (modified == CharT{})
  5958 + {
  5959 +#endif
  5960 + y = std::abs(y) % 100;
  5961 + if (y < 10)
  5962 + os << CharT{'0'};
  5963 + os << y;
  5964 +#if !ONLY_C_LOCALE
  5965 + }
  5966 + else
  5967 + {
  5968 + const CharT f[] = {'%', modified, *fmt};
  5969 + tm.tm_year = y - 1900;
  5970 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  5971 + }
  5972 +#endif
  5973 + modified = CharT{};
  5974 + command = nullptr;
  5975 + }
  5976 + else
  5977 + os << *fmt;
  5978 + break;
  5979 + case 'Y':
  5980 + if (command)
  5981 + {
  5982 + if (modified == CharT{'O'})
  5983 + os << CharT{'%'} << modified << *fmt;
  5984 + else
  5985 + {
  5986 + if (!fds.ymd.year().ok())
  5987 + os.setstate(std::ios::failbit);
  5988 + auto y = fds.ymd.year();
  5989 +#if !ONLY_C_LOCALE
  5990 + if (modified == CharT{})
  5991 +#endif
  5992 + {
  5993 + save_ostream<CharT, Traits> _(os);
  5994 + os.imbue(std::locale::classic());
  5995 + os << y;
  5996 + }
  5997 +#if !ONLY_C_LOCALE
  5998 + else if (modified == CharT{'E'})
  5999 + {
  6000 + const CharT f[] = {'%', modified, *fmt};
  6001 + tm.tm_year = static_cast<int>(y) - 1900;
  6002 + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f));
  6003 + }
  6004 +#endif
  6005 + }
  6006 + modified = CharT{};
  6007 + command = nullptr;
  6008 + }
  6009 + else
  6010 + os << *fmt;
  6011 + break;
  6012 + case 'z':
  6013 + if (command)
  6014 + {
  6015 + if (offset_sec == nullptr)
  6016 + {
  6017 + // Can not format %z with unknown offset
  6018 + os.setstate(ios::failbit);
  6019 + return os;
  6020 + }
  6021 + auto m = duration_cast<minutes>(*offset_sec);
  6022 + auto neg = m < minutes{0};
  6023 + m = date::abs(m);
  6024 + auto h = duration_cast<hours>(m);
  6025 + m -= h;
  6026 + if (neg)
  6027 + os << CharT{'-'};
  6028 + else
  6029 + os << CharT{'+'};
  6030 + if (h < hours{10})
  6031 + os << CharT{'0'};
  6032 + os << h.count();
  6033 + if (modified != CharT{})
  6034 + os << CharT{':'};
  6035 + if (m < minutes{10})
  6036 + os << CharT{'0'};
  6037 + os << m.count();
  6038 + command = nullptr;
  6039 + modified = CharT{};
  6040 + }
  6041 + else
  6042 + os << *fmt;
  6043 + break;
  6044 + case 'Z':
  6045 + if (command)
  6046 + {
  6047 + if (modified == CharT{})
  6048 + {
  6049 + if (abbrev == nullptr)
  6050 + {
  6051 + // Can not format %Z with unknown time_zone
  6052 + os.setstate(ios::failbit);
  6053 + return os;
  6054 + }
  6055 + for (auto c : *abbrev)
  6056 + os << CharT(c);
  6057 + }
  6058 + else
  6059 + {
  6060 + os << CharT{'%'} << modified << *fmt;
  6061 + modified = CharT{};
  6062 + }
  6063 + command = nullptr;
  6064 + }
  6065 + else
  6066 + os << *fmt;
  6067 + break;
  6068 + case 'E':
  6069 + case 'O':
  6070 + if (command)
  6071 + {
  6072 + if (modified == CharT{})
  6073 + {
  6074 + modified = *fmt;
  6075 + }
  6076 + else
  6077 + {
  6078 + os << CharT{'%'} << modified << *fmt;
  6079 + command = nullptr;
  6080 + modified = CharT{};
  6081 + }
  6082 + }
  6083 + else
  6084 + os << *fmt;
  6085 + break;
  6086 + case '%':
  6087 + if (command)
  6088 + {
  6089 + if (modified == CharT{})
  6090 + {
  6091 + os << CharT{'%'};
  6092 + command = nullptr;
  6093 + }
  6094 + else
  6095 + {
  6096 + os << CharT{'%'} << modified << CharT{'%'};
  6097 + command = nullptr;
  6098 + modified = CharT{};
  6099 + }
  6100 + }
  6101 + else
  6102 + command = fmt;
  6103 + break;
  6104 + default:
  6105 + if (command)
  6106 + {
  6107 + os << CharT{'%'};
  6108 + command = nullptr;
  6109 + }
  6110 + if (modified != CharT{})
  6111 + {
  6112 + os << modified;
  6113 + modified = CharT{};
  6114 + }
  6115 + os << *fmt;
  6116 + break;
  6117 + }
  6118 + }
  6119 + if (command)
  6120 + os << CharT{'%'};
  6121 + if (modified != CharT{})
  6122 + os << modified;
  6123 + return os;
  6124 +}
  6125 +
  6126 +template <class CharT, class Traits>
  6127 +inline
  6128 +std::basic_ostream<CharT, Traits>&
  6129 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt, const year& y)
  6130 +{
  6131 + using CT = std::chrono::seconds;
  6132 + fields<CT> fds{y/0/0};
  6133 + return to_stream(os, fmt, fds);
  6134 +}
  6135 +
  6136 +template <class CharT, class Traits>
  6137 +inline
  6138 +std::basic_ostream<CharT, Traits>&
  6139 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt, const month& m)
  6140 +{
  6141 + using CT = std::chrono::seconds;
  6142 + fields<CT> fds{m/0/nanyear};
  6143 + return to_stream(os, fmt, fds);
  6144 +}
  6145 +
  6146 +template <class CharT, class Traits>
  6147 +inline
  6148 +std::basic_ostream<CharT, Traits>&
  6149 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt, const day& d)
  6150 +{
  6151 + using CT = std::chrono::seconds;
  6152 + fields<CT> fds{d/0/nanyear};
  6153 + return to_stream(os, fmt, fds);
  6154 +}
  6155 +
  6156 +template <class CharT, class Traits>
  6157 +inline
  6158 +std::basic_ostream<CharT, Traits>&
  6159 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt, const weekday& wd)
  6160 +{
  6161 + using CT = std::chrono::seconds;
  6162 + fields<CT> fds{wd};
  6163 + return to_stream(os, fmt, fds);
  6164 +}
  6165 +
  6166 +template <class CharT, class Traits>
  6167 +inline
  6168 +std::basic_ostream<CharT, Traits>&
  6169 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt, const year_month& ym)
  6170 +{
  6171 + using CT = std::chrono::seconds;
  6172 + fields<CT> fds{ym/0};
  6173 + return to_stream(os, fmt, fds);
  6174 +}
  6175 +
  6176 +template <class CharT, class Traits>
  6177 +inline
  6178 +std::basic_ostream<CharT, Traits>&
  6179 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt, const month_day& md)
  6180 +{
  6181 + using CT = std::chrono::seconds;
  6182 + fields<CT> fds{md/nanyear};
  6183 + return to_stream(os, fmt, fds);
  6184 +}
  6185 +
  6186 +template <class CharT, class Traits>
  6187 +inline
  6188 +std::basic_ostream<CharT, Traits>&
  6189 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
  6190 + const year_month_day& ymd)
  6191 +{
  6192 + using CT = std::chrono::seconds;
  6193 + fields<CT> fds{ymd};
  6194 + return to_stream(os, fmt, fds);
  6195 +}
  6196 +
  6197 +template <class CharT, class Traits, class Rep, class Period>
  6198 +inline
  6199 +std::basic_ostream<CharT, Traits>&
  6200 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
  6201 + const std::chrono::duration<Rep, Period>& d)
  6202 +{
  6203 + using Duration = std::chrono::duration<Rep, Period>;
  6204 + using CT = typename std::common_type<Duration, std::chrono::seconds>::type;
  6205 + fields<CT> fds{hh_mm_ss<CT>{d}};
  6206 + return to_stream(os, fmt, fds);
  6207 +}
  6208 +
  6209 +template <class CharT, class Traits, class Duration>
  6210 +std::basic_ostream<CharT, Traits>&
  6211 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
  6212 + const local_time<Duration>& tp, const std::string* abbrev = nullptr,
  6213 + const std::chrono::seconds* offset_sec = nullptr)
  6214 +{
  6215 + using CT = typename std::common_type<Duration, std::chrono::seconds>::type;
  6216 + auto ld = std::chrono::time_point_cast<days>(tp);
  6217 + fields<CT> fds;
  6218 + if (ld <= tp)
  6219 + fds = fields<CT>{year_month_day{ld}, hh_mm_ss<CT>{tp-local_seconds{ld}}};
  6220 + else
  6221 + fds = fields<CT>{year_month_day{ld - days{1}},
  6222 + hh_mm_ss<CT>{days{1} - (local_seconds{ld} - tp)}};
  6223 + return to_stream(os, fmt, fds, abbrev, offset_sec);
  6224 +}
  6225 +
  6226 +template <class CharT, class Traits, class Duration>
  6227 +std::basic_ostream<CharT, Traits>&
  6228 +to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
  6229 + const sys_time<Duration>& tp)
  6230 +{
  6231 + using std::chrono::seconds;
  6232 + using CT = typename std::common_type<Duration, seconds>::type;
  6233 + const std::string abbrev("UTC");
  6234 + CONSTDATA seconds offset{0};
  6235 + auto sd = std::chrono::time_point_cast<days>(tp);
  6236 + fields<CT> fds;
  6237 + if (sd <= tp)
  6238 + fds = fields<CT>{year_month_day{sd}, hh_mm_ss<CT>{tp-sys_seconds{sd}}};
  6239 + else
  6240 + fds = fields<CT>{year_month_day{sd - days{1}},
  6241 + hh_mm_ss<CT>{days{1} - (sys_seconds{sd} - tp)}};
  6242 + return to_stream(os, fmt, fds, &abbrev, &offset);
  6243 +}
  6244 +
  6245 +// format
  6246 +
  6247 +template <class CharT, class Streamable>
  6248 +auto
  6249 +format(const std::locale& loc, const CharT* fmt, const Streamable& tp)
  6250 + -> decltype(to_stream(std::declval<std::basic_ostream<CharT>&>(), fmt, tp),
  6251 + std::basic_string<CharT>{})
  6252 +{
  6253 + std::basic_ostringstream<CharT> os;
  6254 + os.exceptions(std::ios::failbit | std::ios::badbit);
  6255 + os.imbue(loc);
  6256 + to_stream(os, fmt, tp);
  6257 + return os.str();
  6258 +}
  6259 +
  6260 +template <class CharT, class Streamable>
  6261 +auto
  6262 +format(const CharT* fmt, const Streamable& tp)
  6263 + -> decltype(to_stream(std::declval<std::basic_ostream<CharT>&>(), fmt, tp),
  6264 + std::basic_string<CharT>{})
  6265 +{
  6266 + std::basic_ostringstream<CharT> os;
  6267 + os.exceptions(std::ios::failbit | std::ios::badbit);
  6268 + to_stream(os, fmt, tp);
  6269 + return os.str();
  6270 +}
  6271 +
  6272 +template <class CharT, class Traits, class Alloc, class Streamable>
  6273 +auto
  6274 +format(const std::locale& loc, const std::basic_string<CharT, Traits, Alloc>& fmt,
  6275 + const Streamable& tp)
  6276 + -> decltype(to_stream(std::declval<std::basic_ostream<CharT, Traits>&>(), fmt.c_str(), tp),
  6277 + std::basic_string<CharT, Traits, Alloc>{})
  6278 +{
  6279 + std::basic_ostringstream<CharT, Traits, Alloc> os;
  6280 + os.exceptions(std::ios::failbit | std::ios::badbit);
  6281 + os.imbue(loc);
  6282 + to_stream(os, fmt.c_str(), tp);
  6283 + return os.str();
  6284 +}
  6285 +
  6286 +template <class CharT, class Traits, class Alloc, class Streamable>
  6287 +auto
  6288 +format(const std::basic_string<CharT, Traits, Alloc>& fmt, const Streamable& tp)
  6289 + -> decltype(to_stream(std::declval<std::basic_ostream<CharT, Traits>&>(), fmt.c_str(), tp),
  6290 + std::basic_string<CharT, Traits, Alloc>{})
  6291 +{
  6292 + std::basic_ostringstream<CharT, Traits, Alloc> os;
  6293 + os.exceptions(std::ios::failbit | std::ios::badbit);
  6294 + to_stream(os, fmt.c_str(), tp);
  6295 + return os.str();
  6296 +}
  6297 +
  6298 +// parse
  6299 +
  6300 +namespace detail
  6301 +{
  6302 +
  6303 +template <class CharT, class Traits>
  6304 +bool
  6305 +read_char(std::basic_istream<CharT, Traits>& is, CharT fmt, std::ios::iostate& err)
  6306 +{
  6307 + auto ic = is.get();
  6308 + if (Traits::eq_int_type(ic, Traits::eof()) ||
  6309 + !Traits::eq(Traits::to_char_type(ic), fmt))
  6310 + {
  6311 + err |= std::ios::failbit;
  6312 + is.setstate(std::ios::failbit);
  6313 + return false;
  6314 + }
  6315 + return true;
  6316 +}
  6317 +
  6318 +template <class CharT, class Traits>
  6319 +unsigned
  6320 +read_unsigned(std::basic_istream<CharT, Traits>& is, unsigned m = 1, unsigned M = 10)
  6321 +{
  6322 + unsigned x = 0;
  6323 + unsigned count = 0;
  6324 + while (true)
  6325 + {
  6326 + auto ic = is.peek();
  6327 + if (Traits::eq_int_type(ic, Traits::eof()))
  6328 + break;
  6329 + auto c = static_cast<char>(Traits::to_char_type(ic));
  6330 + if (!('0' <= c && c <= '9'))
  6331 + break;
  6332 + (void)is.get();
  6333 + ++count;
  6334 + x = 10*x + static_cast<unsigned>(c - '0');
  6335 + if (count == M)
  6336 + break;
  6337 + }
  6338 + if (count < m)
  6339 + is.setstate(std::ios::failbit);
  6340 + return x;
  6341 +}
  6342 +
  6343 +template <class CharT, class Traits>
  6344 +int
  6345 +read_signed(std::basic_istream<CharT, Traits>& is, unsigned m = 1, unsigned M = 10)
  6346 +{
  6347 + auto ic = is.peek();
  6348 + if (!Traits::eq_int_type(ic, Traits::eof()))
  6349 + {
  6350 + auto c = static_cast<char>(Traits::to_char_type(ic));
  6351 + if (('0' <= c && c <= '9') || c == '-' || c == '+')
  6352 + {
  6353 + if (c == '-' || c == '+')
  6354 + (void)is.get();
  6355 + auto x = static_cast<int>(read_unsigned(is, std::max(m, 1u), M));
  6356 + if (!is.fail())
  6357 + {
  6358 + if (c == '-')
  6359 + x = -x;
  6360 + return x;
  6361 + }
  6362 + }
  6363 + }
  6364 + if (m > 0)
  6365 + is.setstate(std::ios::failbit);
  6366 + return 0;
  6367 +}
  6368 +
  6369 +template <class CharT, class Traits>
  6370 +long double
  6371 +read_long_double(std::basic_istream<CharT, Traits>& is, unsigned m = 1, unsigned M = 10)
  6372 +{
  6373 + unsigned count = 0;
  6374 + unsigned fcount = 0;
  6375 + unsigned long long i = 0;
  6376 + unsigned long long f = 0;
  6377 + bool parsing_fraction = false;
  6378 +#if ONLY_C_LOCALE
  6379 + typename Traits::int_type decimal_point = '.';
  6380 +#else
  6381 + auto decimal_point = Traits::to_int_type(
  6382 + std::use_facet<std::numpunct<CharT>>(is.getloc()).decimal_point());
  6383 +#endif
  6384 + while (true)
  6385 + {
  6386 + auto ic = is.peek();
  6387 + if (Traits::eq_int_type(ic, Traits::eof()))
  6388 + break;
  6389 + if (Traits::eq_int_type(ic, decimal_point))
  6390 + {
  6391 + decimal_point = Traits::eof();
  6392 + parsing_fraction = true;
  6393 + }
  6394 + else
  6395 + {
  6396 + auto c = static_cast<char>(Traits::to_char_type(ic));
  6397 + if (!('0' <= c && c <= '9'))
  6398 + break;
  6399 + if (!parsing_fraction)
  6400 + {
  6401 + i = 10*i + static_cast<unsigned>(c - '0');
  6402 + }
  6403 + else
  6404 + {
  6405 + f = 10*f + static_cast<unsigned>(c - '0');
  6406 + ++fcount;
  6407 + }
  6408 + }
  6409 + (void)is.get();
  6410 + if (++count == M)
  6411 + break;
  6412 + }
  6413 + if (count < m)
  6414 + {
  6415 + is.setstate(std::ios::failbit);
  6416 + return 0;
  6417 + }
  6418 + return static_cast<long double>(i) + static_cast<long double>(f)/std::pow(10.L, fcount);
  6419 +}
  6420 +
  6421 +struct rs
  6422 +{
  6423 + int& i;
  6424 + unsigned m;
  6425 + unsigned M;
  6426 +};
  6427 +
  6428 +struct ru
  6429 +{
  6430 + int& i;
  6431 + unsigned m;
  6432 + unsigned M;
  6433 +};
  6434 +
  6435 +struct rld
  6436 +{
  6437 + long double& i;
  6438 + unsigned m;
  6439 + unsigned M;
  6440 +};
  6441 +
  6442 +template <class CharT, class Traits>
  6443 +void
  6444 +read(std::basic_istream<CharT, Traits>&)
  6445 +{
  6446 +}
  6447 +
  6448 +template <class CharT, class Traits, class ...Args>
  6449 +void
  6450 +read(std::basic_istream<CharT, Traits>& is, CharT a0, Args&& ...args);
  6451 +
  6452 +template <class CharT, class Traits, class ...Args>
  6453 +void
  6454 +read(std::basic_istream<CharT, Traits>& is, rs a0, Args&& ...args);
  6455 +
  6456 +template <class CharT, class Traits, class ...Args>
  6457 +void
  6458 +read(std::basic_istream<CharT, Traits>& is, ru a0, Args&& ...args);
  6459 +
  6460 +template <class CharT, class Traits, class ...Args>
  6461 +void
  6462 +read(std::basic_istream<CharT, Traits>& is, int a0, Args&& ...args);
  6463 +
  6464 +template <class CharT, class Traits, class ...Args>
  6465 +void
  6466 +read(std::basic_istream<CharT, Traits>& is, rld a0, Args&& ...args);
  6467 +
  6468 +template <class CharT, class Traits, class ...Args>
  6469 +void
  6470 +read(std::basic_istream<CharT, Traits>& is, CharT a0, Args&& ...args)
  6471 +{
  6472 + // No-op if a0 == CharT{}
  6473 + if (a0 != CharT{})
  6474 + {
  6475 + auto ic = is.peek();
  6476 + if (Traits::eq_int_type(ic, Traits::eof()))
  6477 + {
  6478 + is.setstate(std::ios::failbit | std::ios::eofbit);
  6479 + return;
  6480 + }
  6481 + if (!Traits::eq(Traits::to_char_type(ic), a0))
  6482 + {
  6483 + is.setstate(std::ios::failbit);
  6484 + return;
  6485 + }
  6486 + (void)is.get();
  6487 + }
  6488 + read(is, std::forward<Args>(args)...);
  6489 +}
  6490 +
  6491 +template <class CharT, class Traits, class ...Args>
  6492 +void
  6493 +read(std::basic_istream<CharT, Traits>& is, rs a0, Args&& ...args)
  6494 +{
  6495 + auto x = read_signed(is, a0.m, a0.M);
  6496 + if (is.fail())
  6497 + return;
  6498 + a0.i = x;
  6499 + read(is, std::forward<Args>(args)...);
  6500 +}
  6501 +
  6502 +template <class CharT, class Traits, class ...Args>
  6503 +void
  6504 +read(std::basic_istream<CharT, Traits>& is, ru a0, Args&& ...args)
  6505 +{
  6506 + auto x = read_unsigned(is, a0.m, a0.M);
  6507 + if (is.fail())
  6508 + return;
  6509 + a0.i = static_cast<int>(x);
  6510 + read(is, std::forward<Args>(args)...);
  6511 +}
  6512 +
  6513 +template <class CharT, class Traits, class ...Args>
  6514 +void
  6515 +read(std::basic_istream<CharT, Traits>& is, int a0, Args&& ...args)
  6516 +{
  6517 + if (a0 != -1)
  6518 + {
  6519 + auto u = static_cast<unsigned>(a0);
  6520 + CharT buf[std::numeric_limits<unsigned>::digits10+2u] = {};
  6521 + auto e = buf;
  6522 + do
  6523 + {
  6524 + *e++ = static_cast<CharT>(CharT(u % 10) + CharT{'0'});
  6525 + u /= 10;
  6526 + } while (u > 0);
  6527 + std::reverse(buf, e);
  6528 + for (auto p = buf; p != e && is.rdstate() == std::ios::goodbit; ++p)
  6529 + read(is, *p);
  6530 + }
  6531 + if (is.rdstate() == std::ios::goodbit)
  6532 + read(is, std::forward<Args>(args)...);
  6533 +}
  6534 +
  6535 +template <class CharT, class Traits, class ...Args>
  6536 +void
  6537 +read(std::basic_istream<CharT, Traits>& is, rld a0, Args&& ...args)
  6538 +{
  6539 + auto x = read_long_double(is, a0.m, a0.M);
  6540 + if (is.fail())
  6541 + return;
  6542 + a0.i = x;
  6543 + read(is, std::forward<Args>(args)...);
  6544 +}
  6545 +
  6546 +template <class T, class CharT, class Traits>
  6547 +inline
  6548 +void
  6549 +checked_set(T& value, T from, T not_a_value, std::basic_ios<CharT, Traits>& is)
  6550 +{
  6551 + if (!is.fail())
  6552 + {
  6553 + if (value == not_a_value)
  6554 + value = std::move(from);
  6555 + else if (value != from)
  6556 + is.setstate(std::ios::failbit);
  6557 + }
  6558 +}
  6559 +
  6560 +} // namespace detail;
  6561 +
  6562 +template <class CharT, class Traits, class Duration, class Alloc = std::allocator<CharT>>
  6563 +std::basic_istream<CharT, Traits>&
  6564 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,
  6565 + fields<Duration>& fds, std::basic_string<CharT, Traits, Alloc>* abbrev,
  6566 + std::chrono::minutes* offset)
  6567 +{
  6568 + using std::numeric_limits;
  6569 + using std::ios;
  6570 + using std::chrono::duration;
  6571 + using std::chrono::duration_cast;
  6572 + using std::chrono::seconds;
  6573 + using std::chrono::minutes;
  6574 + using std::chrono::hours;
  6575 + using detail::round_i;
  6576 + typename std::basic_istream<CharT, Traits>::sentry ok{is, true};
  6577 + if (ok)
  6578 + {
  6579 + date::detail::save_istream<CharT, Traits> ss(is);
  6580 + is.fill(' ');
  6581 + is.flags(std::ios::skipws | std::ios::dec);
  6582 + is.width(0);
  6583 +#if !ONLY_C_LOCALE
  6584 + auto& f = std::use_facet<std::time_get<CharT>>(is.getloc());
  6585 + std::tm tm{};
  6586 +#endif
  6587 + const CharT* command = nullptr;
  6588 + auto modified = CharT{};
  6589 + auto width = -1;
  6590 +
  6591 + CONSTDATA int not_a_year = numeric_limits<short>::min();
  6592 + CONSTDATA int not_a_2digit_year = 100;
  6593 + CONSTDATA int not_a_century = not_a_year / 100;
  6594 + CONSTDATA int not_a_month = 0;
  6595 + CONSTDATA int not_a_day = 0;
  6596 + CONSTDATA int not_a_hour = numeric_limits<int>::min();
  6597 + CONSTDATA int not_a_hour_12_value = 0;
  6598 + CONSTDATA int not_a_minute = not_a_hour;
  6599 + CONSTDATA Duration not_a_second = Duration::min();
  6600 + CONSTDATA int not_a_doy = -1;
  6601 + CONSTDATA int not_a_weekday = 8;
  6602 + CONSTDATA int not_a_week_num = 100;
  6603 + CONSTDATA int not_a_ampm = -1;
  6604 + CONSTDATA minutes not_a_offset = minutes::min();
  6605 +
  6606 + int Y = not_a_year; // c, F, Y *
  6607 + int y = not_a_2digit_year; // D, x, y *
  6608 + int g = not_a_2digit_year; // g *
  6609 + int G = not_a_year; // G *
  6610 + int C = not_a_century; // C *
  6611 + int m = not_a_month; // b, B, h, m, c, D, F, x *
  6612 + int d = not_a_day; // c, d, D, e, F, x *
  6613 + int j = not_a_doy; // j *
  6614 + int wd = not_a_weekday; // a, A, u, w *
  6615 + int H = not_a_hour; // c, H, R, T, X *
  6616 + int I = not_a_hour_12_value; // I, r *
  6617 + int p = not_a_ampm; // p, r *
  6618 + int M = not_a_minute; // c, M, r, R, T, X *
  6619 + Duration s = not_a_second; // c, r, S, T, X *
  6620 + int U = not_a_week_num; // U *
  6621 + int V = not_a_week_num; // V *
  6622 + int W = not_a_week_num; // W *
  6623 + std::basic_string<CharT, Traits, Alloc> temp_abbrev; // Z *
  6624 + minutes temp_offset = not_a_offset; // z *
  6625 +
  6626 + using detail::read;
  6627 + using detail::rs;
  6628 + using detail::ru;
  6629 + using detail::rld;
  6630 + using detail::checked_set;
  6631 + for (; *fmt != CharT{} && !is.fail(); ++fmt)
  6632 + {
  6633 + switch (*fmt)
  6634 + {
  6635 + case 'a':
  6636 + case 'A':
  6637 + case 'u':
  6638 + case 'w': // wd: a, A, u, w
  6639 + if (command)
  6640 + {
  6641 + int trial_wd = not_a_weekday;
  6642 + if (*fmt == 'a' || *fmt == 'A')
  6643 + {
  6644 + if (modified == CharT{})
  6645 + {
  6646 +#if !ONLY_C_LOCALE
  6647 + ios::iostate err = ios::goodbit;
  6648 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6649 + is.setstate(err);
  6650 + if (!is.fail())
  6651 + trial_wd = tm.tm_wday;
  6652 +#else
  6653 + auto nm = detail::weekday_names();
  6654 + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first;
  6655 + if (!is.fail())
  6656 + trial_wd = i % 7;
  6657 +#endif
  6658 + }
  6659 + else
  6660 + read(is, CharT{'%'}, width, modified, *fmt);
  6661 + }
  6662 + else // *fmt == 'u' || *fmt == 'w'
  6663 + {
  6664 +#if !ONLY_C_LOCALE
  6665 + if (modified == CharT{})
  6666 +#else
  6667 + if (modified != CharT{'E'})
  6668 +#endif
  6669 + {
  6670 + read(is, ru{trial_wd, 1, width == -1 ?
  6671 + 1u : static_cast<unsigned>(width)});
  6672 + if (!is.fail())
  6673 + {
  6674 + if (*fmt == 'u')
  6675 + {
  6676 + if (!(1 <= trial_wd && trial_wd <= 7))
  6677 + {
  6678 + trial_wd = not_a_weekday;
  6679 + is.setstate(ios::failbit);
  6680 + }
  6681 + else if (trial_wd == 7)
  6682 + trial_wd = 0;
  6683 + }
  6684 + else // *fmt == 'w'
  6685 + {
  6686 + if (!(0 <= trial_wd && trial_wd <= 6))
  6687 + {
  6688 + trial_wd = not_a_weekday;
  6689 + is.setstate(ios::failbit);
  6690 + }
  6691 + }
  6692 + }
  6693 + }
  6694 +#if !ONLY_C_LOCALE
  6695 + else if (modified == CharT{'O'})
  6696 + {
  6697 + ios::iostate err = ios::goodbit;
  6698 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6699 + is.setstate(err);
  6700 + if (!is.fail())
  6701 + trial_wd = tm.tm_wday;
  6702 + }
  6703 +#endif
  6704 + else
  6705 + read(is, CharT{'%'}, width, modified, *fmt);
  6706 + }
  6707 + if (trial_wd != not_a_weekday)
  6708 + checked_set(wd, trial_wd, not_a_weekday, is);
  6709 + }
  6710 + else // !command
  6711 + read(is, *fmt);
  6712 + command = nullptr;
  6713 + width = -1;
  6714 + modified = CharT{};
  6715 + break;
  6716 + case 'b':
  6717 + case 'B':
  6718 + case 'h':
  6719 + if (command)
  6720 + {
  6721 + if (modified == CharT{})
  6722 + {
  6723 + int ttm = not_a_month;
  6724 +#if !ONLY_C_LOCALE
  6725 + ios::iostate err = ios::goodbit;
  6726 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6727 + if ((err & ios::failbit) == 0)
  6728 + ttm = tm.tm_mon + 1;
  6729 + is.setstate(err);
  6730 +#else
  6731 + auto nm = detail::month_names();
  6732 + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first;
  6733 + if (!is.fail())
  6734 + ttm = i % 12 + 1;
  6735 +#endif
  6736 + checked_set(m, ttm, not_a_month, is);
  6737 + }
  6738 + else
  6739 + read(is, CharT{'%'}, width, modified, *fmt);
  6740 + command = nullptr;
  6741 + width = -1;
  6742 + modified = CharT{};
  6743 + }
  6744 + else
  6745 + read(is, *fmt);
  6746 + break;
  6747 + case 'c':
  6748 + if (command)
  6749 + {
  6750 + if (modified != CharT{'O'})
  6751 + {
  6752 +#if !ONLY_C_LOCALE
  6753 + ios::iostate err = ios::goodbit;
  6754 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6755 + if ((err & ios::failbit) == 0)
  6756 + {
  6757 + checked_set(Y, tm.tm_year + 1900, not_a_year, is);
  6758 + checked_set(m, tm.tm_mon + 1, not_a_month, is);
  6759 + checked_set(d, tm.tm_mday, not_a_day, is);
  6760 + checked_set(H, tm.tm_hour, not_a_hour, is);
  6761 + checked_set(M, tm.tm_min, not_a_minute, is);
  6762 + checked_set(s, duration_cast<Duration>(seconds{tm.tm_sec}),
  6763 + not_a_second, is);
  6764 + }
  6765 + is.setstate(err);
  6766 +#else
  6767 + // "%a %b %e %T %Y"
  6768 + auto nm = detail::weekday_names();
  6769 + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first;
  6770 + checked_set(wd, static_cast<int>(i % 7), not_a_weekday, is);
  6771 + ws(is);
  6772 + nm = detail::month_names();
  6773 + i = detail::scan_keyword(is, nm.first, nm.second) - nm.first;
  6774 + checked_set(m, static_cast<int>(i % 12 + 1), not_a_month, is);
  6775 + ws(is);
  6776 + int td = not_a_day;
  6777 + read(is, rs{td, 1, 2});
  6778 + checked_set(d, td, not_a_day, is);
  6779 + ws(is);
  6780 + using dfs = detail::decimal_format_seconds<Duration>;
  6781 + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width;
  6782 + int tH;
  6783 + int tM;
  6784 + long double S{};
  6785 + read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2},
  6786 + CharT{':'}, rld{S, 1, w});
  6787 + checked_set(H, tH, not_a_hour, is);
  6788 + checked_set(M, tM, not_a_minute, is);
  6789 + checked_set(s, round_i<Duration>(duration<long double>{S}),
  6790 + not_a_second, is);
  6791 + ws(is);
  6792 + int tY = not_a_year;
  6793 + read(is, rs{tY, 1, 4u});
  6794 + checked_set(Y, tY, not_a_year, is);
  6795 +#endif
  6796 + }
  6797 + else
  6798 + read(is, CharT{'%'}, width, modified, *fmt);
  6799 + command = nullptr;
  6800 + width = -1;
  6801 + modified = CharT{};
  6802 + }
  6803 + else
  6804 + read(is, *fmt);
  6805 + break;
  6806 + case 'x':
  6807 + if (command)
  6808 + {
  6809 + if (modified != CharT{'O'})
  6810 + {
  6811 +#if !ONLY_C_LOCALE
  6812 + ios::iostate err = ios::goodbit;
  6813 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6814 + if ((err & ios::failbit) == 0)
  6815 + {
  6816 + checked_set(Y, tm.tm_year + 1900, not_a_year, is);
  6817 + checked_set(m, tm.tm_mon + 1, not_a_month, is);
  6818 + checked_set(d, tm.tm_mday, not_a_day, is);
  6819 + }
  6820 + is.setstate(err);
  6821 +#else
  6822 + // "%m/%d/%y"
  6823 + int ty = not_a_2digit_year;
  6824 + int tm = not_a_month;
  6825 + int td = not_a_day;
  6826 + read(is, ru{tm, 1, 2}, CharT{'/'}, ru{td, 1, 2}, CharT{'/'},
  6827 + rs{ty, 1, 2});
  6828 + checked_set(y, ty, not_a_2digit_year, is);
  6829 + checked_set(m, tm, not_a_month, is);
  6830 + checked_set(d, td, not_a_day, is);
  6831 +#endif
  6832 + }
  6833 + else
  6834 + read(is, CharT{'%'}, width, modified, *fmt);
  6835 + command = nullptr;
  6836 + width = -1;
  6837 + modified = CharT{};
  6838 + }
  6839 + else
  6840 + read(is, *fmt);
  6841 + break;
  6842 + case 'X':
  6843 + if (command)
  6844 + {
  6845 + if (modified != CharT{'O'})
  6846 + {
  6847 +#if !ONLY_C_LOCALE
  6848 + ios::iostate err = ios::goodbit;
  6849 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6850 + if ((err & ios::failbit) == 0)
  6851 + {
  6852 + checked_set(H, tm.tm_hour, not_a_hour, is);
  6853 + checked_set(M, tm.tm_min, not_a_minute, is);
  6854 + checked_set(s, duration_cast<Duration>(seconds{tm.tm_sec}),
  6855 + not_a_second, is);
  6856 + }
  6857 + is.setstate(err);
  6858 +#else
  6859 + // "%T"
  6860 + using dfs = detail::decimal_format_seconds<Duration>;
  6861 + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width;
  6862 + int tH = not_a_hour;
  6863 + int tM = not_a_minute;
  6864 + long double S{};
  6865 + read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2},
  6866 + CharT{':'}, rld{S, 1, w});
  6867 + checked_set(H, tH, not_a_hour, is);
  6868 + checked_set(M, tM, not_a_minute, is);
  6869 + checked_set(s, round_i<Duration>(duration<long double>{S}),
  6870 + not_a_second, is);
  6871 +#endif
  6872 + }
  6873 + else
  6874 + read(is, CharT{'%'}, width, modified, *fmt);
  6875 + command = nullptr;
  6876 + width = -1;
  6877 + modified = CharT{};
  6878 + }
  6879 + else
  6880 + read(is, *fmt);
  6881 + break;
  6882 + case 'C':
  6883 + if (command)
  6884 + {
  6885 + int tC = not_a_century;
  6886 +#if !ONLY_C_LOCALE
  6887 + if (modified == CharT{})
  6888 + {
  6889 +#endif
  6890 + read(is, rs{tC, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  6891 +#if !ONLY_C_LOCALE
  6892 + }
  6893 + else
  6894 + {
  6895 + ios::iostate err = ios::goodbit;
  6896 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6897 + if ((err & ios::failbit) == 0)
  6898 + {
  6899 + auto tY = tm.tm_year + 1900;
  6900 + tC = (tY >= 0 ? tY : tY-99) / 100;
  6901 + }
  6902 + is.setstate(err);
  6903 + }
  6904 +#endif
  6905 + checked_set(C, tC, not_a_century, is);
  6906 + command = nullptr;
  6907 + width = -1;
  6908 + modified = CharT{};
  6909 + }
  6910 + else
  6911 + read(is, *fmt);
  6912 + break;
  6913 + case 'D':
  6914 + if (command)
  6915 + {
  6916 + if (modified == CharT{})
  6917 + {
  6918 + int tn = not_a_month;
  6919 + int td = not_a_day;
  6920 + int ty = not_a_2digit_year;
  6921 + read(is, ru{tn, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'},
  6922 + ru{td, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'},
  6923 + rs{ty, 1, 2});
  6924 + checked_set(y, ty, not_a_2digit_year, is);
  6925 + checked_set(m, tn, not_a_month, is);
  6926 + checked_set(d, td, not_a_day, is);
  6927 + }
  6928 + else
  6929 + read(is, CharT{'%'}, width, modified, *fmt);
  6930 + command = nullptr;
  6931 + width = -1;
  6932 + modified = CharT{};
  6933 + }
  6934 + else
  6935 + read(is, *fmt);
  6936 + break;
  6937 + case 'F':
  6938 + if (command)
  6939 + {
  6940 + if (modified == CharT{})
  6941 + {
  6942 + int tY = not_a_year;
  6943 + int tn = not_a_month;
  6944 + int td = not_a_day;
  6945 + read(is, rs{tY, 1, width == -1 ? 4u : static_cast<unsigned>(width)},
  6946 + CharT{'-'}, ru{tn, 1, 2}, CharT{'-'}, ru{td, 1, 2});
  6947 + checked_set(Y, tY, not_a_year, is);
  6948 + checked_set(m, tn, not_a_month, is);
  6949 + checked_set(d, td, not_a_day, is);
  6950 + }
  6951 + else
  6952 + read(is, CharT{'%'}, width, modified, *fmt);
  6953 + command = nullptr;
  6954 + width = -1;
  6955 + modified = CharT{};
  6956 + }
  6957 + else
  6958 + read(is, *fmt);
  6959 + break;
  6960 + case 'd':
  6961 + case 'e':
  6962 + if (command)
  6963 + {
  6964 +#if !ONLY_C_LOCALE
  6965 + if (modified == CharT{})
  6966 +#else
  6967 + if (modified != CharT{'E'})
  6968 +#endif
  6969 + {
  6970 + int td = not_a_day;
  6971 + read(is, rs{td, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  6972 + checked_set(d, td, not_a_day, is);
  6973 + }
  6974 +#if !ONLY_C_LOCALE
  6975 + else if (modified == CharT{'O'})
  6976 + {
  6977 + ios::iostate err = ios::goodbit;
  6978 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  6979 + command = nullptr;
  6980 + width = -1;
  6981 + modified = CharT{};
  6982 + if ((err & ios::failbit) == 0)
  6983 + checked_set(d, tm.tm_mday, not_a_day, is);
  6984 + is.setstate(err);
  6985 + }
  6986 +#endif
  6987 + else
  6988 + read(is, CharT{'%'}, width, modified, *fmt);
  6989 + command = nullptr;
  6990 + width = -1;
  6991 + modified = CharT{};
  6992 + }
  6993 + else
  6994 + read(is, *fmt);
  6995 + break;
  6996 + case 'H':
  6997 + if (command)
  6998 + {
  6999 +#if !ONLY_C_LOCALE
  7000 + if (modified == CharT{})
  7001 +#else
  7002 + if (modified != CharT{'E'})
  7003 +#endif
  7004 + {
  7005 + int tH = not_a_hour;
  7006 + read(is, ru{tH, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7007 + checked_set(H, tH, not_a_hour, is);
  7008 + }
  7009 +#if !ONLY_C_LOCALE
  7010 + else if (modified == CharT{'O'})
  7011 + {
  7012 + ios::iostate err = ios::goodbit;
  7013 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7014 + if ((err & ios::failbit) == 0)
  7015 + checked_set(H, tm.tm_hour, not_a_hour, is);
  7016 + is.setstate(err);
  7017 + }
  7018 +#endif
  7019 + else
  7020 + read(is, CharT{'%'}, width, modified, *fmt);
  7021 + command = nullptr;
  7022 + width = -1;
  7023 + modified = CharT{};
  7024 + }
  7025 + else
  7026 + read(is, *fmt);
  7027 + break;
  7028 + case 'I':
  7029 + if (command)
  7030 + {
  7031 + if (modified == CharT{})
  7032 + {
  7033 + int tI = not_a_hour_12_value;
  7034 + // reads in an hour into I, but most be in [1, 12]
  7035 + read(is, rs{tI, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7036 + if (!(1 <= tI && tI <= 12))
  7037 + is.setstate(ios::failbit);
  7038 + checked_set(I, tI, not_a_hour_12_value, is);
  7039 + }
  7040 + else
  7041 + read(is, CharT{'%'}, width, modified, *fmt);
  7042 + command = nullptr;
  7043 + width = -1;
  7044 + modified = CharT{};
  7045 + }
  7046 + else
  7047 + read(is, *fmt);
  7048 + break;
  7049 + case 'j':
  7050 + if (command)
  7051 + {
  7052 + if (modified == CharT{})
  7053 + {
  7054 + int tj = not_a_doy;
  7055 + read(is, ru{tj, 1, width == -1 ? 3u : static_cast<unsigned>(width)});
  7056 + checked_set(j, tj, not_a_doy, is);
  7057 + }
  7058 + else
  7059 + read(is, CharT{'%'}, width, modified, *fmt);
  7060 + command = nullptr;
  7061 + width = -1;
  7062 + modified = CharT{};
  7063 + }
  7064 + else
  7065 + read(is, *fmt);
  7066 + break;
  7067 + case 'M':
  7068 + if (command)
  7069 + {
  7070 +#if !ONLY_C_LOCALE
  7071 + if (modified == CharT{})
  7072 +#else
  7073 + if (modified != CharT{'E'})
  7074 +#endif
  7075 + {
  7076 + int tM = not_a_minute;
  7077 + read(is, ru{tM, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7078 + checked_set(M, tM, not_a_minute, is);
  7079 + }
  7080 +#if !ONLY_C_LOCALE
  7081 + else if (modified == CharT{'O'})
  7082 + {
  7083 + ios::iostate err = ios::goodbit;
  7084 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7085 + if ((err & ios::failbit) == 0)
  7086 + checked_set(M, tm.tm_min, not_a_minute, is);
  7087 + is.setstate(err);
  7088 + }
  7089 +#endif
  7090 + else
  7091 + read(is, CharT{'%'}, width, modified, *fmt);
  7092 + command = nullptr;
  7093 + width = -1;
  7094 + modified = CharT{};
  7095 + }
  7096 + else
  7097 + read(is, *fmt);
  7098 + break;
  7099 + case 'm':
  7100 + if (command)
  7101 + {
  7102 +#if !ONLY_C_LOCALE
  7103 + if (modified == CharT{})
  7104 +#else
  7105 + if (modified != CharT{'E'})
  7106 +#endif
  7107 + {
  7108 + int tn = not_a_month;
  7109 + read(is, rs{tn, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7110 + checked_set(m, tn, not_a_month, is);
  7111 + }
  7112 +#if !ONLY_C_LOCALE
  7113 + else if (modified == CharT{'O'})
  7114 + {
  7115 + ios::iostate err = ios::goodbit;
  7116 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7117 + if ((err & ios::failbit) == 0)
  7118 + checked_set(m, tm.tm_mon + 1, not_a_month, is);
  7119 + is.setstate(err);
  7120 + }
  7121 +#endif
  7122 + else
  7123 + read(is, CharT{'%'}, width, modified, *fmt);
  7124 + command = nullptr;
  7125 + width = -1;
  7126 + modified = CharT{};
  7127 + }
  7128 + else
  7129 + read(is, *fmt);
  7130 + break;
  7131 + case 'n':
  7132 + case 't':
  7133 + if (command)
  7134 + {
  7135 + if (modified == CharT{})
  7136 + {
  7137 + // %n matches a single white space character
  7138 + // %t matches 0 or 1 white space characters
  7139 + auto ic = is.peek();
  7140 + if (Traits::eq_int_type(ic, Traits::eof()))
  7141 + {
  7142 + ios::iostate err = ios::eofbit;
  7143 + if (*fmt == 'n')
  7144 + err |= ios::failbit;
  7145 + is.setstate(err);
  7146 + break;
  7147 + }
  7148 + if (isspace(ic))
  7149 + {
  7150 + (void)is.get();
  7151 + }
  7152 + else if (*fmt == 'n')
  7153 + is.setstate(ios::failbit);
  7154 + }
  7155 + else
  7156 + read(is, CharT{'%'}, width, modified, *fmt);
  7157 + command = nullptr;
  7158 + width = -1;
  7159 + modified = CharT{};
  7160 + }
  7161 + else
  7162 + read(is, *fmt);
  7163 + break;
  7164 + case 'p':
  7165 + if (command)
  7166 + {
  7167 + if (modified == CharT{})
  7168 + {
  7169 + int tp = not_a_ampm;
  7170 +#if !ONLY_C_LOCALE
  7171 + tm = std::tm{};
  7172 + tm.tm_hour = 1;
  7173 + ios::iostate err = ios::goodbit;
  7174 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7175 + is.setstate(err);
  7176 + if (tm.tm_hour == 1)
  7177 + tp = 0;
  7178 + else if (tm.tm_hour == 13)
  7179 + tp = 1;
  7180 + else
  7181 + is.setstate(err);
  7182 +#else
  7183 + auto nm = detail::ampm_names();
  7184 + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first;
  7185 + tp = static_cast<decltype(tp)>(i);
  7186 +#endif
  7187 + checked_set(p, tp, not_a_ampm, is);
  7188 + }
  7189 + else
  7190 + read(is, CharT{'%'}, width, modified, *fmt);
  7191 + command = nullptr;
  7192 + width = -1;
  7193 + modified = CharT{};
  7194 + }
  7195 + else
  7196 + read(is, *fmt);
  7197 +
  7198 + break;
  7199 + case 'r':
  7200 + if (command)
  7201 + {
  7202 + if (modified == CharT{})
  7203 + {
  7204 +#if !ONLY_C_LOCALE
  7205 + ios::iostate err = ios::goodbit;
  7206 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7207 + if ((err & ios::failbit) == 0)
  7208 + {
  7209 + checked_set(H, tm.tm_hour, not_a_hour, is);
  7210 + checked_set(M, tm.tm_min, not_a_hour, is);
  7211 + checked_set(s, duration_cast<Duration>(seconds{tm.tm_sec}),
  7212 + not_a_second, is);
  7213 + }
  7214 + is.setstate(err);
  7215 +#else
  7216 + // "%I:%M:%S %p"
  7217 + using dfs = detail::decimal_format_seconds<Duration>;
  7218 + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width;
  7219 + long double S{};
  7220 + int tI = not_a_hour_12_value;
  7221 + int tM = not_a_minute;
  7222 + read(is, ru{tI, 1, 2}, CharT{':'}, ru{tM, 1, 2},
  7223 + CharT{':'}, rld{S, 1, w});
  7224 + checked_set(I, tI, not_a_hour_12_value, is);
  7225 + checked_set(M, tM, not_a_minute, is);
  7226 + checked_set(s, round_i<Duration>(duration<long double>{S}),
  7227 + not_a_second, is);
  7228 + ws(is);
  7229 + auto nm = detail::ampm_names();
  7230 + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first;
  7231 + checked_set(p, static_cast<int>(i), not_a_ampm, is);
  7232 +#endif
  7233 + }
  7234 + else
  7235 + read(is, CharT{'%'}, width, modified, *fmt);
  7236 + command = nullptr;
  7237 + width = -1;
  7238 + modified = CharT{};
  7239 + }
  7240 + else
  7241 + read(is, *fmt);
  7242 + break;
  7243 + case 'R':
  7244 + if (command)
  7245 + {
  7246 + if (modified == CharT{})
  7247 + {
  7248 + int tH = not_a_hour;
  7249 + int tM = not_a_minute;
  7250 + read(is, ru{tH, 1, 2}, CharT{'\0'}, CharT{':'}, CharT{'\0'},
  7251 + ru{tM, 1, 2}, CharT{'\0'});
  7252 + checked_set(H, tH, not_a_hour, is);
  7253 + checked_set(M, tM, not_a_minute, is);
  7254 + }
  7255 + else
  7256 + read(is, CharT{'%'}, width, modified, *fmt);
  7257 + command = nullptr;
  7258 + width = -1;
  7259 + modified = CharT{};
  7260 + }
  7261 + else
  7262 + read(is, *fmt);
  7263 + break;
  7264 + case 'S':
  7265 + if (command)
  7266 + {
  7267 + #if !ONLY_C_LOCALE
  7268 + if (modified == CharT{})
  7269 +#else
  7270 + if (modified != CharT{'E'})
  7271 +#endif
  7272 + {
  7273 + using dfs = detail::decimal_format_seconds<Duration>;
  7274 + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width;
  7275 + long double S{};
  7276 + read(is, rld{S, 1, width == -1 ? w : static_cast<unsigned>(width)});
  7277 + checked_set(s, round_i<Duration>(duration<long double>{S}),
  7278 + not_a_second, is);
  7279 + }
  7280 +#if !ONLY_C_LOCALE
  7281 + else if (modified == CharT{'O'})
  7282 + {
  7283 + ios::iostate err = ios::goodbit;
  7284 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7285 + if ((err & ios::failbit) == 0)
  7286 + checked_set(s, duration_cast<Duration>(seconds{tm.tm_sec}),
  7287 + not_a_second, is);
  7288 + is.setstate(err);
  7289 + }
  7290 +#endif
  7291 + else
  7292 + read(is, CharT{'%'}, width, modified, *fmt);
  7293 + command = nullptr;
  7294 + width = -1;
  7295 + modified = CharT{};
  7296 + }
  7297 + else
  7298 + read(is, *fmt);
  7299 + break;
  7300 + case 'T':
  7301 + if (command)
  7302 + {
  7303 + if (modified == CharT{})
  7304 + {
  7305 + using dfs = detail::decimal_format_seconds<Duration>;
  7306 + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width;
  7307 + int tH = not_a_hour;
  7308 + int tM = not_a_minute;
  7309 + long double S{};
  7310 + read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2},
  7311 + CharT{':'}, rld{S, 1, w});
  7312 + checked_set(H, tH, not_a_hour, is);
  7313 + checked_set(M, tM, not_a_minute, is);
  7314 + checked_set(s, round_i<Duration>(duration<long double>{S}),
  7315 + not_a_second, is);
  7316 + }
  7317 + else
  7318 + read(is, CharT{'%'}, width, modified, *fmt);
  7319 + command = nullptr;
  7320 + width = -1;
  7321 + modified = CharT{};
  7322 + }
  7323 + else
  7324 + read(is, *fmt);
  7325 + break;
  7326 + case 'Y':
  7327 + if (command)
  7328 + {
  7329 +#if !ONLY_C_LOCALE
  7330 + if (modified == CharT{})
  7331 +#else
  7332 + if (modified != CharT{'O'})
  7333 +#endif
  7334 + {
  7335 + int tY = not_a_year;
  7336 + read(is, rs{tY, 1, width == -1 ? 4u : static_cast<unsigned>(width)});
  7337 + checked_set(Y, tY, not_a_year, is);
  7338 + }
  7339 +#if !ONLY_C_LOCALE
  7340 + else if (modified == CharT{'E'})
  7341 + {
  7342 + ios::iostate err = ios::goodbit;
  7343 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7344 + if ((err & ios::failbit) == 0)
  7345 + checked_set(Y, tm.tm_year + 1900, not_a_year, is);
  7346 + is.setstate(err);
  7347 + }
  7348 +#endif
  7349 + else
  7350 + read(is, CharT{'%'}, width, modified, *fmt);
  7351 + command = nullptr;
  7352 + width = -1;
  7353 + modified = CharT{};
  7354 + }
  7355 + else
  7356 + read(is, *fmt);
  7357 + break;
  7358 + case 'y':
  7359 + if (command)
  7360 + {
  7361 +#if !ONLY_C_LOCALE
  7362 + if (modified == CharT{})
  7363 +#endif
  7364 + {
  7365 + int ty = not_a_2digit_year;
  7366 + read(is, ru{ty, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7367 + checked_set(y, ty, not_a_2digit_year, is);
  7368 + }
  7369 +#if !ONLY_C_LOCALE
  7370 + else
  7371 + {
  7372 + ios::iostate err = ios::goodbit;
  7373 + f.get(is, nullptr, is, err, &tm, command, fmt+1);
  7374 + if ((err & ios::failbit) == 0)
  7375 + checked_set(Y, tm.tm_year + 1900, not_a_year, is);
  7376 + is.setstate(err);
  7377 + }
  7378 +#endif
  7379 + command = nullptr;
  7380 + width = -1;
  7381 + modified = CharT{};
  7382 + }
  7383 + else
  7384 + read(is, *fmt);
  7385 + break;
  7386 + case 'g':
  7387 + if (command)
  7388 + {
  7389 + if (modified == CharT{})
  7390 + {
  7391 + int tg = not_a_2digit_year;
  7392 + read(is, ru{tg, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7393 + checked_set(g, tg, not_a_2digit_year, is);
  7394 + }
  7395 + else
  7396 + read(is, CharT{'%'}, width, modified, *fmt);
  7397 + command = nullptr;
  7398 + width = -1;
  7399 + modified = CharT{};
  7400 + }
  7401 + else
  7402 + read(is, *fmt);
  7403 + break;
  7404 + case 'G':
  7405 + if (command)
  7406 + {
  7407 + if (modified == CharT{})
  7408 + {
  7409 + int tG = not_a_year;
  7410 + read(is, rs{tG, 1, width == -1 ? 4u : static_cast<unsigned>(width)});
  7411 + checked_set(G, tG, not_a_year, is);
  7412 + }
  7413 + else
  7414 + read(is, CharT{'%'}, width, modified, *fmt);
  7415 + command = nullptr;
  7416 + width = -1;
  7417 + modified = CharT{};
  7418 + }
  7419 + else
  7420 + read(is, *fmt);
  7421 + break;
  7422 + case 'U':
  7423 + if (command)
  7424 + {
  7425 + if (modified == CharT{})
  7426 + {
  7427 + int tU = not_a_week_num;
  7428 + read(is, ru{tU, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7429 + checked_set(U, tU, not_a_week_num, is);
  7430 + }
  7431 + else
  7432 + read(is, CharT{'%'}, width, modified, *fmt);
  7433 + command = nullptr;
  7434 + width = -1;
  7435 + modified = CharT{};
  7436 + }
  7437 + else
  7438 + read(is, *fmt);
  7439 + break;
  7440 + case 'V':
  7441 + if (command)
  7442 + {
  7443 + if (modified == CharT{})
  7444 + {
  7445 + int tV = not_a_week_num;
  7446 + read(is, ru{tV, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7447 + checked_set(V, tV, not_a_week_num, is);
  7448 + }
  7449 + else
  7450 + read(is, CharT{'%'}, width, modified, *fmt);
  7451 + command = nullptr;
  7452 + width = -1;
  7453 + modified = CharT{};
  7454 + }
  7455 + else
  7456 + read(is, *fmt);
  7457 + break;
  7458 + case 'W':
  7459 + if (command)
  7460 + {
  7461 + if (modified == CharT{})
  7462 + {
  7463 + int tW = not_a_week_num;
  7464 + read(is, ru{tW, 1, width == -1 ? 2u : static_cast<unsigned>(width)});
  7465 + checked_set(W, tW, not_a_week_num, is);
  7466 + }
  7467 + else
  7468 + read(is, CharT{'%'}, width, modified, *fmt);
  7469 + command = nullptr;
  7470 + width = -1;
  7471 + modified = CharT{};
  7472 + }
  7473 + else
  7474 + read(is, *fmt);
  7475 + break;
  7476 + case 'E':
  7477 + case 'O':
  7478 + if (command)
  7479 + {
  7480 + if (modified == CharT{})
  7481 + {
  7482 + modified = *fmt;
  7483 + }
  7484 + else
  7485 + {
  7486 + read(is, CharT{'%'}, width, modified, *fmt);
  7487 + command = nullptr;
  7488 + width = -1;
  7489 + modified = CharT{};
  7490 + }
  7491 + }
  7492 + else
  7493 + read(is, *fmt);
  7494 + break;
  7495 + case '%':
  7496 + if (command)
  7497 + {
  7498 + if (modified == CharT{})
  7499 + read(is, *fmt);
  7500 + else
  7501 + read(is, CharT{'%'}, width, modified, *fmt);
  7502 + command = nullptr;
  7503 + width = -1;
  7504 + modified = CharT{};
  7505 + }
  7506 + else
  7507 + command = fmt;
  7508 + break;
  7509 + case 'z':
  7510 + if (command)
  7511 + {
  7512 + int tH, tM;
  7513 + minutes toff = not_a_offset;
  7514 + bool neg = false;
  7515 + auto ic = is.peek();
  7516 + if (!Traits::eq_int_type(ic, Traits::eof()))
  7517 + {
  7518 + auto c = static_cast<char>(Traits::to_char_type(ic));
  7519 + if (c == '-')
  7520 + neg = true;
  7521 + }
  7522 + if (modified == CharT{})
  7523 + {
  7524 + read(is, rs{tH, 2, 2});
  7525 + if (!is.fail())
  7526 + toff = hours{std::abs(tH)};
  7527 + if (is.good())
  7528 + {
  7529 + ic = is.peek();
  7530 + if (!Traits::eq_int_type(ic, Traits::eof()))
  7531 + {
  7532 + auto c = static_cast<char>(Traits::to_char_type(ic));
  7533 + if ('0' <= c && c <= '9')
  7534 + {
  7535 + read(is, ru{tM, 2, 2});
  7536 + if (!is.fail())
  7537 + toff += minutes{tM};
  7538 + }
  7539 + }
  7540 + }
  7541 + }
  7542 + else
  7543 + {
  7544 + read(is, rs{tH, 1, 2});
  7545 + if (!is.fail())
  7546 + toff = hours{std::abs(tH)};
  7547 + if (is.good())
  7548 + {
  7549 + ic = is.peek();
  7550 + if (!Traits::eq_int_type(ic, Traits::eof()))
  7551 + {
  7552 + auto c = static_cast<char>(Traits::to_char_type(ic));
  7553 + if (c == ':')
  7554 + {
  7555 + (void)is.get();
  7556 + read(is, ru{tM, 2, 2});
  7557 + if (!is.fail())
  7558 + toff += minutes{tM};
  7559 + }
  7560 + }
  7561 + }
  7562 + }
  7563 + if (neg)
  7564 + toff = -toff;
  7565 + checked_set(temp_offset, toff, not_a_offset, is);
  7566 + command = nullptr;
  7567 + width = -1;
  7568 + modified = CharT{};
  7569 + }
  7570 + else
  7571 + read(is, *fmt);
  7572 + break;
  7573 + case 'Z':
  7574 + if (command)
  7575 + {
  7576 + if (modified == CharT{})
  7577 + {
  7578 + std::basic_string<CharT, Traits, Alloc> buf;
  7579 + while (is.rdstate() == std::ios::goodbit)
  7580 + {
  7581 + auto i = is.rdbuf()->sgetc();
  7582 + if (Traits::eq_int_type(i, Traits::eof()))
  7583 + {
  7584 + is.setstate(ios::eofbit);
  7585 + break;
  7586 + }
  7587 + auto wc = Traits::to_char_type(i);
  7588 + auto c = static_cast<char>(wc);
  7589 + // is c a valid time zone name or abbreviation character?
  7590 + if (!(CharT{1} < wc && wc < CharT{127}) || !(isalnum(c) ||
  7591 + c == '_' || c == '/' || c == '-' || c == '+'))
  7592 + break;
  7593 + buf.push_back(c);
  7594 + is.rdbuf()->sbumpc();
  7595 + }
  7596 + if (buf.empty())
  7597 + is.setstate(ios::failbit);
  7598 + checked_set(temp_abbrev, buf, {}, is);
  7599 + }
  7600 + else
  7601 + read(is, CharT{'%'}, width, modified, *fmt);
  7602 + command = nullptr;
  7603 + width = -1;
  7604 + modified = CharT{};
  7605 + }
  7606 + else
  7607 + read(is, *fmt);
  7608 + break;
  7609 + default:
  7610 + if (command)
  7611 + {
  7612 + if (width == -1 && modified == CharT{} && '0' <= *fmt && *fmt <= '9')
  7613 + {
  7614 + width = static_cast<char>(*fmt) - '0';
  7615 + while ('0' <= fmt[1] && fmt[1] <= '9')
  7616 + width = 10*width + static_cast<char>(*++fmt) - '0';
  7617 + }
  7618 + else
  7619 + {
  7620 + if (modified == CharT{})
  7621 + read(is, CharT{'%'}, width, *fmt);
  7622 + else
  7623 + read(is, CharT{'%'}, width, modified, *fmt);
  7624 + command = nullptr;
  7625 + width = -1;
  7626 + modified = CharT{};
  7627 + }
  7628 + }
  7629 + else // !command
  7630 + {
  7631 + if (isspace(static_cast<unsigned char>(*fmt)))
  7632 + {
  7633 + // space matches 0 or more white space characters
  7634 + if (is.good())
  7635 + ws(is);
  7636 + }
  7637 + else
  7638 + read(is, *fmt);
  7639 + }
  7640 + break;
  7641 + }
  7642 + }
  7643 + // is.fail() || *fmt == CharT{}
  7644 + if (is.rdstate() == ios::goodbit && command)
  7645 + {
  7646 + if (modified == CharT{})
  7647 + read(is, CharT{'%'}, width);
  7648 + else
  7649 + read(is, CharT{'%'}, width, modified);
  7650 + }
  7651 + if (!is.fail())
  7652 + {
  7653 + if (y != not_a_2digit_year)
  7654 + {
  7655 + // Convert y and an optional C to Y
  7656 + if (!(0 <= y && y <= 99))
  7657 + goto broken;
  7658 + if (C == not_a_century)
  7659 + {
  7660 + if (Y == not_a_year)
  7661 + {
  7662 + if (y >= 69)
  7663 + C = 19;
  7664 + else
  7665 + C = 20;
  7666 + }
  7667 + else
  7668 + {
  7669 + C = (Y >= 0 ? Y : Y-100) / 100;
  7670 + }
  7671 + }
  7672 + int tY;
  7673 + if (C >= 0)
  7674 + tY = 100*C + y;
  7675 + else
  7676 + tY = 100*(C+1) - (y == 0 ? 100 : y);
  7677 + if (Y != not_a_year && Y != tY)
  7678 + goto broken;
  7679 + Y = tY;
  7680 + }
  7681 + if (g != not_a_2digit_year)
  7682 + {
  7683 + // Convert g and an optional C to G
  7684 + if (!(0 <= g && g <= 99))
  7685 + goto broken;
  7686 + if (C == not_a_century)
  7687 + {
  7688 + if (G == not_a_year)
  7689 + {
  7690 + if (g >= 69)
  7691 + C = 19;
  7692 + else
  7693 + C = 20;
  7694 + }
  7695 + else
  7696 + {
  7697 + C = (G >= 0 ? G : G-100) / 100;
  7698 + }
  7699 + }
  7700 + int tG;
  7701 + if (C >= 0)
  7702 + tG = 100*C + g;
  7703 + else
  7704 + tG = 100*(C+1) - (g == 0 ? 100 : g);
  7705 + if (G != not_a_year && G != tG)
  7706 + goto broken;
  7707 + G = tG;
  7708 + }
  7709 + if (Y < static_cast<int>(year::min()) || Y > static_cast<int>(year::max()))
  7710 + Y = not_a_year;
  7711 + bool computed = false;
  7712 + if (G != not_a_year && V != not_a_week_num && wd != not_a_weekday)
  7713 + {
  7714 + year_month_day ymd_trial = sys_days(year{G-1}/December/Thursday[last]) +
  7715 + (Monday-Thursday) + weeks{V-1} +
  7716 + (weekday{static_cast<unsigned>(wd)}-Monday);
  7717 + if (Y == not_a_year)
  7718 + Y = static_cast<int>(ymd_trial.year());
  7719 + else if (year{Y} != ymd_trial.year())
  7720 + goto broken;
  7721 + if (m == not_a_month)
  7722 + m = static_cast<int>(static_cast<unsigned>(ymd_trial.month()));
  7723 + else if (month(static_cast<unsigned>(m)) != ymd_trial.month())
  7724 + goto broken;
  7725 + if (d == not_a_day)
  7726 + d = static_cast<int>(static_cast<unsigned>(ymd_trial.day()));
  7727 + else if (day(static_cast<unsigned>(d)) != ymd_trial.day())
  7728 + goto broken;
  7729 + computed = true;
  7730 + }
  7731 + if (Y != not_a_year && U != not_a_week_num && wd != not_a_weekday)
  7732 + {
  7733 + year_month_day ymd_trial = sys_days(year{Y}/January/Sunday[1]) +
  7734 + weeks{U-1} +
  7735 + (weekday{static_cast<unsigned>(wd)} - Sunday);
  7736 + if (Y == not_a_year)
  7737 + Y = static_cast<int>(ymd_trial.year());
  7738 + else if (year{Y} != ymd_trial.year())
  7739 + goto broken;
  7740 + if (m == not_a_month)
  7741 + m = static_cast<int>(static_cast<unsigned>(ymd_trial.month()));
  7742 + else if (month(static_cast<unsigned>(m)) != ymd_trial.month())
  7743 + goto broken;
  7744 + if (d == not_a_day)
  7745 + d = static_cast<int>(static_cast<unsigned>(ymd_trial.day()));
  7746 + else if (day(static_cast<unsigned>(d)) != ymd_trial.day())
  7747 + goto broken;
  7748 + computed = true;
  7749 + }
  7750 + if (Y != not_a_year && W != not_a_week_num && wd != not_a_weekday)
  7751 + {
  7752 + year_month_day ymd_trial = sys_days(year{Y}/January/Monday[1]) +
  7753 + weeks{W-1} +
  7754 + (weekday{static_cast<unsigned>(wd)} - Monday);
  7755 + if (Y == not_a_year)
  7756 + Y = static_cast<int>(ymd_trial.year());
  7757 + else if (year{Y} != ymd_trial.year())
  7758 + goto broken;
  7759 + if (m == not_a_month)
  7760 + m = static_cast<int>(static_cast<unsigned>(ymd_trial.month()));
  7761 + else if (month(static_cast<unsigned>(m)) != ymd_trial.month())
  7762 + goto broken;
  7763 + if (d == not_a_day)
  7764 + d = static_cast<int>(static_cast<unsigned>(ymd_trial.day()));
  7765 + else if (day(static_cast<unsigned>(d)) != ymd_trial.day())
  7766 + goto broken;
  7767 + computed = true;
  7768 + }
  7769 + if (j != not_a_doy && Y != not_a_year)
  7770 + {
  7771 + auto ymd_trial = year_month_day{local_days(year{Y}/1/1) + days{j-1}};
  7772 + if (m == not_a_month)
  7773 + m = static_cast<int>(static_cast<unsigned>(ymd_trial.month()));
  7774 + else if (month(static_cast<unsigned>(m)) != ymd_trial.month())
  7775 + goto broken;
  7776 + if (d == not_a_day)
  7777 + d = static_cast<int>(static_cast<unsigned>(ymd_trial.day()));
  7778 + else if (day(static_cast<unsigned>(d)) != ymd_trial.day())
  7779 + goto broken;
  7780 + j = not_a_doy;
  7781 + }
  7782 + auto ymd = year{Y}/m/d;
  7783 + if (ymd.ok())
  7784 + {
  7785 + if (wd == not_a_weekday)
  7786 + wd = static_cast<int>((weekday(sys_days(ymd)) - Sunday).count());
  7787 + else if (wd != static_cast<int>((weekday(sys_days(ymd)) - Sunday).count()))
  7788 + goto broken;
  7789 + if (!computed)
  7790 + {
  7791 + if (G != not_a_year || V != not_a_week_num)
  7792 + {
  7793 + sys_days sd = ymd;
  7794 + auto G_trial = year_month_day{sd + days{3}}.year();
  7795 + auto start = sys_days((G_trial - years{1})/December/Thursday[last]) +
  7796 + (Monday - Thursday);
  7797 + if (sd < start)
  7798 + {
  7799 + --G_trial;
  7800 + if (V != not_a_week_num)
  7801 + start = sys_days((G_trial - years{1})/December/Thursday[last])
  7802 + + (Monday - Thursday);
  7803 + }
  7804 + if (G != not_a_year && G != static_cast<int>(G_trial))
  7805 + goto broken;
  7806 + if (V != not_a_week_num)
  7807 + {
  7808 + auto V_trial = duration_cast<weeks>(sd - start).count() + 1;
  7809 + if (V != V_trial)
  7810 + goto broken;
  7811 + }
  7812 + }
  7813 + if (U != not_a_week_num)
  7814 + {
  7815 + auto start = sys_days(Sunday[1]/January/ymd.year());
  7816 + auto U_trial = floor<weeks>(sys_days(ymd) - start).count() + 1;
  7817 + if (U != U_trial)
  7818 + goto broken;
  7819 + }
  7820 + if (W != not_a_week_num)
  7821 + {
  7822 + auto start = sys_days(Monday[1]/January/ymd.year());
  7823 + auto W_trial = floor<weeks>(sys_days(ymd) - start).count() + 1;
  7824 + if (W != W_trial)
  7825 + goto broken;
  7826 + }
  7827 + }
  7828 + }
  7829 + fds.ymd = ymd;
  7830 + if (I != not_a_hour_12_value)
  7831 + {
  7832 + if (!(1 <= I && I <= 12))
  7833 + goto broken;
  7834 + if (p != not_a_ampm)
  7835 + {
  7836 + // p is in [0, 1] == [AM, PM]
  7837 + // Store trial H in I
  7838 + if (I == 12)
  7839 + --p;
  7840 + I += p*12;
  7841 + // Either set H from I or make sure H and I are consistent
  7842 + if (H == not_a_hour)
  7843 + H = I;
  7844 + else if (I != H)
  7845 + goto broken;
  7846 + }
  7847 + else // p == not_a_ampm
  7848 + {
  7849 + // if H, make sure H and I could be consistent
  7850 + if (H != not_a_hour)
  7851 + {
  7852 + if (I == 12)
  7853 + {
  7854 + if (H != 0 && H != 12)
  7855 + goto broken;
  7856 + }
  7857 + else if (!(I == H || I == H+12))
  7858 + {
  7859 + goto broken;
  7860 + }
  7861 + }
  7862 + else // I is ambiguous, AM or PM?
  7863 + goto broken;
  7864 + }
  7865 + }
  7866 + if (H != not_a_hour)
  7867 + {
  7868 + fds.has_tod = true;
  7869 + fds.tod = hh_mm_ss<Duration>{hours{H}};
  7870 + }
  7871 + if (M != not_a_minute)
  7872 + {
  7873 + fds.has_tod = true;
  7874 + fds.tod.m_ = minutes{M};
  7875 + }
  7876 + if (s != not_a_second)
  7877 + {
  7878 + fds.has_tod = true;
  7879 + fds.tod.s_ = detail::decimal_format_seconds<Duration>{s};
  7880 + }
  7881 + if (j != not_a_doy)
  7882 + {
  7883 + fds.has_tod = true;
  7884 + fds.tod.h_ += hours{days{j}};
  7885 + }
  7886 + if (wd != not_a_weekday)
  7887 + fds.wd = weekday{static_cast<unsigned>(wd)};
  7888 + if (abbrev != nullptr)
  7889 + *abbrev = std::move(temp_abbrev);
  7890 + if (offset != nullptr && temp_offset != not_a_offset)
  7891 + *offset = temp_offset;
  7892 + }
  7893 + return is;
  7894 + }
  7895 +broken:
  7896 + is.setstate(ios::failbit);
  7897 + return is;
  7898 +}
  7899 +
  7900 +template <class CharT, class Traits, class Alloc = std::allocator<CharT>>
  7901 +std::basic_istream<CharT, Traits>&
  7902 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, year& y,
  7903 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  7904 + std::chrono::minutes* offset = nullptr)
  7905 +{
  7906 + using CT = std::chrono::seconds;
  7907 + fields<CT> fds{};
  7908 + date::from_stream(is, fmt, fds, abbrev, offset);
  7909 + if (!fds.ymd.year().ok())
  7910 + is.setstate(std::ios::failbit);
  7911 + if (!is.fail())
  7912 + y = fds.ymd.year();
  7913 + return is;
  7914 +}
  7915 +
  7916 +template <class CharT, class Traits, class Alloc = std::allocator<CharT>>
  7917 +std::basic_istream<CharT, Traits>&
  7918 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, month& m,
  7919 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  7920 + std::chrono::minutes* offset = nullptr)
  7921 +{
  7922 + using CT = std::chrono::seconds;
  7923 + fields<CT> fds{};
  7924 + date::from_stream(is, fmt, fds, abbrev, offset);
  7925 + if (!fds.ymd.month().ok())
  7926 + is.setstate(std::ios::failbit);
  7927 + if (!is.fail())
  7928 + m = fds.ymd.month();
  7929 + return is;
  7930 +}
  7931 +
  7932 +template <class CharT, class Traits, class Alloc = std::allocator<CharT>>
  7933 +std::basic_istream<CharT, Traits>&
  7934 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, day& d,
  7935 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  7936 + std::chrono::minutes* offset = nullptr)
  7937 +{
  7938 + using CT = std::chrono::seconds;
  7939 + fields<CT> fds{};
  7940 + date::from_stream(is, fmt, fds, abbrev, offset);
  7941 + if (!fds.ymd.day().ok())
  7942 + is.setstate(std::ios::failbit);
  7943 + if (!is.fail())
  7944 + d = fds.ymd.day();
  7945 + return is;
  7946 +}
  7947 +
  7948 +template <class CharT, class Traits, class Alloc = std::allocator<CharT>>
  7949 +std::basic_istream<CharT, Traits>&
  7950 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, weekday& wd,
  7951 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  7952 + std::chrono::minutes* offset = nullptr)
  7953 +{
  7954 + using CT = std::chrono::seconds;
  7955 + fields<CT> fds{};
  7956 + date::from_stream(is, fmt, fds, abbrev, offset);
  7957 + if (!fds.wd.ok())
  7958 + is.setstate(std::ios::failbit);
  7959 + if (!is.fail())
  7960 + wd = fds.wd;
  7961 + return is;
  7962 +}
  7963 +
  7964 +template <class CharT, class Traits, class Alloc = std::allocator<CharT>>
  7965 +std::basic_istream<CharT, Traits>&
  7966 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, year_month& ym,
  7967 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  7968 + std::chrono::minutes* offset = nullptr)
  7969 +{
  7970 + using CT = std::chrono::seconds;
  7971 + fields<CT> fds{};
  7972 + date::from_stream(is, fmt, fds, abbrev, offset);
  7973 + if (!fds.ymd.month().ok())
  7974 + is.setstate(std::ios::failbit);
  7975 + if (!is.fail())
  7976 + ym = fds.ymd.year()/fds.ymd.month();
  7977 + return is;
  7978 +}
  7979 +
  7980 +template <class CharT, class Traits, class Alloc = std::allocator<CharT>>
  7981 +std::basic_istream<CharT, Traits>&
  7982 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, month_day& md,
  7983 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  7984 + std::chrono::minutes* offset = nullptr)
  7985 +{
  7986 + using CT = std::chrono::seconds;
  7987 + fields<CT> fds{};
  7988 + date::from_stream(is, fmt, fds, abbrev, offset);
  7989 + if (!fds.ymd.month().ok() || !fds.ymd.day().ok())
  7990 + is.setstate(std::ios::failbit);
  7991 + if (!is.fail())
  7992 + md = fds.ymd.month()/fds.ymd.day();
  7993 + return is;
  7994 +}
  7995 +
  7996 +template <class CharT, class Traits, class Alloc = std::allocator<CharT>>
  7997 +std::basic_istream<CharT, Traits>&
  7998 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,
  7999 + year_month_day& ymd, std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  8000 + std::chrono::minutes* offset = nullptr)
  8001 +{
  8002 + using CT = std::chrono::seconds;
  8003 + fields<CT> fds{};
  8004 + date::from_stream(is, fmt, fds, abbrev, offset);
  8005 + if (!fds.ymd.ok())
  8006 + is.setstate(std::ios::failbit);
  8007 + if (!is.fail())
  8008 + ymd = fds.ymd;
  8009 + return is;
  8010 +}
  8011 +
  8012 +template <class Duration, class CharT, class Traits, class Alloc = std::allocator<CharT>>
  8013 +std::basic_istream<CharT, Traits>&
  8014 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,
  8015 + sys_time<Duration>& tp, std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  8016 + std::chrono::minutes* offset = nullptr)
  8017 +{
  8018 + using CT = typename std::common_type<Duration, std::chrono::seconds>::type;
  8019 + using detail::round_i;
  8020 + std::chrono::minutes offset_local{};
  8021 + auto offptr = offset ? offset : &offset_local;
  8022 + fields<CT> fds{};
  8023 + fds.has_tod = true;
  8024 + date::from_stream(is, fmt, fds, abbrev, offptr);
  8025 + if (!fds.ymd.ok() || !fds.tod.in_conventional_range())
  8026 + is.setstate(std::ios::failbit);
  8027 + if (!is.fail())
  8028 + tp = round_i<Duration>(sys_days(fds.ymd) - *offptr + fds.tod.to_duration());
  8029 + return is;
  8030 +}
  8031 +
  8032 +template <class Duration, class CharT, class Traits, class Alloc = std::allocator<CharT>>
  8033 +std::basic_istream<CharT, Traits>&
  8034 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,
  8035 + local_time<Duration>& tp, std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  8036 + std::chrono::minutes* offset = nullptr)
  8037 +{
  8038 + using CT = typename std::common_type<Duration, std::chrono::seconds>::type;
  8039 + using detail::round_i;
  8040 + fields<CT> fds{};
  8041 + fds.has_tod = true;
  8042 + date::from_stream(is, fmt, fds, abbrev, offset);
  8043 + if (!fds.ymd.ok() || !fds.tod.in_conventional_range())
  8044 + is.setstate(std::ios::failbit);
  8045 + if (!is.fail())
  8046 + tp = round_i<Duration>(local_seconds{local_days(fds.ymd)} + fds.tod.to_duration());
  8047 + return is;
  8048 +}
  8049 +
  8050 +template <class Rep, class Period, class CharT, class Traits, class Alloc = std::allocator<CharT>>
  8051 +std::basic_istream<CharT, Traits>&
  8052 +from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,
  8053 + std::chrono::duration<Rep, Period>& d,
  8054 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  8055 + std::chrono::minutes* offset = nullptr)
  8056 +{
  8057 + using Duration = std::chrono::duration<Rep, Period>;
  8058 + using CT = typename std::common_type<Duration, std::chrono::seconds>::type;
  8059 + using detail::round_i;
  8060 + fields<CT> fds{};
  8061 + date::from_stream(is, fmt, fds, abbrev, offset);
  8062 + if (!fds.has_tod)
  8063 + is.setstate(std::ios::failbit);
  8064 + if (!is.fail())
  8065 + d = round_i<Duration>(fds.tod.to_duration());
  8066 + return is;
  8067 +}
  8068 +
  8069 +template <class Parsable, class CharT, class Traits = std::char_traits<CharT>,
  8070 + class Alloc = std::allocator<CharT>>
  8071 +struct parse_manip
  8072 +{
  8073 + const std::basic_string<CharT, Traits, Alloc> format_;
  8074 + Parsable& tp_;
  8075 + std::basic_string<CharT, Traits, Alloc>* abbrev_;
  8076 + std::chrono::minutes* offset_;
  8077 +
  8078 +public:
  8079 + parse_manip(std::basic_string<CharT, Traits, Alloc> format, Parsable& tp,
  8080 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  8081 + std::chrono::minutes* offset = nullptr)
  8082 + : format_(std::move(format))
  8083 + , tp_(tp)
  8084 + , abbrev_(abbrev)
  8085 + , offset_(offset)
  8086 + {}
  8087 +
  8088 +#if HAS_STRING_VIEW
  8089 + parse_manip(const CharT* format, Parsable& tp,
  8090 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  8091 + std::chrono::minutes* offset = nullptr)
  8092 + : format_(format)
  8093 + , tp_(tp)
  8094 + , abbrev_(abbrev)
  8095 + , offset_(offset)
  8096 + {}
  8097 +
  8098 + parse_manip(std::basic_string_view<CharT, Traits> format, Parsable& tp,
  8099 + std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,
  8100 + std::chrono::minutes* offset = nullptr)
  8101 + : format_(format)
  8102 + , tp_(tp)
  8103 + , abbrev_(abbrev)
  8104 + , offset_(offset)
  8105 + {}
  8106 +#endif // HAS_STRING_VIEW
  8107 +};
  8108 +
  8109 +template <class Parsable, class CharT, class Traits, class Alloc>
  8110 +std::basic_istream<CharT, Traits>&
  8111 +operator>>(std::basic_istream<CharT, Traits>& is,
  8112 + const parse_manip<Parsable, CharT, Traits, Alloc>& x)
  8113 +{
  8114 + return date::from_stream(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_);
  8115 +}
  8116 +
  8117 +template <class Parsable, class CharT, class Traits, class Alloc>
  8118 +inline
  8119 +auto
  8120 +parse(const std::basic_string<CharT, Traits, Alloc>& format, Parsable& tp)
  8121 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT, Traits>&>(),
  8122 + format.c_str(), tp),
  8123 + parse_manip<Parsable, CharT, Traits, Alloc>{format, tp})
  8124 +{
  8125 + return {format, tp};
  8126 +}
  8127 +
  8128 +template <class Parsable, class CharT, class Traits, class Alloc>
  8129 +inline
  8130 +auto
  8131 +parse(const std::basic_string<CharT, Traits, Alloc>& format, Parsable& tp,
  8132 + std::basic_string<CharT, Traits, Alloc>& abbrev)
  8133 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT, Traits>&>(),
  8134 + format.c_str(), tp, &abbrev),
  8135 + parse_manip<Parsable, CharT, Traits, Alloc>{format, tp, &abbrev})
  8136 +{
  8137 + return {format, tp, &abbrev};
  8138 +}
  8139 +
  8140 +template <class Parsable, class CharT, class Traits, class Alloc>
  8141 +inline
  8142 +auto
  8143 +parse(const std::basic_string<CharT, Traits, Alloc>& format, Parsable& tp,
  8144 + std::chrono::minutes& offset)
  8145 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT, Traits>&>(),
  8146 + format.c_str(), tp,
  8147 + std::declval<std::basic_string<CharT, Traits, Alloc>*>(),
  8148 + &offset),
  8149 + parse_manip<Parsable, CharT, Traits, Alloc>{format, tp, nullptr, &offset})
  8150 +{
  8151 + return {format, tp, nullptr, &offset};
  8152 +}
  8153 +
  8154 +template <class Parsable, class CharT, class Traits, class Alloc>
  8155 +inline
  8156 +auto
  8157 +parse(const std::basic_string<CharT, Traits, Alloc>& format, Parsable& tp,
  8158 + std::basic_string<CharT, Traits, Alloc>& abbrev, std::chrono::minutes& offset)
  8159 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT, Traits>&>(),
  8160 + format.c_str(), tp, &abbrev, &offset),
  8161 + parse_manip<Parsable, CharT, Traits, Alloc>{format, tp, &abbrev, &offset})
  8162 +{
  8163 + return {format, tp, &abbrev, &offset};
  8164 +}
  8165 +
  8166 +// const CharT* formats
  8167 +
  8168 +template <class Parsable, class CharT>
  8169 +inline
  8170 +auto
  8171 +parse(const CharT* format, Parsable& tp)
  8172 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT>&>(), format, tp),
  8173 + parse_manip<Parsable, CharT>{format, tp})
  8174 +{
  8175 + return {format, tp};
  8176 +}
  8177 +
  8178 +template <class Parsable, class CharT, class Traits, class Alloc>
  8179 +inline
  8180 +auto
  8181 +parse(const CharT* format, Parsable& tp, std::basic_string<CharT, Traits, Alloc>& abbrev)
  8182 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT, Traits>&>(), format,
  8183 + tp, &abbrev),
  8184 + parse_manip<Parsable, CharT, Traits, Alloc>{format, tp, &abbrev})
  8185 +{
  8186 + return {format, tp, &abbrev};
  8187 +}
  8188 +
  8189 +template <class Parsable, class CharT>
  8190 +inline
  8191 +auto
  8192 +parse(const CharT* format, Parsable& tp, std::chrono::minutes& offset)
  8193 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT>&>(), format,
  8194 + tp, std::declval<std::basic_string<CharT>*>(), &offset),
  8195 + parse_manip<Parsable, CharT>{format, tp, nullptr, &offset})
  8196 +{
  8197 + return {format, tp, nullptr, &offset};
  8198 +}
  8199 +
  8200 +template <class Parsable, class CharT, class Traits, class Alloc>
  8201 +inline
  8202 +auto
  8203 +parse(const CharT* format, Parsable& tp,
  8204 + std::basic_string<CharT, Traits, Alloc>& abbrev, std::chrono::minutes& offset)
  8205 + -> decltype(date::from_stream(std::declval<std::basic_istream<CharT, Traits>&>(), format,
  8206 + tp, &abbrev, &offset),
  8207 + parse_manip<Parsable, CharT, Traits, Alloc>{format, tp, &abbrev, &offset})
  8208 +{
  8209 + return {format, tp, &abbrev, &offset};
  8210 +}
  8211 +
  8212 +// duration streaming
  8213 +
  8214 +template <class CharT, class Traits, class Rep, class Period>
  8215 +inline
  8216 +std::basic_ostream<CharT, Traits>&
  8217 +operator<<(std::basic_ostream<CharT, Traits>& os,
  8218 + const std::chrono::duration<Rep, Period>& d)
  8219 +{
  8220 + return os << detail::make_string<CharT, Traits>::from(d.count()) +
  8221 + detail::get_units<CharT>(typename Period::type{});
  8222 +}
  8223 +
  8224 +} // namespace date
  8225 +
  8226 +#ifdef _MSC_VER
  8227 +# pragma warning(pop)
  8228 +#endif
  8229 +
  8230 +#ifdef __GNUC__
  8231 +# pragma GCC diagnostic pop
  8232 +#endif
  8233 +
  8234 +#endif // DATE_H
  8235 +
... ...
src/errorcode.cpp 0 → 100644
  1 +++ a/src/errorcode.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "errorcode.h"
  20 +
  21 +// paho
  22 +#include <MQTTAsync.h>
  23 +
  24 +namespace osdev {
  25 +namespace components {
  26 +namespace mqtt {
  27 +
  28 +std::string pahoAsyncErrorCodeToString( int errorCode )
  29 +{
  30 + if (MQTTASYNC_SUCCESS == errorCode) {
  31 + return "MQTTASYNC_SUCCESS";
  32 + }
  33 + else if (MQTTASYNC_FAILURE == errorCode) {
  34 + return "MQTTASYNC_FAILURE";
  35 + }
  36 + else if (MQTTASYNC_PERSISTENCE_ERROR == errorCode) {
  37 + return "MQTTASYNC_PERSISTENCE_ERROR";
  38 + }
  39 + else if (MQTTASYNC_DISCONNECTED == errorCode) {
  40 + return "MQTTASYNC_DISCONNECTED";
  41 + }
  42 + else if (MQTTASYNC_MAX_MESSAGES_INFLIGHT == errorCode) {
  43 + return "MQTTASYNC_MAX_MESSAGES_INFLIGHT";
  44 + }
  45 + else if (MQTTASYNC_BAD_UTF8_STRING == errorCode) {
  46 + return "MQTTASYNC_BAD_UTF8_STRING";
  47 + }
  48 + else if (MQTTASYNC_NULL_PARAMETER == errorCode) {
  49 + return "MQTTASYNC_NULL_PARAMETER";
  50 + }
  51 + else if (MQTTASYNC_TOPICNAME_TRUNCATED == errorCode) {
  52 + return "MQTTASYNC_TOPICNAME_TRUNCATED";
  53 + }
  54 + else if (MQTTASYNC_BAD_STRUCTURE == errorCode) {
  55 + return "MQTTASYNC_BAD_STRUCTURE";
  56 + }
  57 + else if (MQTTASYNC_BAD_QOS == errorCode) {
  58 + return "MQTTASYNC_BAD_QOS";
  59 + }
  60 + else if (MQTTASYNC_NO_MORE_MSGIDS == errorCode) {
  61 + return "MQTTASYNC_NO_MORE_MSGIDS";
  62 + }
  63 + else if (MQTTASYNC_OPERATION_INCOMPLETE == errorCode) {
  64 + return "MQTTASYNC_OPERATION_INCOMPLETE";
  65 + }
  66 + else if (MQTTASYNC_MAX_BUFFERED_MESSAGES == errorCode) {
  67 + return "MQTTASYNC_MAX_BUFFERED_MESSAGES";
  68 + }
  69 + else if (MQTTASYNC_SSL_NOT_SUPPORTED == errorCode) {
  70 + return "MQTTASYNC_SSL_NOT_SUPPORTED";
  71 + }
  72 + else if (MQTTASYNC_BAD_PROTOCOL == errorCode) {
  73 + return "MQTTASYNC_BAD_PROTOCOL";
  74 + }
  75 +#if defined MQTTASYNC_BAD_MQTT_OPTION // Not supported by centos7 paho-c
  76 + else if (MQTTASYNC_BAD_MQTT_OPTION == errorCode) {
  77 + return "MQTTASYNC_BAD_MQTT_OPTION";
  78 + }
  79 +#endif
  80 +#if defined MQTTASYNC_WRONG_MQTT_VERSION // Not supported by centos7 paho-c
  81 + else if (MQTTASYNC_WRONG_MQTT_VERSION == errorCode) {
  82 + return "MQTTASYNC_WRONG_MQTT_VERSION";
  83 + }
  84 +#endif
  85 + return "Unknown(" + std::to_string(errorCode) + ")";
  86 +}
  87 +
  88 +} // End namespace mqtt
  89 +} // End namespace components
  90 +} // End namespace osdev
... ...
src/errorcode.h 0 → 100644
  1 +++ a/src/errorcode.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_ERRORCODE_H
  2 +#define OSDEV_COMPONENTS_MQTT_ERRORCODE_H
  3 +
  4 +// std
  5 +#include <string>
  6 +
  7 +namespace osdev {
  8 +namespace components {
  9 +namespace mqtt {
  10 +
  11 +/*!
  12 + * \return A stringified paho error code...
  13 + */
  14 +std::string pahoAsyncErrorCodeToString( int errorCode );
  15 +
  16 +
  17 +} // End namespace mqtt
  18 +} // End nnamespace components
  19 +} // End namesapce osdev
  20 +
  21 +#endif // OSDEV_COMPONENTS_MQTT_ERRORCODE_H
... ...
src/histogram.h 0 → 100644
  1 +++ a/src/histogram.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAM_H
  20 +#define OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAM_H
  21 +
  22 +#include <atomic>
  23 +#include <cmath>
  24 +#include <limits>
  25 +#include <mutex>
  26 +#include <string>
  27 +#include <vector>
  28 +
  29 +#include "utils.h"
  30 +
  31 +#include "ihistogram.h"
  32 +
  33 +namespace osdev {
  34 +namespace components {
  35 +namespace mqtt {
  36 +namespace measurement {
  37 +
  38 +template <typename T>
  39 +double fabs(T val)
  40 +{
  41 + return std::fabs(val);
  42 +}
  43 +
  44 +template <typename Rep, typename Dur>
  45 +double fabs(const std::chrono::duration<Rep, Dur>& dur)
  46 +{
  47 + return std::fabs(dur.count());
  48 +}
  49 +
  50 +template <typename T>
  51 +double to_double(T val)
  52 +{
  53 + return static_cast<double>(val);
  54 +}
  55 +
  56 +template <typename Rep, typename Dur>
  57 +double to_double(const std::chrono::duration<Rep, Dur>& dur)
  58 +{
  59 + return static_cast<double>(dur.count());
  60 +}
  61 +
  62 +template <typename T>
  63 +std::string to_string(T val)
  64 +{
  65 + return std::to_string(val);
  66 +}
  67 +
  68 +template <typename Rep, typename Dur>
  69 +std::string to_string(const std::chrono::duration<Rep, Dur>& dur)
  70 +{
  71 + return std::to_string(dur.count());
  72 +}
  73 +
  74 +template <typename T>
  75 +T min(T)
  76 +{
  77 + return std::numeric_limits<T>::min();
  78 +}
  79 +
  80 +template <typename Rep, typename Dur>
  81 +std::chrono::duration<Rep, Dur> min(const std::chrono::duration<Rep, Dur>&)
  82 +{
  83 + return std::chrono::duration<Rep, Dur>::min();
  84 +}
  85 +
  86 +template <typename T>
  87 +T max(T)
  88 +{
  89 + return std::numeric_limits<T>::max();
  90 +}
  91 +
  92 +template <typename Rep, typename Dur>
  93 +std::chrono::duration<Rep, Dur> max(const std::chrono::duration<Rep, Dur>&)
  94 +{
  95 + return std::chrono::duration<Rep, Dur>::max();
  96 +}
  97 +
  98 +/**
  99 + * @brief This class implements a histogram for typed values.
  100 + * Outlier values to the left are counted on the first bin and to the right on the last bin.
  101 + * @tparam T The value type. T must be default constructable.
  102 + * @tparam Buckets The number of buckets to use for this histogram. Default is 10 buckets.
  103 + */
  104 +template <typename T, std::size_t Buckets = 10>
  105 +class Histogram : public IHistogram
  106 +{
  107 +public:
  108 + /**
  109 + * @brief Construct a histogram.
  110 + * @param id The histogram identification
  111 + * @param minValue The minimum value of the histogram.
  112 + * @param maxValue The maxmim value of the histogram.
  113 + * @param unit The unit of the values that are collected in this histogram.
  114 + */
  115 + Histogram(const std::string& id, T minValue, T maxValue, const char* unit);
  116 +
  117 + /**
  118 + * @brief Set a value in the histogram.
  119 + */
  120 + void setValue(T value);
  121 +
  122 + /**
  123 + * @return true when values are added since the last time a snapshot was taken of the histogram or a reset was performed, false otherwise.
  124 + */
  125 + bool isUpdated() const;
  126 +
  127 + /**
  128 + * @brief Reset the histogram.
  129 + * All counts are made zero and the smallest and largest recorded values are set to default.
  130 + */
  131 + void reset();
  132 +
  133 + // IHistogram implementation
  134 + virtual const std::string& id() const override;
  135 + virtual std::size_t numBuckets() const override;
  136 + virtual double bucketWidth() const override;
  137 + virtual std::string minValueString() const override;
  138 + virtual std::string maxValueString() const override;
  139 + virtual const std::string& unit() const override;
  140 + virtual HistogramData histogramData() const override;
  141 + virtual std::size_t numberOfValuesSinceLastClear() const override;
  142 + virtual std::string smallestValueSinceLastClear() const override;
  143 + virtual std::string largestValueSinceLastClear() const override;
  144 + virtual std::string averageValueSinceLastClear() const override;
  145 + virtual void clearRunningValues() override;
  146 + virtual std::string toString() const override;
  147 +
  148 +private:
  149 + /**
  150 + * @brief Get the index on which to count the given value.
  151 + * The outliers to the left are counted on index 0 and to the right on index size() - 1.
  152 + * @param value The value to get the index for.
  153 + * @return the index in the histogram on which to count the given value.
  154 + */
  155 + inline std::size_t index(T value)
  156 + {
  157 + if (value > m_maxValue) {
  158 + return m_histogram.size() - 1;
  159 + }
  160 + if (value < m_minValue) {
  161 + return 0;
  162 + }
  163 +
  164 + return 1 + static_cast<std::size_t>(to_double((value - m_minValue) / m_bucketWidth));
  165 + }
  166 +
  167 + const std::string m_id;
  168 + const std::string m_unit;
  169 + const T m_minValue;
  170 + const T m_maxValue;
  171 + const double m_bucketWidth;
  172 + T m_smallestValue;
  173 + T m_largestValue;
  174 + T m_smallestValueSinceLastClear;
  175 + T m_largestValueSinceLastClear;
  176 + T m_summedValueSinceLastClear;
  177 + std::size_t m_numberOfValuesSinceLastClear;
  178 + mutable std::mutex m_mutex;
  179 + std::vector<std::size_t> m_histogram;
  180 + mutable std::atomic_bool m_isUpdated;
  181 +};
  182 +
  183 +template <typename T, std::size_t Buckets>
  184 +Histogram<T, Buckets>::Histogram(const std::string& _id, T minValue, T maxValue, const char* _unit)
  185 + : m_id(_id)
  186 + , m_unit(_unit)
  187 + , m_minValue(minValue)
  188 + , m_maxValue(maxValue)
  189 + , m_bucketWidth(fabs((m_maxValue - m_minValue)) / Buckets)
  190 + , m_smallestValue{ max(maxValue) }
  191 + , m_largestValue{ min(maxValue) }
  192 + , m_smallestValueSinceLastClear{ max(maxValue) }
  193 + , m_largestValueSinceLastClear{ min(maxValue) }
  194 + , m_summedValueSinceLastClear{}
  195 + , m_numberOfValuesSinceLastClear{}
  196 + , m_mutex()
  197 + , m_histogram(Buckets + 2) // + 2 for the out of bound buckets
  198 + , m_isUpdated(false)
  199 +{
  200 +}
  201 +
  202 +template <typename T, std::size_t Buckets>
  203 +void Histogram<T, Buckets>::setValue(T value)
  204 +{
  205 + std::lock_guard<std::mutex> lg(m_mutex);
  206 + apply_unused_parameters(lg);
  207 +
  208 + if (value > m_largestValueSinceLastClear) {
  209 + m_largestValueSinceLastClear = value;
  210 + }
  211 +
  212 + if (value < m_smallestValueSinceLastClear) {
  213 + m_smallestValueSinceLastClear = value;
  214 + }
  215 +
  216 + if (value > m_largestValue) {
  217 + m_largestValue = value;
  218 + }
  219 +
  220 + if (value < m_smallestValue) {
  221 + m_smallestValue = value;
  222 + }
  223 +
  224 + m_summedValueSinceLastClear += value;
  225 + ++m_numberOfValuesSinceLastClear;
  226 +
  227 + m_histogram[this->index(value)]++;
  228 + m_isUpdated = true;
  229 +}
  230 +
  231 +template <typename T, std::size_t Buckets>
  232 +bool Histogram<T, Buckets>::isUpdated() const
  233 +{
  234 + return m_isUpdated;
  235 +}
  236 +
  237 +template <typename T, std::size_t Buckets>
  238 +void Histogram<T, Buckets>::reset()
  239 +{
  240 + std::lock_guard<std::mutex> lg(m_mutex);
  241 + apply_unused_parameters(lg);
  242 +
  243 + m_smallestValue = T{ max(m_maxValue) };
  244 + m_largestValue = T{ min(m_maxValue) };
  245 + m_smallestValueSinceLastClear = T{ max(m_maxValue) };
  246 + m_largestValueSinceLastClear = T{ min(m_maxValue) };
  247 + m_summedValueSinceLastClear = T{};
  248 + m_numberOfValuesSinceLastClear = 0;
  249 +
  250 + std::fill(m_histogram.begin(), m_histogram.end(), 0);
  251 + m_isUpdated = false;
  252 +}
  253 +
  254 +template <typename T, std::size_t Buckets>
  255 +const std::string& Histogram<T, Buckets>::id() const
  256 +{
  257 + return m_id;
  258 +}
  259 +
  260 +template <typename T, std::size_t Buckets>
  261 +std::size_t Histogram<T, Buckets>::numBuckets() const
  262 +{
  263 + return Buckets;
  264 +}
  265 +
  266 +template <typename T, std::size_t Buckets>
  267 +double Histogram<T, Buckets>::bucketWidth() const
  268 +{
  269 + return m_bucketWidth;
  270 +}
  271 +
  272 +template <typename T, std::size_t Buckets>
  273 +std::string Histogram<T, Buckets>::minValueString() const
  274 +{
  275 + return to_string(m_minValue);
  276 +}
  277 +
  278 +template <typename T, std::size_t Buckets>
  279 +std::string Histogram<T, Buckets>::maxValueString() const
  280 +{
  281 + return to_string(m_maxValue);
  282 +}
  283 +
  284 +template <typename T, std::size_t Buckets>
  285 +const std::string& Histogram<T, Buckets>::unit() const
  286 +{
  287 + return m_unit;
  288 +}
  289 +
  290 +template <typename T, std::size_t Buckets>
  291 +HistogramData Histogram<T, Buckets>::histogramData() const
  292 +{
  293 + std::lock_guard<std::mutex> lg(m_mutex);
  294 + apply_unused_parameters(lg);
  295 +
  296 + m_isUpdated = false;
  297 + return HistogramData(to_string(m_smallestValue), to_string(m_largestValue),
  298 + to_string(m_smallestValueSinceLastClear), to_string(m_largestValueSinceLastClear), averageValueSinceLastClear(),
  299 + m_numberOfValuesSinceLastClear, m_histogram);
  300 +}
  301 +
  302 +template <typename T, std::size_t Buckets>
  303 +std::size_t Histogram<T, Buckets>::numberOfValuesSinceLastClear() const
  304 +{
  305 + return m_numberOfValuesSinceLastClear;
  306 +}
  307 +
  308 +template <typename T, std::size_t Buckets>
  309 +std::string Histogram<T, Buckets>::smallestValueSinceLastClear() const
  310 +{
  311 + return to_string(m_smallestValueSinceLastClear);
  312 +}
  313 +
  314 +template <typename T, std::size_t Buckets>
  315 +std::string Histogram<T, Buckets>::largestValueSinceLastClear() const
  316 +{
  317 + return to_string(m_largestValueSinceLastClear);
  318 +}
  319 +
  320 +template <typename T, std::size_t Buckets>
  321 +std::string Histogram<T, Buckets>::averageValueSinceLastClear() const
  322 +{
  323 + if (0 == m_numberOfValuesSinceLastClear) {
  324 + return "undefined";
  325 + }
  326 + return to_string(to_double(m_summedValueSinceLastClear) / m_numberOfValuesSinceLastClear);
  327 +}
  328 +
  329 +template <typename T, std::size_t Buckets>
  330 +void Histogram<T, Buckets>::clearRunningValues()
  331 +{
  332 + std::lock_guard<std::mutex> lg(m_mutex);
  333 + apply_unused_parameters(lg);
  334 +
  335 + m_smallestValueSinceLastClear = T{ max(m_maxValue) };
  336 + m_largestValueSinceLastClear = T{ min(m_maxValue) };
  337 + m_summedValueSinceLastClear = T{};
  338 + m_numberOfValuesSinceLastClear = 0;
  339 +}
  340 +
  341 +template <typename T, std::size_t Buckets>
  342 +std::string Histogram<T, Buckets>::toString() const
  343 +{
  344 + return visualizeHistogram(*this);
  345 +}
  346 +
  347 +} // End namespace measurement
  348 +} // End namespace mqtt
  349 +} // End namespace components
  350 +} // End namespace osdev
  351 +
  352 +#endif // OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAM_H
... ...
src/histogramprovider.h 0 → 100644
  1 +++ a/src/histogramprovider.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAMPROVIDER_H
  20 +#define OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAMPROVIDER_H
  21 +
  22 +// std
  23 +#include <iostream>
  24 +#include <memory>
  25 +#include <string>
  26 +#include <unordered_map>
  27 +
  28 +#include "compat-c++14.h"
  29 +
  30 +// mlogic::common
  31 +#include "sharedreaderlock.h"
  32 +
  33 +#include "histogram.h"
  34 +
  35 +namespace osdev {
  36 +namespace components {
  37 +namespace mqtt {
  38 +namespace measurement {
  39 +
  40 +/**
  41 + * @brief This class provides the user with Entry class for putting measurements in a histogram.
  42 + * @tparam TContext A tag type that is used to give the HistogramProvider a user defined context.
  43 + * @tparam TValue Defines the value type that the histograms of this provider use.
  44 + * @tparam Buckets The number of buckets to use on creation of a Histogram. The default number of buckets is 10.
  45 + *
  46 + * The TValue type must define the following publically visible members.
  47 + *
  48 + * value_type - The underlying value type used in the histogram
  49 + * const value_type minValue - The minimum value of the histogram
  50 + * const value_type maxValue - The maximum value of the histogram
  51 + * const char* unit - The value unit.
  52 + */
  53 +template <typename TContext, typename TValue, std::size_t Buckets = 10>
  54 +class HistogramProvider
  55 +{
  56 +public:
  57 + using HistogramType = Histogram<typename TValue::value_type, Buckets>;
  58 +
  59 + /**
  60 + * @brief Get the HistogramProvider instance.
  61 + */
  62 + static HistogramProvider& instance();
  63 +
  64 + /**
  65 + * @brief Add a value to the histogram identified by id.
  66 + * @param id Identifies the histogram to add the value to.
  67 + * @param value The value to add.
  68 + */
  69 + void addValue(const std::string& id, typename TValue::value_type value);
  70 +
  71 + /**
  72 + * @brief Log the histogram identified by id via the ILogger framework.
  73 + * @param id The histogram to log. If no histogram is found no work is done.
  74 + */
  75 + void log(const std::string& id);
  76 +
  77 + /**
  78 + * @brief Log the histograms via the ILogger framework.
  79 + * @param onlyChanged When set to true only log histograms that have changed. Default is false.
  80 + */
  81 + void logAll(bool onlyChanged = false);
  82 +
  83 + /**
  84 + * @brief Reset all the histograms.
  85 + * @param logBeforeReset Flag that indicates whether the histogram needs to be logged before reset. Default is true.
  86 + * @note Only changed histograms are logged.
  87 + */
  88 + void resetAll(bool logBeforeReset = true);
  89 +
  90 + /**
  91 + * @return The logOnDestroy flag value.
  92 + */
  93 + bool logOnDestroy() const { return m_logOnDestroy; }
  94 +
  95 + /**
  96 + * @brief Set the logOnDestroy flag.
  97 + * @param logOnDest Log on destruction when true, do not log when set to false.
  98 + */
  99 + void setLogOnDestroy(bool logOnDest) { m_logOnDestroy = logOnDest; }
  100 +
  101 +private:
  102 + HistogramProvider();
  103 +
  104 + /**
  105 + * @brief On destruction the provider will log all its histogram information to stdout.
  106 + * Since the destruction will happen as one of the last things in the process, stdout is
  107 + * used for logging.
  108 + */
  109 + ~HistogramProvider();
  110 +
  111 + void log(const HistogramType& hist, bool onlyChanged);
  112 +
  113 + SharedReaderLock m_sharedLock;
  114 + std::unordered_map<std::string, std::unique_ptr<HistogramType>> m_histograms;
  115 + bool m_logOnDestroy; ///< Default is true.
  116 +};
  117 +
  118 +// static
  119 +template <typename TContext, typename TValue, std::size_t Buckets>
  120 +HistogramProvider<TContext, TValue, Buckets>& HistogramProvider<TContext, TValue, Buckets>::instance()
  121 +{
  122 + static HistogramProvider<TContext, TValue, Buckets> s_provider;
  123 + return s_provider;
  124 +}
  125 +
  126 +template <typename TContext, typename TValue, std::size_t Buckets>
  127 +void HistogramProvider<TContext, TValue, Buckets>::addValue(const std::string& id, typename TValue::value_type value)
  128 +{
  129 + OSDEV_COMPONENTS_SHAREDLOCK_SCOPE(m_sharedLock);
  130 + auto it = m_histograms.find(id);
  131 + if (m_histograms.end() == it) {
  132 + OSDEV_COMPONENTS_EXCLUSIVELOCK_SCOPE(m_sharedLock);
  133 + constexpr TValue val;
  134 + it = (m_histograms.emplace(std::make_pair(id, std::make_unique<HistogramType>(id, val.minValue, val.maxValue, val.unit)))).first;
  135 + }
  136 + it->second->setValue(value);
  137 +}
  138 +
  139 +template <typename TContext, typename TValue, std::size_t Buckets>
  140 +void HistogramProvider<TContext, TValue, Buckets>::log(const std::string& id)
  141 +{
  142 + OSDEV_COMPONENTS_SHAREDLOCK_SCOPE(m_sharedLock);
  143 + auto it = m_histograms.find(id);
  144 + if (m_histograms.end() != it) {
  145 + log(*(it->second), false);
  146 + }
  147 +}
  148 +
  149 +template <typename TContext, typename TValue, std::size_t Buckets>
  150 +void HistogramProvider<TContext, TValue, Buckets>::logAll(bool onlyChanged)
  151 +{
  152 + OSDEV_COMPONENTS_SHAREDLOCK_SCOPE(m_sharedLock);
  153 + for (const auto& h : m_histograms) {
  154 + log(*h.second, onlyChanged);
  155 + }
  156 +}
  157 +
  158 +template <typename TContext, typename TValue, std::size_t Buckets>
  159 +void HistogramProvider<TContext, TValue, Buckets>::resetAll(bool logBeforeReset)
  160 +{
  161 + OSDEV_COMPONENTS_SHAREDLOCK_SCOPE(m_sharedLock);
  162 + for (const auto& h : m_histograms) {
  163 + if (logBeforeReset) {
  164 + log(*h.second, true); // only log the histograms that have changed
  165 + }
  166 + h.second->reset();
  167 + }
  168 +}
  169 +
  170 +template <typename TContext, typename TValue, std::size_t Buckets>
  171 +HistogramProvider<TContext, TValue, Buckets>::HistogramProvider()
  172 + : m_sharedLock()
  173 + , m_histograms()
  174 + , m_logOnDestroy(true)
  175 +{
  176 +}
  177 +
  178 +template <typename TContext, typename TValue, std::size_t Buckets>
  179 +HistogramProvider<TContext, TValue, Buckets>::~HistogramProvider()
  180 +{
  181 + if (m_logOnDestroy) {
  182 + for (const auto& h : m_histograms) {
  183 + std::cout << *h.second << std::endl;
  184 + }
  185 + }
  186 +}
  187 +
  188 +template <typename TContext, typename TValue, std::size_t Buckets>
  189 +void HistogramProvider<TContext, TValue, Buckets>::log(const HistogramType& hist, bool onlyChanged)
  190 +{
  191 + if ((onlyChanged && hist.isUpdated()) || !onlyChanged) {
  192 + MLOGIC_COMMON_INFO("HistogramProvider", "%1", hist);
  193 + }
  194 +}
  195 +
  196 +} // End namespace measurement
  197 +} // End namespace mqtt
  198 +} // End namespace components
  199 +} // End namespace osdev
  200 +
  201 +#endif // OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAMPROVIDER_H
... ...
src/ihistogram.cpp 0 → 100644
  1 +++ a/src/ihistogram.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "ihistogram.h"
  20 +
  21 +// std
  22 +#include <algorithm>
  23 +#include <iomanip>
  24 +#include <numeric>
  25 +#include <sstream>
  26 +
  27 +namespace osdev {
  28 +namespace components {
  29 +namespace mqtt {
  30 +namespace measurement {
  31 +
  32 +IHistogram::~IHistogram() = default;
  33 +
  34 +namespace {
  35 +
  36 +std::pair<int, std::size_t> largestValueUnderThreshold(const std::vector<std::size_t>& hist, double thresholdValue)
  37 +{
  38 + int bin = -1;
  39 + std::size_t maxValue = 0;
  40 + // do not use the outlier bins for this calculation.
  41 + for (std::size_t i = 1; i < hist.size() - 1; ++i) {
  42 + if (hist[i] > maxValue && hist[i] < thresholdValue) {
  43 + bin = static_cast<int>(i);
  44 + maxValue = hist[i];
  45 + }
  46 + }
  47 + return { bin, maxValue };
  48 +}
  49 +
  50 +} // namespace
  51 +
  52 +std::string visualizeHistogram(const IHistogram& histogram)
  53 +{
  54 + const std::size_t diagramHeight = 10;
  55 + auto histData = histogram.histogramData(); // take a snapshot of the histogram data.
  56 + const auto& hist = histData.data();
  57 +
  58 + std::ostringstream oss;
  59 + oss << "Histogram for " << histogram.id() << "\\n";
  60 + auto minmax = std::minmax_element(hist.begin(), hist.end());
  61 +
  62 + auto minCountValueString = std::to_string(*minmax.first);
  63 + auto maxCountValueString = std::to_string(*minmax.second);
  64 +
  65 + auto countValueFieldWidth = static_cast<std::int32_t>(std::max(minCountValueString.size(), maxCountValueString.size()));
  66 + auto verticalSize = (*minmax.second - *minmax.first) / static_cast<double>(diagramHeight);
  67 +
  68 + auto totalMeasurements = std::accumulate(hist.begin(), hist.end(), 0);
  69 + oss << "Total nr of measurements: " << totalMeasurements << ", Nr of buckets: " << histogram.numBuckets() << ", bucket width: " << histogram.bucketWidth() << ", vertical resolution: " << verticalSize << "\\n";
  70 + auto binValuePair = largestValueUnderThreshold(hist, verticalSize);
  71 + if (binValuePair.first >= 0) {
  72 + oss << "Largest value under threshold: " << binValuePair.second << " in bin " << binValuePair.first << "\\n";
  73 + }
  74 + if (hist.front() > 0) {
  75 + oss << "Outliers to the left: " << hist.front() << ", smallest value: " << histData.smallestValueString() << histogram.unit() << "\\n";
  76 + }
  77 + if (hist.back() > 0) {
  78 + oss << "Outliers to the right: " << hist.back() << ", largest value: " << histData.largestValueString() << histogram.unit() << "\\n";
  79 + }
  80 +
  81 + oss << "\\nSince last clear:\\n"
  82 + << " number of values : " << histData.numberOfValuesSinceLastClear() << "\\n"
  83 + << " smallest value : " << histData.smallestValueSinceLastClearString() << histogram.unit() << "\\n"
  84 + << " largest value : " << histData.largestValueSinceLastClearString() << histogram.unit() << "\\n"
  85 + << " average value : " << histData.averageValueSinceLastClearString() << histogram.unit() << "\\n\\n";
  86 +
  87 + oss << std::setw(countValueFieldWidth) << maxCountValueString << " |";
  88 + for (std::size_t line = 0; line < diagramHeight; ++line) {
  89 + for (const auto& cnt : hist) {
  90 + if ((cnt - *minmax.first) > (diagramHeight - line - 1) * verticalSize) {
  91 + oss << '*';
  92 + }
  93 + else {
  94 + oss << ' ';
  95 + }
  96 + }
  97 + oss << "|\\n";
  98 + if (line + 1 < diagramHeight) {
  99 + oss << std::setw(countValueFieldWidth + 2) << '|';
  100 + }
  101 + }
  102 + oss << std::setw(countValueFieldWidth) << minCountValueString << " |/" << std::string(hist.size() - 2, '-') << "\\| " << histogram.unit() << "\\n";
  103 +
  104 + auto minValueString = histogram.minValueString();
  105 + auto maxValueString = histogram.maxValueString();
  106 + auto valueFieldWidth = std::max(minValueString.size(), maxValueString.size());
  107 +
  108 + for (std::size_t line = 0; line < valueFieldWidth; ++line) {
  109 + oss << std::setw(countValueFieldWidth + 3 + static_cast<int>(line)) << "";
  110 + oss << (line < minValueString.size() ? minValueString[line] : ' ');
  111 +
  112 + if (line < maxValueString.size()) {
  113 + oss << std::string(histogram.numBuckets() - 1, ' ') << maxValueString[line];
  114 + }
  115 + oss << "\\n";
  116 + }
  117 +
  118 + // Add the non zero data to the log for later analysis
  119 + // format: binnumber:value;binnumber:value;....
  120 + bool first = true;
  121 + for (std::size_t idx = 0; idx < hist.size(); ++idx) {
  122 + if (hist[idx] > 0) {
  123 + if (!first) {
  124 + oss << ';';
  125 + }
  126 + else {
  127 + oss << "\\n";
  128 + first = false;
  129 + }
  130 + oss << idx << ':' << hist[idx];
  131 + }
  132 + }
  133 +
  134 + return oss.str();
  135 +}
  136 +
  137 +std::ostream& operator<<(std::ostream& os, const IHistogram& rhs)
  138 +{
  139 + return os << rhs.toString();
  140 +}
  141 +
  142 +} // End namespace measurement
  143 +} // End namespace mqtt
  144 +} // End namespace components
  145 +} // End namespace osdev
... ...
src/ihistogram.h 0 → 100644
  1 +++ a/src/ihistogram.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MEASUREMENT_IHISTOGRAM_H
  2 +#define OSDEV_COMPONENTS_MQTT_MEASUREMENT_IHISTOGRAM_H
  3 +
  4 +// std
  5 +#include <ostream>
  6 +#include <string>
  7 +#include <vector>
  8 +
  9 +namespace osdev {
  10 +namespace components {
  11 +namespace mqtt {
  12 +namespace measurement {
  13 +
  14 +/**
  15 + * @brief Class that holds the dynamic part of a histogram.
  16 + * Used to take a snapshot of the histogram.
  17 + */
  18 +class HistogramData
  19 +{
  20 +public:
  21 + /**
  22 + * @brief Construct dynamic histrogram data.
  23 + * @param smallestValue A stringified version of the smallest recorded value since the last update.
  24 + * @param largestValue A stringified version of the largest recorded value since the last update.
  25 + * @param smallestValueSinceLastClear A stringified version of the smallest value as string since last clear.
  26 + * @param largestValueSinceLastClear A stringified version of the largest value as string since last clear.
  27 + * @param averageValueSinceLastClear A stringified version of the average value as string since last clear.
  28 + * @param nrOfValuesSinceLastClear Number of values since last clear.
  29 + * @param histCounts The histogram counts.
  30 + */
  31 + HistogramData(const std::string& smallestValue, const std::string& largestValue,
  32 + const std::string& smallestValueSinceLastClear,
  33 + const std::string& largestValueSinceLastClear,
  34 + const std::string& averageValueSinceLastClear,
  35 + std::size_t nrOfValuesSinceLastClear,
  36 + const std::vector<std::size_t>& histCounts)
  37 + : m_smallestValueString(smallestValue)
  38 + , m_largestValueString(largestValue)
  39 + , m_smallestValueSinceLastClearString(smallestValueSinceLastClear)
  40 + , m_largestValueSinceLastClearString(largestValueSinceLastClear)
  41 + , m_averageValueSinceLastClearString(averageValueSinceLastClear)
  42 + , m_numberOfValuesSinceLastClear(nrOfValuesSinceLastClear)
  43 + , m_data(histCounts)
  44 + {
  45 + }
  46 +
  47 + /**
  48 + * @return The smallest recorded value as a string.
  49 + */
  50 + const std::string& smallestValueString() const
  51 + {
  52 + return m_smallestValueString;
  53 + }
  54 +
  55 + /**
  56 + * @return The largest recorded value as a string.
  57 + */
  58 + const std::string& largestValueString() const
  59 + {
  60 + return m_largestValueString;
  61 + }
  62 +
  63 + /**
  64 + * @return The number of value since last clear.
  65 + */
  66 + std::size_t numberOfValuesSinceLastClear() const
  67 + {
  68 + return m_numberOfValuesSinceLastClear;
  69 + }
  70 +
  71 + /**
  72 + * @return The smallest value since last clear as a string.
  73 + */
  74 + const std::string& smallestValueSinceLastClearString() const
  75 + {
  76 + return m_smallestValueSinceLastClearString;
  77 + }
  78 +
  79 + /**
  80 + * @return The largest value since last clear as a string.
  81 + */
  82 + const std::string& largestValueSinceLastClearString() const
  83 + {
  84 + return m_largestValueSinceLastClearString;
  85 + }
  86 +
  87 + /**
  88 + * @return The average value since last clear as a string.
  89 + */
  90 + const std::string& averageValueSinceLastClearString() const
  91 + {
  92 + return m_averageValueSinceLastClearString;
  93 + }
  94 +
  95 + /**
  96 + * @return The histogram counts.
  97 + */
  98 + const std::vector<std::size_t>& data() const
  99 + {
  100 + return m_data;
  101 + }
  102 +
  103 +private:
  104 + std::string m_smallestValueString;
  105 + std::string m_largestValueString;
  106 + std::string m_smallestValueSinceLastClearString;
  107 + std::string m_largestValueSinceLastClearString;
  108 + std::string m_averageValueSinceLastClearString;
  109 + std::size_t m_numberOfValuesSinceLastClear;
  110 + std::vector<std::size_t> m_data;
  111 +};
  112 +
  113 +/**
  114 + * @brief Interface for histogram classes
  115 + * Can be used to store histograms in a container and visualize the histogram.
  116 + */
  117 +class IHistogram
  118 +{
  119 +public:
  120 + virtual ~IHistogram();
  121 +
  122 + /**
  123 + * @return The histogram identification (title).
  124 + */
  125 + virtual const std::string& id() const = 0;
  126 +
  127 + /**
  128 + * @return The number of buckets.
  129 + */
  130 + virtual std::size_t numBuckets() const = 0;
  131 +
  132 + /**
  133 + * @return The bucket width as a floating point value.
  134 + */
  135 + virtual double bucketWidth() const = 0;
  136 +
  137 + /**
  138 + * @return The minimum value of the histogram definition as a string.
  139 + */
  140 + virtual std::string minValueString() const = 0;
  141 +
  142 + /**
  143 + * @return The maximum value of the histogram definition as a string.
  144 + */
  145 + virtual std::string maxValueString() const = 0;
  146 +
  147 + /**
  148 + * @return The value unit as a a string.
  149 + */
  150 + virtual const std::string& unit() const = 0;
  151 +
  152 + /**
  153 + * @return The histogram data.
  154 + * This takes a snapshot of the histogram data.
  155 + */
  156 + virtual HistogramData histogramData() const = 0;
  157 +
  158 + /**
  159 + * @return The number of value since last clear.
  160 + */
  161 + virtual std::size_t numberOfValuesSinceLastClear() const = 0;
  162 +
  163 + /**
  164 + * @return The smallest value since last clear as a string.
  165 + */
  166 + virtual std::string smallestValueSinceLastClear() const = 0;
  167 +
  168 + /**
  169 + * @return The largest value since last clear as a string.
  170 + */
  171 + virtual std::string largestValueSinceLastClear() const = 0;
  172 +
  173 + /**
  174 + * @return The average value since last clear as a string.
  175 + */
  176 + virtual std::string averageValueSinceLastClear() const = 0;
  177 +
  178 + /**
  179 + * @brief Clears the values that are kept between successive clearRunningValues calls.
  180 + * These are:
  181 + * smallestValueSinceLastClear
  182 + * largestValueSinceLastClear
  183 + * averageValueSinceLastClear
  184 + */
  185 + virtual void clearRunningValues() = 0;
  186 +
  187 + /**
  188 + * @return The ascii representation of the histogram.
  189 + */
  190 + virtual std::string toString() const = 0;
  191 +};
  192 +
  193 +/**
  194 + * @brief Make an ascii visualisation of the given histogram data.
  195 + * @param histogram The histogram to visualize.
  196 + */
  197 +std::string visualizeHistogram(const IHistogram& histogram);
  198 +
  199 +/**
  200 + * @brief Stream operator for IHistogram instances.
  201 + */
  202 +std::ostream& operator<<(std::ostream& os, const IHistogram& rhs);
  203 +
  204 +} // End namespace measurement
  205 +} // End namespace mqtt
  206 +} // End namespace components
  207 +} // End namespace osdev
  208 +
  209 +#endif // OSDEV_COMPONENTS_MQTT_MEASUREMENT_IHISTOGRAM_H
... ...
src/imqttclient.h 0 → 100644
  1 +++ a/src/imqttclient.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_IMQTTCLIENT_H
  20 +#define OSDEV_COMPONENTS_MQTT_IMQTTCLIENT_H
  21 +
  22 +// std
  23 +#include <chrono>
  24 +#include <functional>
  25 +#include <set>
  26 +#include <string>
  27 +
  28 +// boost
  29 +#include <boost/optional.hpp>
  30 +
  31 +// mlogic::client
  32 +#include "istatecallback.h"
  33 +
  34 +// mlogic::mqtt
  35 +#include "connectionstatus.h"
  36 +#include "credentials.h"
  37 +#include "mqttmessage.h"
  38 +#include "token.h"
  39 +
  40 +namespace osdev {
  41 +namespace components {
  42 +namespace mqtt {
  43 +
  44 +/**
  45 + * @brief Interface that describes the minimal interface that a wrapper implementation needs to have in
  46 + * order to coorporate with the MqttClient class.
  47 + */
  48 +class IMqttClient : public virtual IStateCallback
  49 +{
  50 +public:
  51 + virtual ~IMqttClient() {}
  52 +
  53 + /**
  54 + * @brief Connect to the endpoint
  55 + * @param host The host name or ip address.
  56 + * @param port The port to use.
  57 + * @param credentials The credentials to use.
  58 + */
  59 + virtual void connect(const std::string& host, int port, const Credentials& credentials) = 0;
  60 +
  61 + /**
  62 + * @brief Connect to the endpoint
  63 + * @param endpoint an uri endpoint description.
  64 + */
  65 + virtual void connect(const std::string& endpoint) = 0;
  66 +
  67 + /**
  68 + * @brief Disconnect the client from the broker
  69 + */
  70 + virtual void disconnect() = 0;
  71 +
  72 + /**
  73 + * @brief Publish a message with a given quality of service (0, 1 or 2).
  74 + * @param message The message to publish.
  75 + * @param qos The quality of service to use.
  76 + * @return The token that identifies the publication (used in the deliveryCompleteCallback).
  77 + */
  78 + virtual Token publish(const MqttMessage& message, int qos) = 0;
  79 +
  80 + /**
  81 + * @brief Subscribe to a topic(filter).
  82 + * The combination of topic and qos makes a subscription unique on a wrapper client. When a topic has overlap
  83 + * with an existing subscription it is guaranteed that the given callback is only called once.
  84 + * @param topic The topic to subscribe to (can have wildcards). The topic can have overlap with existing topic subscriptions.
  85 + * @param qos The quality of service to use (0, 1 or 2).
  86 + * @param cb The callback that is called when messages are received for this subscription.
  87 + * @return A token that identifies the subscribe command.
  88 + */
  89 + virtual Token subscribe(const std::string& topic, int qos, const std::function<void(MqttMessage)>& cb) = 0;
  90 +
  91 + /**
  92 + * @brief Unsubscribe an existing subscription.
  93 + * The combination of topic and qos can occur on multiple wrapper clients. All subscriptions that match are unsubscribed.
  94 + * @param topic The topic to unsubscribe.
  95 + * @param qos The quality of service of the subscription (0, 1 or 2).
  96 + * @return A set of unsubscribe tokens identifying the unsubscribe commands. Usually the set will contain only one item.
  97 + */
  98 + virtual std::set<Token> unsubscribe(const std::string& topic, int qos) = 0;
  99 +
  100 + /**
  101 + * @brief Wait for all commands to complete.
  102 + * @param waitFor The number of milliseconds to wait for completetion of all commands.
  103 + * @return True when all commands have completed in time or false if not.
  104 + */
  105 + virtual bool waitForCompletion(std::chrono::milliseconds waitFor) const = 0;
  106 +
  107 + /**
  108 + * @brief Wait for a single command to complete.
  109 + * @param waitFor The number of milliseconds to wait for completetion of the command.
  110 + * @param token The token to wait for.
  111 + * @return True when the command has completed in time or false if not.
  112 + * @note Non existent tokens are also reported as completed.
  113 + */
  114 + virtual bool waitForCompletion(std::chrono::milliseconds waitFor, const Token& token) const = 0;
  115 +
  116 + /**
  117 + * @brief Wait for commands to complete.
  118 + * This method enables the user to wait for a set of commands. An empty set means to wait for all commands to complete.
  119 + * @param waitFor The number of milliseconds to wait for completetion of all commands.
  120 + * @param tokens The tokens to wait for. An empty set means to wait for all commands to complete.
  121 + * @return True when the commands have completed in time or false if not.
  122 + * @note Non existent tokens are also reported as completed.
  123 + */
  124 + virtual bool waitForCompletion(std::chrono::milliseconds waitFor, const std::set<Token>& tokens) const = 0;
  125 +
  126 + /**
  127 + * @brief Get the result of a command.
  128 + * @param token The token identifying the result.
  129 + * @return The command result when available.
  130 + */
  131 + virtual boost::optional<bool> commandResult(const Token& token) const = 0;
  132 +
  133 + /**
  134 + * @return The endpoint uri.
  135 + */
  136 + virtual std::string endpoint() const = 0;
  137 +};
  138 +
  139 +} // End namespace mqtt
  140 +} // End namespace components
  141 +} // End namespace osdev
  142 +
  143 +#endif // OSDEV_COMPONENTS_MQTT_IMQTTCLIENT_H
... ...
src/imqttclientimpl.cpp 0 → 100644
  1 +++ a/src/imqttclientimpl.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "imqttclientimpl.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +IMqttClientImpl::~IMqttClientImpl() = default;
... ...
src/imqttclientimpl.h 0 → 100644
  1 +++ a/src/imqttclientimpl.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_IMQTTCLIENTIMPL_H
  20 +#define OSDEV_COMPONENTS_MQTT_IMQTTCLIENTIMPL_H
  21 +
  22 +// std
  23 +#include <chrono>
  24 +#include <functional>
  25 +#include <ostream>
  26 +#include <set>
  27 +#include <string>
  28 +#include <vector>
  29 +
  30 +// boost
  31 +#include <boost/optional.hpp>
  32 +
  33 +// mlogic::mqtt
  34 +#include "connectionstatus.h"
  35 +#include "mqttmessage.h"
  36 +
  37 +namespace osdev {
  38 +namespace components {
  39 +namespace mqtt {
  40 +
  41 +/**
  42 + * @brief Interface that describes the minimal interface that a wrapper implementation needs to have in
  43 + * order to coorporate with the MqttClient class.
  44 + */
  45 +class IMqttClientImpl
  46 +{
  47 +public:
  48 + virtual ~IMqttClientImpl();
  49 +
  50 + /**
  51 + * @return id of this client.
  52 + */
  53 + virtual std::string clientId() const = 0;
  54 +
  55 + /**
  56 + * @return The connection status of the wrapper.
  57 + */
  58 + virtual ConnectionStatus connectionStatus() const = 0;
  59 +
  60 + /**
  61 + * @brief Connect the wrapper to the endpoint.
  62 + * @param wait A flag that indicates if the method should wait for a succesful connection.
  63 + * @return the operation token.
  64 + */
  65 + virtual std::int32_t connect(bool wait) = 0;
  66 +
  67 + /**
  68 + * @brief Disconnect the wrapper.
  69 + * @param wait A flag that indicates if the method should wait for a succesful disconnect. When true the method will wait for the timeoutMs value plus some additional time.
  70 + * @param timeoutMs A timeout in milliseconds. The timeout is used to give in flight messages a chance to get delivered. After the timeout the disconnect command is sent.
  71 + * @return the operation token.
  72 + */
  73 + virtual std::int32_t disconnect(bool wait, int timeoutMs) = 0;
  74 +
  75 + /**
  76 + * @brief Publish a message to an mqtt endpoint with a given quality of service.
  77 + * When the connection is in state reconnect the publish is saved so that it can be published later.
  78 + * @param message The message to send.
  79 + * @param qos The quality of service to use (0, 1 or 2).
  80 + * @return The message token. This token identifies the publication.
  81 + */
  82 + virtual std::int32_t publish(const MqttMessage& message, int qos) = 0;
  83 +
  84 + /**
  85 + * @brief Publish messages when client is reconnected.
  86 + */
  87 + virtual void publishPending() = 0;
  88 +
  89 + /**
  90 + * @brief Subscribe to a topic(filter).
  91 + * The combination of topic and qos makes a subscription unique. Subscriptions with the same topic filter
  92 + * but different qos are considered different, but they will overlap and cannot exist in the same wrapper.
  93 + * @param topic The topic to subscribe to (can have wildcards). The topic cannot have overlap with existing topic subscriptions.
  94 + * @param qos The quality of service to use (0, 1 or 2).
  95 + * @param cb The callback that is called when messages are received for this subscription.
  96 + * @return the operation token.
  97 + */
  98 + virtual std::int32_t subscribe(const std::string& topic, int qos, const std::function<void(MqttMessage msg)>& cb) = 0;
  99 +
  100 + /**
  101 + * @brief Resubscribe existing topicfilters.
  102 + * This method should be called only after a reconnect when subscriptions need to be reinstated.
  103 + */
  104 + virtual void resubscribe() = 0;
  105 +
  106 + /**
  107 + * @brief Unsubscribe an existing subscription.
  108 + * The combination of topic and qos make a subscription unique.
  109 + * @param topic The topic to unsubscribe.
  110 + * @param qos The quality of service of the subscription (0, 1 or 2).
  111 + * @return the operation token.
  112 + */
  113 + virtual std::int32_t unsubscribe(const std::string& topic, int qos) = 0;
  114 +
  115 + /**
  116 + * @brief Unsubscribe all subscriptions.
  117 + */
  118 + virtual void unsubscribeAll() = 0;
  119 +
  120 + /**
  121 + * @brief Wait for commands to complete.
  122 + * This method enables the user to wait for a set of tokens. An empty set means wait for all operations to complete.
  123 + * @param waitFor The number of milliseconds to wait for completetion of all commands.
  124 + * @param tokens The set of tokens to wait for.
  125 + * @return The number of milliseconds left.
  126 + */
  127 + virtual std::chrono::milliseconds waitForCompletion(std::chrono::milliseconds waitFor, const std::set<std::int32_t>& tokens) const = 0;
  128 +
  129 + /**
  130 + * @brief Check if a topic overlaps with existing topic subscriptions.
  131 + * @param topic The topic to test.
  132 + * @return true when overlap is detected, false otherwise.
  133 + */
  134 + virtual bool isOverlapping(const std::string& topic) const = 0;
  135 +
  136 + /**
  137 + * @brief Check if a topic overlaps with existing topic subscriptions.
  138 + * @param topic The topic to test.
  139 + * @param[out] existingTopic Contains the topic on which overlap is detected.
  140 + * @return true when overlap is detected. The existingTopic contains the topic on which overlap is detected, false otherwise.
  141 + */
  142 + virtual bool isOverlapping(const std::string& topic, std::string& existingTopic) const = 0;
  143 +
  144 + /**
  145 + * @return A vector with the pending operation tokens.
  146 + */
  147 + virtual std::vector<std::int32_t> pendingOperations() const = 0;
  148 +
  149 + /**
  150 + * @return true when client wrapper has pending subscriptions, false otherwise.
  151 + */
  152 + virtual bool hasPendingSubscriptions() const = 0;
  153 +
  154 + /**
  155 + * @return The operation result.
  156 + * @retval true operation has succeeded.
  157 + * @retval false operation has failed.
  158 + */
  159 + virtual boost::optional<bool> operationResult(std::int32_t token) const = 0;
  160 +};
  161 +
  162 +} // End namespace mqtt
  163 +} // End namespace components
  164 +} // End namespace osdev
  165 +
  166 +#endif // OSDEV_COMPONENTS_MQTT_IMQTTCLIENTIMPL_H
... ...
src/istatecallback.cpp 0 → 100644
  1 +++ a/src/istatecallback.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "istatecallback.h"
  20 +
  21 +#include <sstream>
  22 +
  23 +namespace osdev {
  24 +namespace components {
  25 +namespace mqtt {
  26 +
  27 +std::ostream& operator<<(std::ostream& os, StateEnum rhs)
  28 +{
  29 + switch (rhs) {
  30 + case StateEnum::Unknown:
  31 + os << "Unknown";
  32 + break;
  33 + case StateEnum::CommunicationFailure:
  34 + os << "CommunicationFailure";
  35 + break;
  36 + case StateEnum::GeneralFailure:
  37 + os << "GeneralFailure";
  38 + break;
  39 + case StateEnum::Good:
  40 + os << "Good";
  41 + break;
  42 + case StateEnum::Shutdown:
  43 + os << "Shutdown";
  44 + break;
  45 + case StateEnum::ConnectionFailure:
  46 + os << "ConnectionFailure";
  47 + break;
  48 + case StateEnum::Unregister:
  49 + os << "Unregister";
  50 + break;
  51 + }
  52 + return os;
  53 +}
  54 +
  55 +std::string createIdentifier(const std::string& idStatic, const ClientIdentifier& clientId, const IStateCallback* idDynamic)
  56 +{
  57 + std::ostringstream oss;
  58 + oss << idStatic;
  59 + if (!clientId.m_id.empty()) {
  60 + oss << "::" << clientId.m_id;
  61 + }
  62 + oss << " " << idDynamic;
  63 + return oss.str();
  64 +}
  65 +
  66 +IStateCallback::~IStateCallback()
  67 +{
  68 +}
  69 +
  70 +} // End namespace mqtt
  71 +} // End namespace components
  72 +} // End namespace osdev
... ...
src/istatecallback.h 0 → 100644
  1 +++ a/src/istatecallback.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_ISTATECALLBACK_H
  2 +#define OSDEV_COMPONENTS_MQTT_ISTATECALLBACK_H
  3 +
  4 +// std
  5 +#include <ostream>
  6 +
  7 +// boost
  8 +#include <boost/signals2.hpp>
  9 +
  10 +namespace osdev {
  11 +namespace components {
  12 +namespace mqtt {
  13 +
  14 +/*!
  15 + * \brief Struct introduces a typed client identifier.
  16 + */
  17 +struct ClientIdentifier
  18 +{
  19 + /*!
  20 + * \brief Construct a ClientIdentifier
  21 + * \param id The client id. Default is empty.
  22 + */
  23 + explicit ClientIdentifier(const std::string& id = "")
  24 + : m_id(id)
  25 + {
  26 + }
  27 +
  28 + const std::string m_id; ///< Client identifier
  29 +};
  30 +
  31 +/*!
  32 + * \brief Enumeration of state codes.
  33 + * The states Unknown, CommunicationFailure, GeneralFailure, Good
  34 + * and Shutdown are used to communicate state about the server
  35 + * that is connected to. The states ConnectionFailure and Unregister
  36 + * communicate state about the client.
  37 + */
  38 +enum class StateEnum
  39 +{
  40 + Unknown, ///< State of underlying data source is unknown.
  41 + CommunicationFailure, ///< No communication with underlying data source.
  42 + GeneralFailure, ///< Some failure in the underlying data source.
  43 + Good, ///< Underlying data source is available.
  44 + Shutdown, ///< Underlying data source is shutting down.
  45 + ConnectionFailure, ///< Client connection has failed.
  46 + Unregister ///< Client is being unregistered.
  47 +};
  48 +
  49 +/*!
  50 + * \brief Stream a StateEnum value
  51 + */
  52 +std::ostream& operator<<(std::ostream& os, StateEnum rhs);
  53 +
  54 +/*!
  55 + * \brief Type for identifying state change callbacks.
  56 + */
  57 +using StateChangeCallbackHandle = std::uint32_t;
  58 +
  59 +class IStateCallback;
  60 +
  61 +/*!
  62 + * \brief Create an id for an IStateCallback object.
  63 + * \param idStatic - Static part of the id (name of the class).
  64 + * \param clientId - Client identifier. Can contain information on how
  65 + * the client is used for instance. If the id is empty
  66 + * it is not used.
  67 + * \param idDynamic - Dynamic part (object address).
  68 + * \return identifier as string
  69 + */
  70 +std::string createIdentifier(const std::string& idStatic, const ClientIdentifier& clientId, const IStateCallback* idDynamic);
  71 +
  72 +/*!
  73 + * \brief State callback interface.
  74 + * When a client implements this interface it can be registered on a
  75 + * server in order to handle state changes from the underlying data source.
  76 + * \note When the client is destroyed it is expected that it unregisters itself by calling
  77 + * clearAllStateChangeCallbacks().
  78 + */
  79 +class IStateCallback
  80 +{
  81 +public:
  82 + using SigStateChange = boost::signals2::signal<void(const IStateCallback*, StateEnum)>;
  83 + using SlotStateChange = SigStateChange::slot_type;
  84 +
  85 + virtual ~IStateCallback();
  86 +
  87 + /*!
  88 + * \brief Get a string that identifies this interface.
  89 + */
  90 + virtual std::string clientId() const = 0;
  91 +
  92 + /*!
  93 + * \brief Register a callback function that is called when the status changes.
  94 + * \param cb - The callback function to register.
  95 + * \return handle to the registered callback.
  96 + * \note Make sure that the callback function lives longer than the IStateCallback object.
  97 + * Otherwise unregister callback function before it is destroyed.
  98 + */
  99 + virtual StateChangeCallbackHandle registerStateChangeCallback(const SlotStateChange& cb) = 0;
  100 +
  101 + /*!
  102 + * \brief Unregister a previously registered callback function.
  103 + * If the callback function was not registered nothing happens.
  104 + * \param handle - Identifies the callback function to to unregister.
  105 + */
  106 + virtual void unregisterStateChangeCallback(StateChangeCallbackHandle handle) = 0;
  107 +
  108 + /*!
  109 + * \brief Remove all the registered callback functions.
  110 + */
  111 + virtual void clearAllStateChangeCallbacks() = 0;
  112 +
  113 + /*!
  114 + * \brief retuns the latest state received.
  115 + */
  116 + virtual StateEnum state() const = 0;
  117 +};
  118 +
  119 +} // End namespace mqtt
  120 +} // End namespace components
  121 +} // End namespace osdev
  122 +
  123 +#endif // OSDEV_COMPONENTS_MQTT_ISTATECALLBACK_H
... ...
src/lockguard.h 0 → 100644
  1 +++ a/src/lockguard.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_LOCKGUARD_H
  20 +#define OSDEV_COMPONENTS_MQTT_LOCKGUARD_H
  21 +
  22 +// std
  23 +#include <mutex>
  24 +
  25 +// osdev::components::mqtt::measurement
  26 +#include "measure.h"
  27 +#include "utils.h"
  28 +
  29 +/**
  30 + * @brief Enable the lock measurements when macro MEASURE_LOCKS is defined.
  31 + * If the macro is not defined the NOP versions of the measure macros are used.
  32 + */
  33 +#ifdef MEASURE_LOCKS
  34 +#define OSDEV_COMPONENTS_MEASURELOCK OSDEV_COMPONENTS_MEASUREMENT_MEASURE
  35 +#define OSDEV_COMPONENTS_MEASURELOCK_SPECIFIC OSDEV_COMPONENTS_MEASUREMENT_MEASURE_SPECIFIC
  36 +#else
  37 +#define OSDEV_COMPONENTS_MEASURELOCK OSDEV_COMPONENTS_MEASUREMENT_MEASURE_NOP
  38 +#define OSDEV_COMPONENTS_MEASURELOCK_SPECIFIC OSDEV_COMPONENTS_MEASUREMENT_MEASURE_SPECIFIC_NOP
  39 +#endif
  40 +
  41 +/**
  42 + * @brief Create a lockguard for a given mutex in a specific context.
  43 + * @param mutexVariableName The name of the mutex.
  44 + * @param mutexObtainer The mutex to lock. This can also be a function that returns a mutex.
  45 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  46 + */
  47 +#define OSDEV_COMPONENTS_LOCKGUARD_OBTAINER(mutexVariableName, mutexObtainer) \
  48 + OSDEV_COMPONENTS_MEASURELOCK(LOCKGUARD, std::lock_guard<std::mutex> Lock__Guard__##mutexVariableName##__(mutexObtainer), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100) \
  49 + osdev::components::mqtt::apply_unused_parameters(Lock__Guard__##mutexVariableName##__);
  50 +
  51 +/**
  52 + * @brief Create a lockguard for a given mutex in a specific context.
  53 + * @param mutexVariableName The name of the mutex.
  54 + * @param mutexObtainer The mutex to lock. This can also be a function that returns a mutex.
  55 + * @param id The id that identifies this specific lock guard. Used for measuring timings.
  56 + * If MEASURE_LOCKS is enabled this method uses a specific histogram instance to record the timing of obtaining this lock.
  57 + */
  58 +#define OSDEV_COMPONENTS_LOCKGUARD_SPECIFIC_OBTAINER(mutexVariableName, mutexObtainer, id) \
  59 + OSDEV_COMPONENTS_MEASURELOCK_SPECIFIC(id, LOCKGUARD, std::lock_guard<std::mutex> Lock__Guard__##mutexVariableName##__(mutexObtainer), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100) \
  60 + osdev::components::mqtt::apply_unused_parameters(Lock__Guard__##mutexVariableName##__);
  61 +
  62 +/**
  63 + * @brief Create a lockguard for a given recursive mutex in a specific context.
  64 + * @param mutexVariableName The name of the mutex.
  65 + * @param mutexObtainer The mutex to lock. This can also be a function that returns a mutex.
  66 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  67 + */
  68 +#define OSDEV_COMPONENTS_RECURSIVELOCKGUARD_OBTAINER(mutexVariableName, mutexObtainer) \
  69 + OSDEV_COMPONENTS_MEASURELOCK(RECURSIVELOCKGUARD, std::lock_guard<std::recursive_mutex> Lock__Guard__##mutexVariableName##__(mutexObtainer), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100) \
  70 + osdev::components::mqtt::apply_unused_parameters(Lock__Guard__##mutexVariableName##__);
  71 +
  72 +/**
  73 + * @brief Create a lockguard for a given recursive mutex in a specific context.
  74 + * @param mutexVariableName The name of the mutex.
  75 + * @param mutexObtainer The mutex to lock. This can also be a function that returns a mutex.
  76 + * @param id The id that identifies this specific lock guard. Used for measuring timings.
  77 + * If MEASURE_LOCKS is enabled this method uses a specific histogram instance to record the timing of obtaining this lock.
  78 + */
  79 +#define OSDEV_COMPONENTS_RECURSIVELOCKGUARD_SPECIFIC_OBTAINER(mutexVariableName, mutexObtainer, id) \
  80 + OSDEV_COMPONENTS_MEASURELOCK_SPECIFIC(id, RECURSIVELOCKGUARD, std::lock_guard<std::recursive_mutex> Lock__Guard__##mutexVariableName##__(mutexVariableName), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100) \
  81 + osdev::components::mqtt::apply_unused_parameters(Lock__Guard__##mutexVariableName##__);
  82 +
  83 +/**
  84 + * @brief Create a lockguard for a given bare mutex in a specific context.
  85 + * @param mutexVariableName The name of the mutex.
  86 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  87 + */
  88 +#define OSDEV_COMPONENTS_LOCKGUARD(mutexVariableName) \
  89 + OSDEV_COMPONENTS_LOCKGUARD_OBTAINER(mutexVariableName, mutexVariableName)
  90 +
  91 +/**
  92 + * @brief Create a lockguard for a given bare mutex in a specific context.
  93 + * @param mutexVariableName The name of the mutex.
  94 + * @param id The id that identifies this specific lock guard. Used for measuring timings.
  95 + * If MEASURE_LOCKS is enabled this method uses a specific histogram instance to record the timing of obtaining this lock.
  96 + */
  97 +#define OSDEV_COMPONENTS_LOCKGUARD_SPECIFIC(mutexVariableName, id) \
  98 + OSDEV_COMPONENTS_LOCKGUARD_SPECIFIC_OBTAINER(mutexVariableName, mutexVariableName, id)
  99 +
  100 +/**
  101 + * @brief Create a lockguard for a given bare recursive mutex in a specific context.
  102 + * @param mutexVariableName The name of the mutex.
  103 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  104 + */
  105 +#define OSDEV_COMPONENTS_RECURSIVELOCKGUARD(mutexVariableName) \
  106 + OSDEV_COMPONENTS_RECURSIVELOCKGUARD_OBTAINER(mutexVariableName, mutexVariableName)
  107 +
  108 +/**
  109 + * @brief Create a lockguard for a given bare recursive mutex in a specific context.
  110 + * @param mutexVariableName The name of the mutex.
  111 + * @param id The id that identifies this specific lock guard. Used for measuring timings.
  112 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  113 + */
  114 +#define OSDEV_COMPONENTS_RECURSIVELOCKGUARD_SPECIFIC(mutexVariableName, id) \
  115 + OSDEV_COMPONENTS_RECURSIVELOCKGUARD_SPECIFIC_OBTAINER(mutexVariableName, mutexVariableName, id)
  116 +
  117 +/**
  118 + * @brief Defines the lock name that is used by the OSDEV_COMPONENTS_UNIQUELOCK_* macros
  119 + */
  120 +#define OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName) Unique__Lock__##mutexVariableName##__
  121 +
  122 +/**
  123 + * @brief Create a uniqeue lock for a given mutex.
  124 + * @param mutexVariableName The name of the mutex.
  125 + * @param mutexObtainer The mutex to lock. This can also be a function that returns a mutex.
  126 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  127 + */
  128 +#define OSDEV_COMPONENTS_UNIQUELOCK_CREATE_OBTAINER(mutexVariableName, mutexObtainer) \
  129 + OSDEV_COMPONENTS_MEASURELOCK(UNIQUELOCK_CREATE, std::unique_lock<std::mutex> OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName)(mutexObtainer), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100) \
  130 + osdev::components::mqtt::apply_unused_parameters(OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName));
  131 +
  132 +/**
  133 + * @brief Create a uniqeue lock for a given mutex.
  134 + * @param mutexVariableName The name of the mutex.
  135 + * @param mutexObtainer The mutex to lock. This can also be a function that returns mutex.
  136 + * @param id The id that identifies this specific lock guard. Used for measuring timings.
  137 + * If MEASURE_LOCKS is enabled this method uses a specific histogram instance to record the timing of obtaining this lock.
  138 + */
  139 +#define OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC_OBTAINER(mutexVariableName, mutexObtainer, id) \
  140 + OSDEV_COMPONENTS_MEASURELOCK_SPECIFIC(id, UNIQUELOCK_CREATE, std::unique_lock<std::mutex> OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName)(mutexObtainer), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100) \
  141 + osdev::components::mqtt::apply_unused_parameters(OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName));
  142 +
  143 +/**
  144 + * @brief Create a uniqeue lock for a given bare mutex.
  145 + * @param mutexVariableName The name of the mutex.
  146 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  147 + */
  148 +#define OSDEV_COMPONENTS_UNIQUELOCK_CREATE(mutexVariableName) \
  149 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_OBTAINER(mutexVariableName, mutexVariableName)
  150 +
  151 +/**
  152 + * @brief Create a uniqeue lock for a given bare mutex.
  153 + * @param mutexVariableName The name of the mutex.
  154 + * @param id The id that identifies this specific unique lock. Used for measuring timings.
  155 + * If MEASURE_LOCKS is enabled this method uses a specific histogram instance to record the timing of obtaining this lock.
  156 + */
  157 +#define OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC(mutexVariableName, id) \
  158 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC_OBTAINER(mutexVariableName, mutexVariableName, id)
  159 +
  160 +/**
  161 + * @brief Lock a given uniqeue lock.
  162 + * @param mutexVariableName The name of the mutex from which the lockname is derived.
  163 + * If MEASURE_LOCKS is enabled this method uses a static histogram to record the timing of obtaining the lock for all instances.
  164 + */
  165 +#define OSDEV_COMPONENTS_UNIQUELOCK_LOCK(mutexVariableName) \
  166 + OSDEV_COMPONENTS_MEASURELOCK(UNIQUELOCK_LOCK, OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName).lock(), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100)
  167 +
  168 +/**
  169 + * @brief Lock a given uniqeue lock.
  170 + * @param mutexVariableName The name of the mutex from which the lockname is derived.
  171 + * @param id The id that identifies this specific unique lock guard. Used for measuring timings.
  172 + * If MEASURE_LOCKS is enabled this method uses a specific histogram instance to record the timing of obtaining this lock.
  173 + */
  174 +#define OSDEV_COMPONENTS_UNIQUELOCK_LOCK_SPECIFIC(mutexVariableName, id) \
  175 + OSDEV_COMPONENTS_MEASURELOCK_SPECIFIC(id, UNIQUELOCK_LOCK, OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName).lock(), osdev::components::mqtt::measure_locking_tag, osdev::components::mqtt::MeasureLockingValue, 100)
  176 +
  177 +/**
  178 + * @brief Unlock a given uniqeue lock.
  179 + * @param mutexVariableName The name of the mutex from which the lockname is derived.
  180 + */
  181 +#define OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK(mutexVariableName) \
  182 + OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName).unlock();
  183 +
  184 +/**
  185 + * @brief Unlock a given uniqeue lock.
  186 + * @param mutexVariableName The name of the mutex from which the lockname is derived.
  187 + * @param id The id that identifies this specific unique lock guard. Can be used for measuring timings.
  188 + */
  189 +#define OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK_SPECIFIC(mutexVariableName, id) \
  190 + OSDEV_COMPONENTS_UNIQUELOCK(mutexVariableName).unlock();
  191 +
  192 +namespace osdev {
  193 +namespace components {
  194 +namespace mqtt {
  195 +
  196 +/**
  197 + * @brief Context tag type.
  198 + */
  199 +struct measure_locking_tag
  200 +{
  201 +};
  202 +
  203 +/**
  204 + * @brief Type for measuring lock timings
  205 + * The unit is microseconds and the values are expected between 0 and 100 microseconds.
  206 + * This type is used to construct a timing histogram.
  207 + */
  208 +struct MeasureLockingValue
  209 +{
  210 + /**
  211 + * @brief The value type of the timing value.
  212 + */
  213 + using value_type = std::chrono::microseconds;
  214 +
  215 + const value_type minValue = value_type(0); ///< Constant mininum value.
  216 + const value_type maxValue = value_type(100); ///< Constant maximum value.
  217 +
  218 + const char* unit = "us"; ///< The value unit.
  219 +};
  220 +
  221 +} // End namespace mqtt
  222 +} // End namespace components
  223 +} // End namespace osdev
  224 +
  225 +#endif // OSDEV_COMPONENTS_MQTT_LOCKGUARD_H
... ...
src/macrodefs.h 0 → 100644
  1 +++ a/src/macrodefs.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_MACRODEFS_H
  20 +#define OSDEV_COMPONENTS_MQTT_MACRODEFS_H
  21 +
  22 +#include <type_traits>
  23 +
  24 +#include "compiletimestring.h"
  25 +
  26 +/// @brief Helper macro to stringify a symbol
  27 +#define OSDEV_COMPONENTS_STRINGIFY(x) #x
  28 +
  29 +/// @brief Use this macro to safely stringify a symbol
  30 +/// This will also work for nested macro's
  31 +#define OSDEV_COMPONENTS_TOSTRING(x) OSDEV_COMPONENTS_STRINGIFY(x)
  32 +
  33 +/// @brief Helper macro to combine two symbols
  34 +#define OSDEV_COMPONENTS_COMBINER(x, y) x##y
  35 +
  36 +/// @brief Use this macro to safely combine two symbols
  37 +/// This will also work for nested macro's
  38 +#define OSDEV_COMPONENTS_COMBINE(x, y) OSDEV_COMPONENTS_COMBINER(x, y)
  39 +
  40 +/// @brief Macro that reduces a path to the basename
  41 +#define OSDEV_COMPONENTS_MANAGEDBASEFILENAME \
  42 + OSDEV_COMPONENTS_CSTRING_BOUNDED(__FILE__, osdev::components::rfind(__FILE__, '/') + 1, sizeof(__FILE__) - 1)
  43 +
  44 +/// @brief Compiletime test if an instance derives from a certain base class
  45 +#define OSDEV_COMPONENTS_DERIVESFROM(derived, baseType) \
  46 + static_assert(std::is_base_of<baseType, \
  47 + typename std::remove_pointer<typename std::remove_reference<decltype(derived)>::type>::type>::value, \
  48 + OSDEV_COMPONENTS_TOSTRING(derived) " must be derived from " OSDEV_COMPONENTS_TOSTRING(baseType))
  49 +
  50 +#endif //OSDEV_COMPONENTS_MQTT_MACRODEFS_H
... ...
src/measure.h 0 → 100644
  1 +++ a/src/measure.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_MEASUREMENT_MEASURE_H
  20 +#define OSDEV_COMPONENTS_MQTT_MEASUREMENT_MEASURE_H
  21 +
  22 +#include "macrodefs.h"
  23 +#include "histogramprovider.h"
  24 +#include "timemeasurement.h"
  25 +
  26 +namespace osdev {
  27 +namespace components {
  28 +namespace mqtt {
  29 +namespace measurement {
  30 +
  31 +inline std::string createId(const std::string& operationName, const std::string& dynamicId)
  32 +{
  33 + return operationName + (dynamicId.empty() ? std::string{} : std::string(" ") + dynamicId);
  34 +}
  35 +
  36 +} // End namespace measurement
  37 +} // End namespace mqtt
  38 +} // End namespace components
  39 +} // End namespace osdev
  40 +
  41 +#define OSDEV_COMPONENTS_MEASUREMENT_LOCATION \
  42 + (OSDEV_COMPONENTS_MANAGEDBASEFILENAME + OSDEV_COMPONENTS_CSTRING(":" OSDEV_COMPONENTS_TOSTRING(__LINE__))).chars
  43 +
  44 +/**
  45 + * @brief Macro helper that sets up a TimeMeasurement on an operation.
  46 + * The operation must have observable side effects otherwise the compiler might move the operation from in between the time measurements.
  47 + * @param operationName The name of the operation. This is part of the id under which the measurements are registered.
  48 + * @param dynamicId The dynamic identification of this measurement. This id makes different instances of the same operation unique.
  49 + * The dynamicId must be an expression that yields a string like object. The dynamic id is part of the id under which the
  50 + * measurements are registered.
  51 + * @param operation The operation to perform.
  52 + * @param context The context tag used to identify the HistogramProvider.
  53 + * @param valueType A datatype that can be used by HistogramProvider.
  54 + * @param numBuckets The number of buckets to use for the histogram.
  55 + * @param boolOnDestroy Boolean value that makes the measurement measure on destruction.
  56 + */
  57 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_HELPER(operationName, dynamicId, operation, context, valueType, numBuckets, boolOnDestroy) \
  58 + static const std::string OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(ID__, __LINE__), __) = \
  59 + std::string(OSDEV_COMPONENTS_CSTRING(#operationName).chars); \
  60 + osdev::components::mqtt::measurement::TimeMeasurement OSDEV_COMPONENTS_COMBINE( \
  61 + OSDEV_COMPONENTS_COMBINE(TM__, __LINE__), __)( \
  62 + osdev::components::mqtt::measurement::createId(OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(ID__, __LINE__), __), dynamicId), \
  63 + [](const std::string& id, std::chrono::steady_clock::time_point, std::chrono::microseconds, std::chrono::microseconds sinceLast) { \
  64 + osdev::components::mqtt::measurement::HistogramProvider<context, valueType, numBuckets>::instance().addValue(id, sinceLast); \
  65 + }, \
  66 + boolOnDestroy); \
  67 + operation;
  68 +
  69 +/**
  70 + * @brief Make a measurement before and after an operation.
  71 + * The operation must have observable side effects otherwise the compiler might move the operation from in between the time measurements.
  72 + * @param operationName The name under which the measurements are registered.
  73 + * @param operation The operation to perform.
  74 + * @param context The context tag used to identify the HistogramProvider.
  75 + * @param valueType A datatype that can be used by HistogramProvider.
  76 + * @param numBuckets The number of buckets to use for the histogram.
  77 + */
  78 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE(operationName, operation, context, valueType, numBuckets) \
  79 + OSDEV_COMPONENTS_MEASUREMENT_MEASURE_HELPER(operationName, OSDEV_COMPONENTS_MEASUREMENT_LOCATION, operation, context, valueType, numBuckets, false) \
  80 + OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(TM__, __LINE__), __).measure();
  81 +
  82 +/**
  83 + * @brief Make a measurement on a return operation.
  84 + * The operation must have observable side effects otherwise the compiler might move the operation from in between the time measurements.
  85 + * @param operationName The name under which the measurements are registered.
  86 + * @param operation The operation to perform.
  87 + * @param context The context tag used to identify the HistogramProvider.
  88 + * @param valueType A datatype that can be used by HistogramProvider.
  89 + * @param numBuckets The number of buckets to use for the histogram.
  90 + */
  91 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_ONDESTROY(operationName, operation, context, valueType, numBuckets) \
  92 + OSDEV_COMPONENTS_MEASUREMENT_MEASURE_HELPER(operationName, OSDEV_COMPONENTS_MEASUREMENT_LOCATION, operation, context, valueType, numBuckets, true)
  93 +
  94 +/**
  95 + * @brief Make a measurement on a return operation.
  96 + * The operation must have observable side effects otherwise the compiler might move the operation from in between the time measurements.
  97 + * @param operationName The name under which the measurements are registered.
  98 + * @param dynamicId An extra identification to discern between instances of the same operation. The dynamicId must be an expression that
  99 + * yields a string like object.
  100 + * @param operation The operation to perform.
  101 + * @param context The context tag used to identify the HistogramProvider.
  102 + * @param valueType A datatype that can be used by HistogramProvider.
  103 + * @param numBuckets The number of buckets to use for the histogram.
  104 + */
  105 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_SPECIFIC_ONDESTROY(operationName, dynamicId, operation, context, valueType, numBuckets) \
  106 + OSDEV_COMPONENTS_MEASUREMENT_MEASURE_HELPER(operationName, dynamicId, operation, context, valueType, numBuckets, true)
  107 +
  108 +/**
  109 + * @brief Nop version that only performs the operation.
  110 + */
  111 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_ONDESTROY_NOP(operationName, operation, context, valueType, numBuckets) operation;
  112 +
  113 +/**
  114 + * @brief Nop version that only performs the operation.
  115 + */
  116 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_NOP(operationName, operation, context, valueType, numBuckets) operation;
  117 +
  118 +/**
  119 + * @brief Nop version that only performs the operation.
  120 + */
  121 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_SPECIFIC_ONDESTROY_NOP(operationName, dynamicId, operation, context, valueType, numBuckets) operation;
  122 +
  123 +/**
  124 + * @brief Make a measurement before and after an operation for a specific instance of the operation.
  125 + * The operation must have observable side effects otherwise the compiler might move the operation from in between the time measurements.
  126 + * @param dynamicId An extra identification to discern between instances of the same operation.
  127 + * @param operationName The name under which the measurements are registered.
  128 + * @param operation The operation to perform.
  129 + * @param context The context tag used to identify the HistogramProvider.
  130 + * @param valueType A datatype that can be used by HistogramProvider.
  131 + * @param numBuckets The number of buckets to use for the histogram.
  132 + */
  133 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_SPECIFIC(dynamicId, operationName, operation, context, valueType, numBuckets) \
  134 + auto OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(IDPREFIX__, __LINE__), __) = \
  135 + OSDEV_COMPONENTS_MANAGEDBASEFILENAME + OSDEV_COMPONENTS_CSTRING(":" OSDEV_COMPONENTS_TOSTRING(__LINE__) ", function "); \
  136 + auto OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(IDPOSTFIX__, __LINE__), __) = OSDEV_COMPONENTS_CSTRING(", " #operationName " "); \
  137 + static const std::string OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(ID__, __LINE__), __) = \
  138 + std::string(OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(IDPREFIX__, __LINE__), __).chars) + \
  139 + std::string(__func__) + \
  140 + OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(IDPOSTFIX__, __LINE__), __).chars; \
  141 + osdev::components::mqtt::measurement::TimeMeasurement OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(TM__, __LINE__), __)( \
  142 + OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(ID__, __LINE__), __) + dynamicId, \
  143 + [](const std::string& id, std::chrono::steady_clock::time_point, std::chrono::microseconds, std::chrono::microseconds sinceLast) { \
  144 + osdev::components::mqtt::measurement::HistogramProvider<context, valueType, numBuckets>::instance().addValue(id, sinceLast); \
  145 + }, \
  146 + false); \
  147 + operation; \
  148 + OSDEV_COMPONENTS_COMBINE(OSDEV_COMPONENTS_COMBINE(TM__, __LINE__), __).measure();
  149 +
  150 +/**
  151 + * @brief Nop version that only performs the operation.
  152 + */
  153 +#define OSDEV_COMPONENTS_MEASUREMENT_MEASURE_SPECIFIC_NOP(dynamicId, operationName, operation, context, valueType, numBuckets) operation;
  154 +
  155 +#endif // OSDEV_COMPONENTS_MEASUREMENT_MEASUREMENT_H
... ...
src/metaprogrammingdefs.h 0 → 100644
  1 +++ a/src/metaprogrammingdefs.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_METAPROGRAMMINGDEFS_H
  20 +#define OSDEV_COMPONENTS_MQTT_METAPROGRAMMINGDEFS_H
  21 +
  22 +#include <cstddef> // for std::size_t
  23 +#include <sstream>
  24 +#include <string>
  25 +
  26 +#include "utils.h"
  27 +
  28 +/**
  29 + * @brief Create a type trait that checks for the existence of a member in a struct.
  30 + * @param memberName The member name
  31 + *
  32 + * A call such as OSDEV_COMPONENTS_HASMEMBER_TRAIT(onSuccess) leads to a type trait has_onSuccess.
  33 + */
  34 +#define OSDEV_COMPONENTS_HASMEMBER_TRAIT(memberName) \
  35 + template <typename T, typename = void> \
  36 + struct has_##memberName : public std::false_type \
  37 + { \
  38 + }; \
  39 + \
  40 + template <typename T> \
  41 + struct has_##memberName<T, osdev::components::mqtt::void_t<decltype(std::declval<T>().memberName)>> : public std::true_type \
  42 + { \
  43 + };
  44 +
  45 +/**
  46 + * @brief Create a type trait that checks for the existence of a member method with no parameters.
  47 + * @param methodName The method name
  48 + * @param postfix An extra postfix that is added to the type trait struct.
  49 + * This makes it possible create type traits that check for
  50 + * overloaded methods.
  51 + *
  52 + * A call such as OSDEV_COMPONENTS_HASMETHOD_TRAIT(capacity,1) leads to a type trait has_capacity1.
  53 + */
  54 +#define OSDEV_COMPONENTS_HASMETHOD_TRAIT(methodName, postfix) \
  55 + template <typename T, typename = void> \
  56 + struct has_##methodName##postfix : public std::false_type \
  57 + { \
  58 + }; \
  59 + \
  60 + template <typename T> \
  61 + struct has_##methodName##postfix<T, osdev::components::mqtt::void_t<decltype(std::declval<T>().methodName())>> : public std::true_type \
  62 + { \
  63 + };
  64 +
  65 +/**
  66 + * @brief Create a type trait that checks for the existence of a member method with 1 or more parameters.
  67 + * @param methodName The method name
  68 + * @param postfix An extra postfix that is added to the type trait struct.
  69 + * This makes it possible create type traits that check for
  70 + * overloaded methods.
  71 + * @param ... Variable number of arguments. These can be values, but also declval constructs such as std::declval<MyClass>().
  72 + *
  73 + * A call such as OSDEV_COMPONENTS_HASMETHODWP_TRAIT(capacity,,"string") leads to a type trait has_capacity that
  74 + * checks for the existence of a method capacity that accepts a const char pointer.
  75 + */
  76 +#define OSDEV_COMPONENTS_HASMETHODWP_TRAIT(methodName, postfix, ...) \
  77 + template <typename T, typename = void> \
  78 + struct has_##methodName##postfix : public std::false_type \
  79 + { \
  80 + }; \
  81 + \
  82 + template <typename T> \
  83 + struct has_##methodName##postfix<T, osdev::components::mqtt::void_t<decltype(std::declval<T>().methodName(__VA_ARGS__))>> : public std::true_type \
  84 + { \
  85 + };
  86 +
  87 +namespace osdev {
  88 +namespace components {
  89 +namespace mqtt {
  90 +
  91 +/**
  92 + * @brief Used to detect ill formed types in a SFINAE context.
  93 + * If the parameter pack Ts contains an invalid type, struct make_void cannot be expanded.
  94 + */
  95 +template <typename... Ts>
  96 +struct make_void
  97 +{
  98 + using type = void;
  99 +};
  100 +
  101 +/**
  102 + * @brief Introduced in c++17 (but will also work in c++11)
  103 + */
  104 +template <typename... Ts>
  105 +using void_t = typename make_void<Ts...>::type;
  106 +
  107 +/// @brief Build an index list that can be used to traverse another list (f.i. a char array).
  108 +/// Recursive definition.
  109 +/// Start with an empty indices list. Build the indices list recursively.
  110 +/// upper-1, indices... becomes the indices... in the next recursive call.
  111 +template <std::size_t lower, std::size_t upper,
  112 + template <std::size_t...> class meta_functor, std::size_t... Is>
  113 +struct apply_bounded_range
  114 +{
  115 + typedef typename apply_bounded_range<lower, upper - 1, meta_functor, upper - 1, Is...>::result result;
  116 +};
  117 +
  118 +/// @brief Terminator of apply_bounded_range.
  119 +/// Terminates when the upper bound (which runs) is equal to the lower bound.
  120 +template <std::size_t lower, template <std::size_t...> class meta_functor, std::size_t... Is>
  121 +struct apply_bounded_range<lower, lower, meta_functor, Is...>
  122 +{
  123 + typedef typename meta_functor<Is...>::result result;
  124 +};
  125 +
  126 +/**
  127 + * @brief Helper method to perform static_assert on the expected count of a parameter pack.
  128 + */
  129 +template <std::size_t expectedCount, typename... Args>
  130 +void static_assert_count()
  131 +{
  132 + constexpr std::size_t actualCount = sizeof...(Args);
  133 + static_assert(expectedCount == actualCount, "Unexpected parameter count.");
  134 +}
  135 +
  136 +/**
  137 + * @brief Helper method to convert a type to std::string.
  138 + * @param d_first The output iterator to write the converted string to.
  139 + * @param arg The argument to convert to std::string.
  140 + */
  141 +template <typename OutputIt, typename T>
  142 +int apply_to_string_one(OutputIt d_first, const T& arg)
  143 +{
  144 + std::ostringstream ss;
  145 + ss << arg;
  146 + *d_first++ = ss.str();
  147 + return 0;
  148 +}
  149 +
  150 +/**
  151 + * @brief Converts all parameters to std::string.
  152 + * @param d_first The output iterator to write the converted strings to.
  153 + * @param args The arguments to convert to std::string.
  154 + */
  155 +template <typename OutputIt, typename... Args>
  156 +void apply_to_string(OutputIt d_first, Args&&... args)
  157 +{
  158 + // d_first is not used when args parameter pack is empty.
  159 + apply_unused_parameters(d_first);
  160 +
  161 + // We need to use the expand_type trick in order to be able to use an initializer list
  162 + // so that we can call the method for every variadic argument
  163 + using expand_type = int[];
  164 + // Array must be initialized with values. Add a single value so that the array can be initialized when the parameter pack is empty.
  165 + expand_type et{ 0, apply_to_string_one(d_first, std::forward<Args>(args))... };
  166 + apply_unused_parameters(et);
  167 +}
  168 +
  169 +/**
  170 + * @brief Helper that removes const, volatile and reference from a type.
  171 + * @note Defined in std C++20.
  172 + */
  173 +template <typename T>
  174 +struct remove_cvref
  175 +{
  176 + using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
  177 +};
  178 +
  179 +} // End namespace mqtt
  180 +} // End namespace components
  181 +} // End namespace osdev
  182 +
  183 +#endif // OSDEV_COMPONENTS_MQTT_METAPROGRAMMINGDEFS_H
... ...
src/mqttclient.cpp 0 → 100644
  1 +++ a/src/mqttclient.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "mqttclient.h"
  20 +
  21 +// osdev::components::mqtt
  22 +#include "clientpaho.h"
  23 +#include "mqttutil.h"
  24 +#include "mqttidgenerator.h"
  25 +#include "mqtttypeconverter.h"
  26 +
  27 +// mlogic::common
  28 +#include "lockguard.h"
  29 +#include "uriparser.h"
  30 +
  31 +// std
  32 +#include <numeric>
  33 +#include <iostream>
  34 +
  35 +using namespace osdev::components::mqtt;
  36 +
  37 +namespace {
  38 +/**
  39 + * @brief Generate a unique client id so that a new client does not steal the connection from an existing client.
  40 + * @param clientId The base client identifier.
  41 + * @param clientNumber The next client that is derived from the base client identifier.
  42 + * @return A unique client identifier string.
  43 + */
  44 +std::string generateUniqueClientId(const std::string& clientId, std::size_t clientNumber)
  45 +{
  46 + return clientId + "_" + std::to_string(clientNumber) + "_" + MqttTypeConverter::toStdString(MqttIdGenerator::generate());
  47 +}
  48 +
  49 +} // namespace
  50 +
  51 +MqttClient::MqttClient(const std::string& _clientId, const std::function<void(const Token& token)>& deliveryCompleteCallback)
  52 + : m_interfaceMutex()
  53 + , m_internalMutex()
  54 + , m_endpoint()
  55 + , m_clientId(_clientId)
  56 + , m_activeTokens()
  57 + , m_activeTokensCV()
  58 + , m_deliveryCompleteCallback(deliveryCompleteCallback)
  59 + , m_serverState(this)
  60 + , m_principalClient()
  61 + , m_additionalClients()
  62 + , m_eventQueue(_clientId)
  63 + , m_workerThread(std::thread(&MqttClient::eventHandler, this))
  64 +{
  65 +}
  66 +
  67 +MqttClient::~MqttClient()
  68 +{
  69 + // DebugLogToFIle ("MqttClient", "%1 - dtor", m_clientId);
  70 + {
  71 + // DebugLogToFIle ("MqttClient", "%1 - disconnect", m_clientId);
  72 + this->disconnect();
  73 + decltype(m_principalClient) principalClient{};
  74 +
  75 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  76 + // DebugLogToFIle ("MqttClient", "%1 - cleanup principal client", m_clientId);
  77 + m_principalClient.swap(principalClient);
  78 + }
  79 + // DebugLogToFIle ("MqttClient", "%1 - dtor stop queue", m_clientId);
  80 + m_eventQueue.stop();
  81 + if (m_workerThread.joinable()) {
  82 + m_workerThread.join();
  83 + }
  84 + // DebugLogToFIle ("MqttClient", "%1 - dtor ready", m_clientId);
  85 +}
  86 +
  87 +std::string MqttClient::clientId() const
  88 +{
  89 + return m_clientId;
  90 +}
  91 +
  92 +StateChangeCallbackHandle MqttClient::registerStateChangeCallback(const SlotStateChange& cb)
  93 +{
  94 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  95 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  96 + return m_serverState.registerStateChangeCallback(cb);
  97 +}
  98 +
  99 +void MqttClient::unregisterStateChangeCallback(StateChangeCallbackHandle handle)
  100 +{
  101 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  102 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  103 + m_serverState.unregisterStateChangeCallback(handle);
  104 +}
  105 +
  106 +void MqttClient::clearAllStateChangeCallbacks()
  107 +{
  108 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  109 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  110 + m_serverState.clearAllStateChangeCallbacks();
  111 +}
  112 +
  113 +StateEnum MqttClient::state() const
  114 +{
  115 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  116 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  117 + return m_serverState.state();
  118 +}
  119 +
  120 +void MqttClient::connect(const std::string& host, int port, const Credentials& credentials)
  121 +{
  122 +
  123 + osdev::components::mqtt::ParsedUri _endpoint = {
  124 + { "scheme", "tcp" },
  125 + { "user", credentials.username() },
  126 + { "password", credentials.password() },
  127 + { "host", host },
  128 + { "port", std::to_string(port) }
  129 + };
  130 + this->connect(UriParser::toString(_endpoint));
  131 +}
  132 +
  133 +void MqttClient::connect(const std::string& _endpoint)
  134 +{
  135 + // InfoLogToFIle ("MqttClient", "%1 - Request connect", m_clientId);
  136 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  137 + IMqttClientImpl* client(nullptr);
  138 + {
  139 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  140 + if (m_principalClient && m_principalClient->connectionStatus() != ConnectionStatus::Disconnected) {
  141 + if (_endpoint == m_endpoint)
  142 + {
  143 + // idempotent
  144 + return;
  145 + }
  146 + else
  147 + {
  148 + // ErrorLogToFIle ("MqttClient", "%1 - Cannot connect to different endpoint. Disconnect first.", m_clientId);
  149 + // Normally a throw (Yuck!) (MqttException, "Cannot connect while already connected");
  150 + }
  151 + }
  152 + m_endpoint = _endpoint;
  153 + if (!m_principalClient) {
  154 + std::string derivedClientId(generateUniqueClientId(m_clientId, 1));
  155 + m_principalClient = std::make_unique<ClientPaho>(
  156 + m_endpoint,
  157 + derivedClientId,
  158 + [this](const std::string& id, ConnectionStatus cs) { this->connectionStatusChanged(id, cs); },
  159 + [this](std::string clientId, std::int32_t token) { this->deliveryComplete(clientId, token); });
  160 + }
  161 + client = m_principalClient.get();
  162 + }
  163 + client->connect(true);
  164 +}
  165 +
  166 +void MqttClient::disconnect()
  167 +{
  168 + // InfoLogToFIle ("MqttClient", "%1 - Request disconnect", m_clientId);
  169 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  170 +
  171 + decltype(m_additionalClients) additionalClients{};
  172 + std::vector<IMqttClientImpl*> clients{};
  173 + {
  174 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  175 + if (!m_principalClient || m_principalClient->connectionStatus() == ConnectionStatus::Disconnected || m_principalClient->connectionStatus() == ConnectionStatus::DisconnectInProgress) {
  176 + return;
  177 + }
  178 + m_additionalClients.swap(additionalClients);
  179 +
  180 + for (const auto& c : additionalClients) {
  181 + clients.push_back(c.get());
  182 + }
  183 + clients.push_back(m_principalClient.get());
  184 + }
  185 +
  186 + // DebugLogToFIle ("MqttClient", "%1 - Unsubscribe and disconnect clients", m_clientId);
  187 + for (auto& cl : clients) {
  188 + cl->unsubscribeAll();
  189 + }
  190 + this->waitForCompletionInternal(clients, std::chrono::milliseconds(2000), std::set<Token>{});
  191 +
  192 + for (auto& cl : clients) {
  193 + cl->disconnect(false, 2000);
  194 + }
  195 + this->waitForCompletionInternal(clients, std::chrono::milliseconds(3000), std::set<Token>{});
  196 +}
  197 +
  198 +Token MqttClient::publish(const MqttMessage& message, int qos)
  199 +{
  200 + if (hasWildcard(message.topic()) || !isValidTopic(message.topic())) {
  201 + return Token(m_clientId, -1);
  202 + }
  203 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  204 + IMqttClientImpl* client(nullptr);
  205 + {
  206 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  207 + if (!m_principalClient || m_principalClient->connectionStatus() == ConnectionStatus::Disconnected)
  208 + {
  209 + if( !m_principalClient )
  210 + {
  211 + std::cout << "Principal client not initialized" << std::endl;
  212 + }
  213 +
  214 + if( m_principalClient->connectionStatus() == ConnectionStatus::Disconnected )
  215 + {
  216 + std::cout << "Unable to publish, not connected.." << std::endl;
  217 + }
  218 + // ErrorLogToFIle ("MqttClient", "%1 - Unable to publish, not connected", m_clientId);
  219 + // Throw (MqttException, "Not connected");
  220 + }
  221 + client = m_principalClient.get();
  222 + }
  223 + return Token(client->clientId(), client->publish(message, qos));
  224 +}
  225 +
  226 +Token MqttClient::subscribe(const std::string& topic, int qos, const std::function<void(MqttMessage)>& cb)
  227 +{
  228 + // DebugLogToFIle ("MqttClient", "%1 - Subscribe to topic %2 with qos %3", m_clientId, topic, qos);
  229 + // OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  230 + bool clientFound = false;
  231 + IMqttClientImpl* client(nullptr);
  232 + {
  233 + // OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  234 + if (!m_principalClient || m_principalClient->connectionStatus() == ConnectionStatus::Disconnected)
  235 + {
  236 + // ErrorLogToFIle ("MqttClient", "%1 - Unable to subscribe, not connected", m_clientId);
  237 + // throw (?)(MqttException, "Not connected");
  238 + }
  239 + if (!m_principalClient->isOverlapping(topic)) {
  240 + client = m_principalClient.get();
  241 + clientFound = true;
  242 + }
  243 + else {
  244 + for (const auto& c : m_additionalClients) {
  245 + if (!c->isOverlapping(topic)) {
  246 + client = c.get();
  247 + clientFound = true;
  248 + break;
  249 + }
  250 + }
  251 + }
  252 + if (!clientFound) {
  253 + // DebugLogToFIle ("MqttClient", "%1 - Creating new ClientPaho instance for subscription on topic %2", m_clientId, topic);
  254 + std::string derivedClientId(generateUniqueClientId(m_clientId, m_additionalClients.size() + 2)); // principal client is nr 1.
  255 + m_additionalClients.emplace_back(std::make_unique<ClientPaho>(
  256 + m_endpoint,
  257 + derivedClientId,
  258 + [this](const std::string& id, ConnectionStatus cs) { this->connectionStatusChanged(id, cs); },
  259 + std::function<void(const std::string&, std::int32_t)>{}));
  260 + client = m_additionalClients.back().get();
  261 + }
  262 + }
  263 + if (!clientFound) {
  264 + client->connect(true);
  265 + }
  266 + return Token{ client->clientId(), client->subscribe(topic, qos, cb) };
  267 +}
  268 +
  269 +std::set<Token> MqttClient::unsubscribe(const std::string& topic, int qos)
  270 +{
  271 + // DebugLogToFIle ("MqttClient", "%1 - Unsubscribe from topic %2 with qos %3", m_clientId, topic, qos);
  272 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  273 + std::vector<IMqttClientImpl*> clients{};
  274 + {
  275 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  276 + if (!m_principalClient || m_principalClient->connectionStatus() == ConnectionStatus::Disconnected) {
  277 + // ErrorLogToFIle ("MqttClient", "%1 - Unable to unsubscribe, not connected", m_clientId);
  278 + // Throw (MqttException, "Not connected");
  279 + }
  280 + clients.push_back(m_principalClient.get());
  281 + for (const auto& c : m_additionalClients) {
  282 + clients.push_back(c.get());
  283 + }
  284 + }
  285 + std::set<Token> tokens{};
  286 + for (auto* c : clients) {
  287 + auto token = c->unsubscribe(topic, qos);
  288 + if (-1 != token) {
  289 + tokens.emplace(Token{ c->clientId(), token });
  290 + }
  291 + }
  292 + return tokens;
  293 +}
  294 +
  295 +bool MqttClient::waitForCompletion(std::chrono::milliseconds waitFor) const
  296 +{
  297 + return this->waitForCompletion(waitFor, std::set<Token>{});
  298 +}
  299 +
  300 +bool MqttClient::waitForCompletion(std::chrono::milliseconds waitFor, const Token& token) const
  301 +{
  302 + if (-1 == token.token()) {
  303 + return false;
  304 + }
  305 + return this->waitForCompletion(waitFor, std::set<Token>{ token });
  306 +}
  307 +
  308 +bool MqttClient::waitForCompletion(std::chrono::milliseconds waitFor, const std::set<Token>& tokens) const
  309 +{
  310 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  311 + std::vector<IMqttClientImpl*> clients{};
  312 + {
  313 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  314 + if (m_principalClient) {
  315 + clients.push_back(m_principalClient.get());
  316 + }
  317 + for (const auto& c : m_additionalClients) {
  318 + clients.push_back(c.get());
  319 + }
  320 + }
  321 + return waitForCompletionInternal(clients, waitFor, tokens);
  322 +}
  323 +
  324 +boost::optional<bool> MqttClient::commandResult(const Token& token) const
  325 +{
  326 + OSDEV_COMPONENTS_LOCKGUARD(m_interfaceMutex);
  327 + std::vector<IMqttClientImpl*> clients{};
  328 + {
  329 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  330 + if (m_principalClient) {
  331 + clients.push_back(m_principalClient.get());
  332 + }
  333 + for (const auto& c : m_additionalClients) {
  334 + clients.push_back(c.get());
  335 + }
  336 + }
  337 + for (auto* c : clients) {
  338 + if (token.clientId() == c->clientId()) {
  339 + return c->operationResult(token.token());
  340 + }
  341 + }
  342 + return boost::optional<bool>{};
  343 +}
  344 +
  345 +std::string MqttClient::endpoint() const
  346 +{
  347 + auto ep = UriParser::parse(m_endpoint);
  348 + if (ep.find("user") != ep.end()) {
  349 + ep["user"].clear();
  350 + }
  351 + if (ep.find("password") != ep.end()) {
  352 + ep["password"].clear();
  353 + }
  354 + return UriParser::toString(ep);
  355 +}
  356 +
  357 +void MqttClient::connectionStatusChanged(const std::string& id, ConnectionStatus cs)
  358 +{
  359 + (void)id;
  360 + (void)cs;
  361 + // DebugLogToFIle ("MqttClient", "%1 - connection status of wrapped client %2 changed to %3", m_clientId, id, cs);
  362 + IMqttClientImpl* principalClient{ nullptr };
  363 + std::vector<IMqttClientImpl*> clients{};
  364 + std::vector<ConnectionStatus> connectionStates{};
  365 + {
  366 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  367 + if (!m_principalClient) {
  368 + return;
  369 + }
  370 + if (m_principalClient) {
  371 + principalClient = m_principalClient.get();
  372 + clients.push_back(principalClient);
  373 + connectionStates.push_back(m_principalClient->connectionStatus());
  374 + }
  375 + for (const auto& c : m_additionalClients) {
  376 + clients.push_back(c.get());
  377 + connectionStates.push_back(c->connectionStatus());
  378 + }
  379 + }
  380 + auto newState = determineState(connectionStates);
  381 + bool resubscribe = (StateEnum::ConnectionFailure == m_serverState.state() && StateEnum::Good == newState);
  382 + if (resubscribe) {
  383 + {
  384 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  385 + m_activeTokens.clear();
  386 + }
  387 + for (auto* cl : clients) {
  388 + try {
  389 + cl->resubscribe();
  390 + }
  391 + catch (const std::exception& e) {
  392 + // ErrorLogToFIle ("MqttClient", "%1 - resubscribe on wrapped client %2 in context of connection status change in wrapped client %3 failed : %4", m_clientId, cl->clientId(), id, e.what());
  393 + }
  394 + }
  395 + m_activeTokensCV.notify_all();
  396 + }
  397 +
  398 + // The server state change and a possible resubscription are done in the context of the MqttClient worker thread
  399 + // The wrapper is free to pick up new work such as the acknowledment of the just recreated subscriptions.
  400 + this->pushEvent([this, resubscribe, clients, principalClient, newState]() {
  401 + if (resubscribe) {
  402 + // Just wait for the subscription commands to complete. We do not use waitForCompletionInternal because that call will always timeout when there are active tokens.
  403 + // Active tokens are removed typically by work done on the worker thread. The wait action is also performed on the worker thread.
  404 + auto waitFor = std::chrono::milliseconds(1000);
  405 + if (!waitForCompletionInternalClients(clients, waitFor, std::set<Token>{})) {
  406 + if (std::accumulate(clients.begin(), clients.end(), false, [](bool hasPending, IMqttClientImpl* client) { return hasPending || client->hasPendingSubscriptions(); })) {
  407 + // WarnLogToFIle ("MqttClient", "%1 - subscriptions are not recovered within timeout.", m_clientId)
  408 + }
  409 + }
  410 + if (principalClient) {
  411 + try {
  412 + principalClient->publishPending();
  413 + }
  414 + catch (const std::exception& e) {
  415 + // ErrorLogToFIle ("MqttClient", "%1 - publishPending on wrapped client %2 failed : %3", m_clientId, principalClient->clientId(), e.what());
  416 + }
  417 + }
  418 + }
  419 + m_serverState.emitStateChanged(newState);
  420 + });
  421 +}
  422 +
  423 +void MqttClient::deliveryComplete(const std::string& _clientId, std::int32_t token)
  424 +{
  425 + if (!m_deliveryCompleteCallback) {
  426 + return;
  427 + }
  428 +
  429 + Token t(_clientId, token);
  430 + {
  431 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  432 + if (!m_activeTokens.insert(t).second) {
  433 + // This should not happen. This means that some callback on the wrapper never came.
  434 + // ErrorLogToFIle ("MqttClient", "%1 -deliveryComplete, token %1 is already active", m_clientId, t);
  435 + }
  436 + }
  437 + this->pushEvent([this, t]() {
  438 + OSDEV_COMPONENTS_SCOPEGUARD(m_activeTokens, [this, &t]() {
  439 + {
  440 + OSDEV_COMPONENTS_LOCKGUARD(m_internalMutex);
  441 + m_activeTokens.erase(t);
  442 + }
  443 + m_activeTokensCV.notify_all();
  444 + });
  445 + m_deliveryCompleteCallback(t);
  446 + });
  447 +}
  448 +
  449 +bool MqttClient::waitForCompletionInternal(const std::vector<IMqttClientImpl*>& clients, std::chrono::milliseconds waitFor, const std::set<Token>& tokens) const
  450 +{
  451 + if (!waitForCompletionInternalClients(clients, waitFor, tokens)) {
  452 + return false;
  453 + }
  454 + std::unique_lock<std::mutex> lck(m_internalMutex);
  455 + return m_activeTokensCV.wait_for(lck, waitFor, [this, &tokens]() {
  456 + if (tokens.empty()) { // wait for all operations to end
  457 + return m_activeTokens.empty();
  458 + }
  459 + else if (tokens.size() == 1) {
  460 + return m_activeTokens.find(*tokens.cbegin()) == m_activeTokens.end();
  461 + }
  462 + std::vector<Token> intersect{};
  463 + std::set_intersection(m_activeTokens.begin(), m_activeTokens.end(), tokens.begin(), tokens.end(), std::back_inserter(intersect));
  464 + return intersect.empty(); });
  465 +}
  466 +
  467 +bool MqttClient::waitForCompletionInternalClients(const std::vector<IMqttClientImpl*>& clients, std::chrono::milliseconds& waitFor, const std::set<Token>& tokens) const
  468 +{
  469 + for (auto* c : clients) {
  470 + std::set<std::int32_t> clientTokens{};
  471 + for (const auto& token : tokens) {
  472 + if (c->clientId() == token.clientId()) {
  473 + clientTokens.insert(token.token());
  474 + }
  475 + }
  476 + if (tokens.empty() || !clientTokens.empty()) {
  477 + waitFor -= c->waitForCompletion(waitFor, clientTokens);
  478 + }
  479 + }
  480 + if (waitFor > std::chrono::milliseconds(0)) {
  481 + return true;
  482 + }
  483 + waitFor = std::chrono::milliseconds(0);
  484 + return false;
  485 +}
  486 +
  487 +StateEnum MqttClient::determineState(const std::vector<ConnectionStatus>& connectionStates)
  488 +{
  489 + std::size_t unknownStates = 0;
  490 + std::size_t goodStates = 0;
  491 + std::size_t reconnectStates = 0;
  492 + for (auto cst : connectionStates) {
  493 + switch (cst) {
  494 + case ConnectionStatus::Disconnected:
  495 + ++unknownStates;
  496 + break;
  497 + case ConnectionStatus::DisconnectInProgress: // count as unknown because we don't want resubscribes to trigger when a wrapper is in this state.
  498 + ++unknownStates;
  499 + break;
  500 + case ConnectionStatus::ConnectInProgress: // count as unknown because the wrapper is not connected yet.
  501 + ++unknownStates;
  502 + break;
  503 + case ConnectionStatus::ReconnectInProgress:
  504 + ++reconnectStates;
  505 + break;
  506 + case ConnectionStatus::Connected:
  507 + ++goodStates;
  508 + break;
  509 + }
  510 + }
  511 + auto newState = StateEnum::Unknown;
  512 + if (reconnectStates > 0) {
  513 + newState = StateEnum::ConnectionFailure;
  514 + }
  515 + else if (unknownStates > 0) {
  516 + newState = StateEnum::Unknown;
  517 + }
  518 + else if (connectionStates.size() == goodStates) {
  519 + newState = StateEnum::Good;
  520 + }
  521 + return newState;
  522 +}
  523 +
  524 +void MqttClient::pushEvent(std::function<void()> ev)
  525 +{
  526 + m_eventQueue.push(ev);
  527 +}
  528 +
  529 +void MqttClient::eventHandler()
  530 +{
  531 + // InfoLogToFIle ("MqttClient", "%1 - starting event handler", m_clientId);
  532 + for (;;) {
  533 + std::vector<std::function<void()>> events;
  534 + if (!m_eventQueue.pop(events))
  535 + {
  536 + break;
  537 + }
  538 + for (const auto& ev : events)
  539 + {
  540 + ev();
  541 + }
  542 + }
  543 + // InfoLogToFIle ("MqttClient", "%1 - leaving event handler", m_clientId);
  544 +}
... ...
src/mqttclient.h 0 → 100644
  1 +++ a/src/mqttclient.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MQTTCLIENT_H
  2 +#define OSDEV_COMPONENTS_MQTT_MQTTCLIENT_H
  3 +
  4 +// std
  5 +#include <condition_variable>
  6 +#include <memory>
  7 +#include <mutex>
  8 +#include <set>
  9 +#include <thread>
  10 +#include <vector>
  11 +
  12 +// osdev::components::mqtt
  13 +#include "synchronizedqueue.h"
  14 +#include "istatecallback.h"
  15 +#include "serverstate.h"
  16 +
  17 +#include "imqttclient.h"
  18 +
  19 +namespace osdev {
  20 +namespace components {
  21 +namespace mqtt {
  22 +
  23 +// Forward definition
  24 +class IMqttClientImpl;
  25 +
  26 +class MqttClient : public virtual IMqttClient
  27 +{
  28 +public:
  29 + /*!
  30 + * \brief Construct an instance of the MqttClient.
  31 + * \param clientId The client identification used in the connection to the mqtt broker.
  32 + * \param deliveryCompleteCallback Optional callback used to signal completion of a publication.
  33 + */
  34 + MqttClient( const std::string& clientId, const std::function<void(const Token& token)>& deliveryCompleteCallback = std::function<void(const Token& token)>{});
  35 + virtual ~MqttClient() override;
  36 +
  37 + // Non copyable, non movable
  38 + MqttClient(const MqttClient&) = delete;
  39 + MqttClient& operator=(const MqttClient&) = delete;
  40 + MqttClient(MqttClient&&) = delete;
  41 + MqttClient& operator=(MqttClient&&) = delete;
  42 +
  43 + /**
  44 + * @see IStateCallback
  45 + */
  46 + virtual std::string clientId() const override;
  47 +
  48 + /**
  49 + * @see IStateCallback
  50 + */
  51 + virtual StateChangeCallbackHandle registerStateChangeCallback(const SlotStateChange& cb) override;
  52 +
  53 + /**
  54 + * @see IStateCallback
  55 + */
  56 + virtual void unregisterStateChangeCallback(StateChangeCallbackHandle handle) override;
  57 +
  58 + /**
  59 + * @see IStateCallback
  60 + */
  61 + virtual void clearAllStateChangeCallbacks() override;
  62 +
  63 + /**
  64 + * @see IStateCallback
  65 + */
  66 + virtual StateEnum state() const override;
  67 +
  68 + // MqttClient interface
  69 +
  70 + /**
  71 + * @see IMqttClient
  72 + */
  73 + virtual void connect(const std::string& host, int port, const Credentials& credentials) override;
  74 +
  75 + /**
  76 + * @see IMqttClient
  77 + */
  78 + virtual void connect(const std::string& endpoint) override;
  79 +
  80 + /**
  81 + * @see IMqttClient
  82 + */
  83 + virtual void disconnect() override;
  84 +
  85 + /**
  86 + * @see IMqttClient
  87 + */
  88 + virtual Token publish(const MqttMessage& message, int qos) override;
  89 +
  90 + /**
  91 + * @see IMqttClient
  92 + * When an overlapping subscription is detected a new connection has to be created. This is a synchronous action
  93 + * and this method will wait for the connection to be up.
  94 + */
  95 + virtual Token subscribe(const std::string& topic, int qos, const std::function<void(MqttMessage)>& cb) override;
  96 +
  97 + /**
  98 + * @see IMqttClient
  99 + */
  100 + virtual std::set<Token> unsubscribe(const std::string& topic, int qos) override;
  101 +
  102 + /**
  103 + * @see IMqttClient
  104 + */
  105 + virtual bool waitForCompletion(std::chrono::milliseconds waitFor) const override;
  106 +
  107 + /**
  108 + * @see IMqttClient
  109 + */
  110 + virtual bool waitForCompletion(std::chrono::milliseconds waitFor, const Token& token) const override;
  111 +
  112 + /**
  113 + * @see IMqttClient
  114 + */
  115 + virtual bool waitForCompletion(std::chrono::milliseconds waitFor, const std::set<Token>& tokens) const override;
  116 +
  117 + /**
  118 + * @see IMqttClient
  119 + */
  120 + virtual boost::optional<bool> commandResult(const Token& token) const override;
  121 +
  122 + /**
  123 + * @see IMqttClient
  124 + */
  125 + virtual std::string endpoint() const override;
  126 +
  127 +private:
  128 + /*!
  129 + * \brief Callback used to pick up the connection status of the wrappers.
  130 + * \param id The client id.
  131 + * \param cs The connection status.
  132 + */
  133 + void connectionStatusChanged( const std::string& id, ConnectionStatus cs );
  134 +
  135 + /*!
  136 + * \brief Callback for handling delivery complete.
  137 + * \param clientId The identifier of the client on which the publish command is executed.
  138 + * \param token The token identifies the publish command.
  139 + */
  140 + void deliveryComplete( const std::string& clientId, std::int32_t token );
  141 +
  142 + /**
  143 + * \brief Wait for commands to complete including active tokens in this client.
  144 + * The interface mutex is not locked by this method.
  145 + * First wait for client commands to complete (use method waitForCompletionInternalClients)
  146 + * and then wait for publish delivery callbacks to complete.
  147 + * \param clients - Vector with client wrapper pointers that need to be waited on.
  148 + * \param[in,out] waitFor - The number of milliseconds to wait for completetion of all commands and delivery callbacks.
  149 + * \param tokens - The tokens to wait for. An empty set means to wait for all commands on all clients to complete
  150 + * including all publish delivery callbacks.
  151 + * \return True when commands have completed in time including delivery callbacks or false if not.
  152 + */
  153 + bool waitForCompletionInternal(const std::vector<IMqttClientImpl*>& clients, std::chrono::milliseconds waitFor, const std::set<Token>& tokens) const;
  154 +
  155 + /**
  156 + * \brief Wait for commands on the wrapper clients to complete.
  157 + * The interface mutex is not locked by this method.
  158 + * \param clients - Vector with client wrapper pointers that need to be waited on.
  159 + * \param[in,out] waitFor - The number of milliseconds to wait for completetion of all commands.
  160 + * On return waitFor contains the time left.
  161 + * \param tokens - The tokens to wait for. An empty set means to wait for all commands
  162 + * on all clients to complete.
  163 + * \return True when all commands have completed in time or false if not.
  164 + */
  165 + bool waitForCompletionInternalClients(const std::vector<IMqttClientImpl*>& clients, std::chrono::milliseconds& waitFor, const std::set<Token>& tokens) const;
  166 +
  167 + /**
  168 + * @brief Determine the state of this client based on the connection statusses of its client wrappers.
  169 + * The states this client can communicate are:
  170 + * Unknown : When at least one wrapper is in a different state then Connected or ReconnectInProgress or when no wrappers are available.
  171 + * Good : When all wrappers are connected.
  172 + * ConnectionFailure : When at least one wrapper attempts reconnection.
  173 + * Unregister : When the serverstate instance is destroyed.
  174 + *
  175 + * The other states are about the information providers to the mqtt broker (the publishers) and we cannot say anything about them here.
  176 + * The state "Good" is the exception. This state means in this case that this clients connection is ok and not that the underlying data
  177 + * source (publisher) is ok.
  178 + *
  179 + * @param connectionStates A vector with the connection statusses of all client wrappers.
  180 + */
  181 + StateEnum determineState(const std::vector<ConnectionStatus>& connectionStates);
  182 +
  183 + /**
  184 + * @brief Add an event to the synchronized queue.
  185 + * @param ev A function object that performs work in the context of this class.
  186 + */
  187 + void pushEvent(std::function<void()> ev);
  188 +
  189 + /**
  190 + * @brief Worker method that executes the events.
  191 + */
  192 + void eventHandler();
  193 +
  194 + mutable std::mutex m_interfaceMutex; ///< Makes the interface mutual exclusive
  195 + mutable std::mutex m_internalMutex; ///< Protect the internal state.
  196 + std::string m_endpoint; ///< The endpoint uri.
  197 + std::string m_clientId; ///< The main client identification.
  198 + std::set<Token> m_activeTokens; ///< Set with active command tokens. Callbacks still need to be made for these tokens.
  199 + mutable std::condition_variable m_activeTokensCV; ///< Wait on a condition to become true w.r.t. the active token set.
  200 + std::function<void(const Token&)> m_deliveryCompleteCallback; ///< Optional callback for publish completion.
  201 + ServerState m_serverState; ///< Records the state of the connection to the broker that this client is connected to.
  202 + std::unique_ptr<IMqttClientImpl> m_principalClient; ///< The main wrapper client.
  203 + std::vector<std::unique_ptr<IMqttClientImpl>> m_additionalClients; ///< A vector of additional wrapper clients.
  204 + SynchronizedQueue<std::function<void()>> m_eventQueue; ///< Synchronized queue for scheduling additional work.
  205 + std::thread m_workerThread; ///< A worker thread that is used to perform actions that cannot be done on the callback threads.
  206 +};
  207 +
  208 +} // End namespace mqtt
  209 +} // End namespace components
  210 +} // End namespace osdev
  211 +
  212 +#endif // OSDEV_COMPONENTS_MQTT_MQTTCLIENT_H
... ...
src/mqttfailure.cpp 0 → 100644
  1 +++ a/src/mqttfailure.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "mqttfailure.h"
  20 +
  21 +// osdev::components::mqtt
  22 +#include "errorcode.h"
  23 +
  24 +using namespace osdev::components::mqtt;
  25 +
  26 +MqttFailure::MqttFailure(const MQTTAsync_failureData* data)
  27 + : m_token(data ? data->token : 0)
  28 + , m_code(data ? data->code : MQTTASYNC_FAILURE)
  29 + , m_message()
  30 +{
  31 + if (!data) {
  32 + m_message = "missing response data";
  33 + }
  34 + else if (!data->message) {
  35 + m_message = "no message";
  36 + }
  37 + else {
  38 + m_message = std::string(data->message);
  39 + }
  40 +}
  41 +
  42 +std::string MqttFailure::codeToString() const
  43 +{
  44 + if (m_code < 0) {
  45 + return pahoAsyncErrorCodeToString(m_code);
  46 + }
  47 + else if (0x80 == m_code) {
  48 + return "Failure";
  49 + }
  50 + return std::string("Unknown(") + std::to_string(m_code) + ")";
  51 +}
... ...
src/mqttfailure.h 0 → 100644
  1 +++ a/src/mqttfailure.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MQTTFAILURE_H
  2 +#define OSDEV_COMPONENTS_MQTT_MQTTFAILURE_H
  3 +
  4 +// std
  5 +#include <string>
  6 +
  7 +// paho
  8 +#include <MQTTAsync.h>
  9 +
  10 +namespace osdev {
  11 +namespace components {
  12 +namespace mqtt {
  13 +
  14 +/*!
  15 + * \brief Class for paho mqtt failure response data.
  16 + */
  17 +class MqttFailure
  18 +{
  19 +public:
  20 + /*!
  21 + * \brief Construct MqttFailure instance by copying information from the paho failure struct.
  22 + * \param data Paho response failure data.
  23 + */
  24 + explicit MqttFailure(const MQTTAsync_failureData* data);
  25 +
  26 + /*!
  27 + * \return The command token
  28 + */
  29 + MQTTAsync_token token() const { return m_token; }
  30 +
  31 + /*!
  32 + * \return The failure code.
  33 + */
  34 + int code() const { return m_code; }
  35 +
  36 + /*!
  37 + * \return The failure message.
  38 + * \retval "no message" when no message is available.
  39 + */
  40 + const std::string& message() const { return m_message; }
  41 +
  42 + /*!
  43 + * \return string interpretation of the code.
  44 + * \note negative codes are interpreted as paho error codes.
  45 + */
  46 + std::string codeToString() const;
  47 +
  48 +private:
  49 + MQTTAsync_token m_token; ///< Command token.
  50 + int m_code; ///< Failure code.
  51 + std::string m_message; ///< Optional message. Equal to "no message" when message is not available.
  52 +};
  53 +
  54 +} // End namespace mqtt
  55 +} // End namespace components
  56 +} // End namespace osdev
  57 +
  58 +#endif // OSDEV_COMPONENTS_MQTT_MQTTFAILURE_H
... ...
src/mqttidgenerator.cpp 0 → 100644
  1 +++ a/src/mqttidgenerator.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "mqttidgenerator.h"
  20 +
  21 +// std
  22 +#include <chrono>
  23 +#include <mutex>
  24 +#include <thread>
  25 +#include <unistd.h>
  26 +
  27 +// boost
  28 +#include <boost/uuid/uuid_generators.hpp>
  29 +
  30 +#include "lockguard.h"
  31 +#include "commondefs.h"
  32 +
  33 +namespace {
  34 +
  35 +boost::uuids::basic_random_generator<boost::mt19937> createGenerator()
  36 +{
  37 + static auto timeSeed = static_cast<boost::mt19937::result_type>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count());
  38 + static auto pidSeed = static_cast<boost::mt19937::result_type>(getpid());
  39 + static std::hash<std::thread::id> hasher;
  40 + static auto threadSeed = static_cast<boost::mt19937::result_type>(hasher(std::this_thread::get_id()));
  41 + static boost::mt19937 randomNumberGenerator;
  42 + randomNumberGenerator.seed(timeSeed ^ pidSeed ^ threadSeed);
  43 + return boost::uuids::basic_random_generator<boost::mt19937>(&randomNumberGenerator);
  44 +}
  45 +
  46 +} // namespace
  47 +
  48 +using namespace osdev::components::mqtt;
  49 +
  50 +const MqttId mqttNamespace = boost::lexical_cast<boost::uuids::uuid>("9c0f3730-cc4f-49eb-ab5b-079bc5b0e481");
  51 +
  52 +// static
  53 +MqttId MqttIdGenerator::generate()
  54 +{
  55 + // From the boost design notes
  56 + // Seeding is unqiue per random generator:
  57 + // The boost::uuids::basic_random_generator class' default constructor seeds the random number generator
  58 + // with a SHA-1 hash of a number of different values including std::time(0), std::clock(), uninitialized data,
  59 + // value return from new unsigned int, etc..
  60 + // Functions are reentrant:
  61 + // All functions are re-entrant. Classes are as thread-safe as an int. That is an instance can not be shared
  62 + // between threads without proper synchronization.
  63 + static auto uuidGenerator = createGenerator();
  64 + static std::mutex generatorMutex;
  65 + OSDEV_COMPONENTS_LOCKGUARD(generatorMutex);
  66 + return uuidGenerator();
  67 +}
  68 +
  69 +//static
  70 +MqttId MqttIdGenerator::nullId()
  71 +{
  72 + return boost::uuids::nil_uuid();
  73 +}
  74 +
  75 +// static
  76 +MqttId MqttIdGenerator::nameId(MqttId namespaceUuid, const std::string& name)
  77 +{
  78 + boost::uuids::name_generator gen(namespaceUuid);
  79 + return gen(name.c_str());
  80 +}
  81 +
  82 +// static
  83 +MqttId MqttIdGenerator::nameId(const std::string& name)
  84 +{
  85 + return MqttIdGenerator::nameId(mqttNamespace, name);
  86 +}
... ...
src/mqttidgenerator.h 0 → 100644
  1 +++ a/src/mqttidgenerator.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MLOGICIDGENERATOR_H
  2 +#define OSDEV_COMPONENTS_MQTT_MLOGICIDGENERATOR_H
  3 +
  4 +// std
  5 +#include <string>
  6 +
  7 +// osdev::components::mqtt
  8 +#include "commondefs.h"
  9 +
  10 +namespace osdev {
  11 +namespace components {
  12 +namespace mqtt {
  13 +
  14 +class MqttIdGenerator
  15 +{
  16 +public:
  17 + /**
  18 + * @brief Generates a new MqttId, which is guaranteed to be unique.
  19 + * @return A new unique MqttId.
  20 + */
  21 + static MqttId generate();
  22 +
  23 + /**
  24 + * @brief Returns an MqttId that represents null.
  25 + * @return An MqttId that represents null.
  26 + */
  27 + static MqttId nullId();
  28 +
  29 + /**
  30 + * @brief Returns an MqttId based on a namespace uuid and a given string.
  31 + * @param namespaceUuid The namespace in which the MqttId is generated.
  32 + * @param name The name for which an MqttId is generated.
  33 + */
  34 + static MqttId nameId(MqttId namespaceUuid, const std::string& name);
  35 +
  36 + /**
  37 + * @brief Returns an MqttId in the MQTT namespace for a given string.
  38 + * @param name The name for which an MqttId is generated.
  39 + */
  40 + static MqttId nameId(const std::string& name);
  41 +};
  42 +
  43 +} // End namespace mqtt
  44 +} // End namespace components
  45 +} // End namespace osdev
  46 +
  47 +#endif // OSDEV_COMPONENTS_MQTT_MLOGICIDGENERATOR_H
... ...
src/mqttmessage.cpp 0 → 100644
  1 +++ a/src/mqttmessage.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "mqttmessage.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +MqttMessage::MqttMessage()
  24 + : m_retained()
  25 + , m_duplicate()
  26 + , m_topic()
  27 + , m_payload()
  28 +{}
  29 +
  30 +MqttMessage::MqttMessage(const std::string &_topic, const MQTTAsync_message &message)
  31 + : m_retained( 0 != message.retained )
  32 + , m_duplicate( 0 != message.dup )
  33 + , m_topic( _topic )
  34 + , m_payload()
  35 +{
  36 + const char *msg = reinterpret_cast<const char*>(message.payload);
  37 + m_payload = std::string( msg, msg + message.payloadlen );
  38 +}
  39 +
  40 +MqttMessage::MqttMessage( const std::string &_topic, bool retainedFlag, bool duplicateFlag, std::string thePayload )
  41 + : m_retained( retainedFlag )
  42 + , m_duplicate( duplicateFlag )
  43 + , m_topic( _topic )
  44 + , m_payload( thePayload )
  45 +{}
  46 +
  47 +MQTTAsync_message MqttMessage::toAsyncMessage() const
  48 +{
  49 + MQTTAsync_message msg = MQTTAsync_message_initializer;
  50 + msg.payload = reinterpret_cast<void*>( const_cast<char*>( m_payload.c_str() ) );
  51 + msg.payloadlen = static_cast<int>( m_payload.size() );
  52 + msg.qos = -1;
  53 + msg.retained = static_cast<int>( m_retained );
  54 +
  55 + return msg;
  56 +}
... ...
src/mqttmessage.h 0 → 100644
  1 +++ a/src/mqttmessage.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MQTTMESSAGE_H
  2 +#define OSDEV_COMPONENTS_MQTT_MQTTMESSAGE_H
  3 +
  4 +// std
  5 +#include <string>
  6 +
  7 +// paho-c
  8 +#include <MQTTAsync.h>
  9 +
  10 +namespace osdev {
  11 +namespace components {
  12 +namespace mqtt {
  13 +
  14 +/*!
  15 + * @brief Class for paho mqtt message data
  16 + */
  17 +class MqttMessage
  18 +{
  19 +public:
  20 + /*!
  21 + * @brief Construct empty MqttMessage instance
  22 + */
  23 + MqttMessage();
  24 +
  25 + /*!
  26 + * @brief Construct MqttMessage instance by copying information gfrom the paho message struct
  27 + * @param topic - Paho topic data (copied)
  28 + * @param msg - Paho message data (copied)
  29 + */
  30 + MqttMessage( const std::string &topic, const MQTTAsync_message &msg );
  31 +
  32 + /*!
  33 + * @brief Construct MqttMessage instance.
  34 + * @param topic - Topic String
  35 + * @param retainedFlag - Flag that indicates if message is retained
  36 + * @param duplicateFlag - Flag that indicates if message is duplicate.
  37 + * @param thePayload - The message itself.
  38 + */
  39 + MqttMessage( const std::string &topic, bool retainedFlag, bool duplicateFlag, std::string thePayload );
  40 +
  41 + /*! @return The retained flag value. */
  42 + bool retained() const { return m_retained; }
  43 +
  44 + /*! @return The duplicate flag value */
  45 + bool duplicate() const { return m_duplicate; }
  46 +
  47 + /*! @return The topic on which the message is received. */
  48 + const std::string& topic() const { return m_topic; }
  49 +
  50 + /*! @return The message payload. */
  51 + const std::string& payload() const { return m_payload; }
  52 +
  53 + /*! @return This instance as a paho message */
  54 + MQTTAsync_message toAsyncMessage() const;
  55 +
  56 +private:
  57 + bool m_retained; ///< Retained flag. Not all brokers communicate this flag correctly. (emqx does not, mosquitto does.)
  58 + bool m_duplicate; ///< Duplicate flag ( for qos 1? )
  59 + std::string m_topic; ///< The topic on which the message is recieved.
  60 + std::string m_payload; ///< The actual message data.
  61 +};
  62 +
  63 +
  64 +} // End namespace mqtt
  65 +} // End namespace components
  66 +} // End namespace osdev
  67 +
  68 +#endif // OSDEV_COMPONENTS_MQTT_MQTTMESSAGE_H
... ...
src/mqttstream.h 0 → 100644
  1 +++ a/src/mqttstream.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_MQTTSTREAM_H
  20 +#define OSDEV_COMPONENTS_MQTT_MQTTSTREAM_H
  21 +
  22 +// This header is used in conjunction with mlogic/commmon/stringify.h to get output streaming of stl container types.
  23 +// The streaming operators are not suitable for marshalling because type information is lost!
  24 +
  25 +// std
  26 +#include <array>
  27 +#include <list>
  28 +#include <map>
  29 +#include <ostream>
  30 +#include <set>
  31 +#include <string>
  32 +#include <vector>
  33 +
  34 +namespace osdev {
  35 +namespace components {
  36 +namespace mqtt {
  37 +
  38 +/**
  39 + * @brief Streams a container with content for which stream operators are available.
  40 + * @note This function is meant for printing and not for marshalling!
  41 + * @tparam Open The container opening character.
  42 + * @tparam Close The container closing character.
  43 + * @tparam T The container type.
  44 + * @param os The stream to use.
  45 + * @param rhs The container that is to be streamed.
  46 + * @param sep The field separator. Default is ", "
  47 + * @return reference to the stream object.
  48 + */
  49 +template <char Open, char Close, typename T>
  50 +std::ostream& streamContainer(std::ostream& os, const T& rhs, const std::string& sep = ", ")
  51 +{
  52 + os << Open;
  53 + for (auto cit = rhs.cbegin(); rhs.cend() != cit; ++cit) {
  54 + os << *cit;
  55 + if (std::next(cit) != rhs.end()) {
  56 + os << sep;
  57 + }
  58 + }
  59 + os << Close;
  60 +
  61 + return os;
  62 +}
  63 +
  64 +} // End namespace mqtt
  65 +} // End namespace components
  66 +} // End namespace osdev
  67 +
  68 +namespace std {
  69 +
  70 +/**
  71 + * @brief Streams a list that contains values for which an output stream operator is available.
  72 + */
  73 +template <typename T>
  74 +std::ostream& operator<<(std::ostream& os, const std::list<T>& rhs)
  75 +{
  76 + return osdev::components::mqtt::streamContainer<'<', '>'>(os, rhs);
  77 +}
  78 +
  79 +/**
  80 + * @brief Streams an array that contains values for which an output stream operator is available.
  81 + */
  82 +template <typename T, std::size_t N>
  83 +std::ostream& operator<<(std::ostream& os, const std::array<T, N>& rhs)
  84 +{
  85 + return osdev::components::mqtt::streamContainer<'[', ']'>(os, rhs);
  86 +}
  87 +
  88 +/**
  89 + * @brief Streams a vector that contains values for which an output stream operator is available.
  90 + */
  91 +template <typename T>
  92 +std::ostream& operator<<(std::ostream& os, const std::vector<T>& rhs)
  93 +{
  94 + return osdev::components::mqtt::streamContainer<'[', ']'>(os, rhs);
  95 +}
  96 +
  97 +/**
  98 + * @brief Streams a set that contains values for which an output stream operator is available.
  99 + */
  100 +template <typename T>
  101 +std::ostream& operator<<(std::ostream& os, const std::set<T>& rhs)
  102 +{
  103 + return osdev::components::mqtt::streamContainer<'{', '}'>(os, rhs);
  104 +}
  105 +
  106 +/**
  107 + * @brief Streams a map that contains keys and values for which an output stream operator is available.
  108 + */
  109 +template <typename TKey, typename TValue>
  110 +std::ostream& operator<<(std::ostream& os, const std::map<TKey, TValue>& rhs)
  111 +{
  112 + return osdev::components::mqtt::streamContainer<'{', '}'>(os, rhs);
  113 +}
  114 +
  115 +/**
  116 + * @brief Streams a pair that contains values for which an output stream operator is available.
  117 + */
  118 +template <typename TFirst, typename TSecond>
  119 +std::ostream& operator<<(std::ostream& os, const std::pair<TFirst, TSecond>& rhs)
  120 +{
  121 + os << "{" << rhs.first << " : " << rhs.second << "}";
  122 + return os;
  123 +}
  124 +
  125 +} // End namespace std
  126 +
  127 +#endif // OSDEV_COMPONENTS_MQTT_MQTTSTREAM_H
... ...
src/mqttsuccess.cpp 0 → 100644
  1 +++ a/src/mqttsuccess.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "mqttsuccess.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +ConnectionData::ConnectionData()
  24 + : m_serverUri()
  25 + , m_mqttVersion()
  26 + , m_sessionPresent()
  27 +{
  28 +}
  29 +
  30 +ConnectionData::ConnectionData(char* _serverUri, int _mqttVersion, int _sessionPresent)
  31 + : m_serverUri(_serverUri ? _serverUri : "unknown")
  32 + , m_mqttVersion(_mqttVersion)
  33 + , m_sessionPresent(0 != _sessionPresent)
  34 +{
  35 +}
  36 +
  37 +MqttSuccess::MqttSuccess(MQTTAsync_token _token)
  38 + : m_token(_token)
  39 + , m_data(Unspecified{})
  40 +{
  41 +}
  42 +
  43 +MqttSuccess::MqttSuccess(MQTTAsync_token _token, int _qos)
  44 + : m_token(_token)
  45 + , m_data(_qos)
  46 +{
  47 +}
  48 +
  49 +MqttSuccess::MqttSuccess(MQTTAsync_token _token, const std::vector<int>& _qosMany)
  50 + : m_token(_token)
  51 + , m_data(_qosMany)
  52 +{
  53 +}
  54 +
  55 +MqttSuccess::MqttSuccess(MQTTAsync_token _token, const MqttMessage& pubMsg)
  56 + : m_token(_token)
  57 + , m_data(pubMsg)
  58 +{
  59 +}
  60 +
  61 +MqttSuccess::MqttSuccess(MQTTAsync_token _token, const ConnectionData& connData)
  62 + : m_token(_token)
  63 + , m_data(connData)
  64 +{
  65 +}
  66 +
  67 +int MqttSuccess::qos() const
  68 +{
  69 + return boost::get<int>(m_data);
  70 +}
  71 +
  72 +std::vector<int> MqttSuccess::qosMany() const
  73 +{
  74 + return boost::get<std::vector<int>>(m_data);
  75 +}
  76 +
  77 +MqttMessage MqttSuccess::publishData() const
  78 +{
  79 + return boost::get<MqttMessage>(m_data);
  80 +}
  81 +
  82 +ConnectionData MqttSuccess::connectionData() const
  83 +{
  84 + return boost::get<ConnectionData>(m_data);
  85 +}
... ...
src/mqttsuccess.h 0 → 100644
  1 +++ a/src/mqttsuccess.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MQTTSUCCESS_H
  2 +#define OSDEV_COMPONENTS_MQTT_MQTTSUCCESS_H
  3 +
  4 +// std
  5 +#include <string>
  6 +#include <vector>
  7 +
  8 +// boost
  9 +#include <boost/variant.hpp>
  10 +
  11 +// paho
  12 +#include <MQTTAsync.h>
  13 +
  14 +// osdev::components::mqtt
  15 +#include "mqttmessage.h"
  16 +
  17 +namespace osdev {
  18 +namespace components {
  19 +namespace mqtt {
  20 +
  21 +/**
  22 + * @brief Class that holds paho connection data which is returned in the connect response.
  23 + */
  24 +class ConnectionData
  25 +{
  26 +public:
  27 + /*!
  28 + * \brief Construct an empty ConnectData instance.
  29 + */
  30 + ConnectionData();
  31 +
  32 + /*!
  33 + * \brief Construct ConnectData based on incoming values from paho.
  34 + * \param serverUri - The serverUri to which the connection is made (needs to be copied).
  35 + * \param mqttVersion - The mqtt version used by the broker.
  36 + * \param sessionPresent - Flag that indicates if a session was present for the given clientId.
  37 + */
  38 + ConnectionData(char* serverUri, int mqttVersion, int sessionPresent);
  39 +
  40 + /*!
  41 + * \return The server uri.
  42 + */
  43 + const std::string& serverUri() const { return m_serverUri; }
  44 +
  45 + /*!
  46 + * \return The mqtt version.
  47 + */
  48 + int mqttVersion() const { return m_mqttVersion; }
  49 +
  50 + /*!
  51 + * \return if a session was present for the given clientId.
  52 + */
  53 + bool sessionPresent() const { return m_sessionPresent; }
  54 +
  55 +private:
  56 + std::string m_serverUri; ///< The broker server uri.
  57 + int m_mqttVersion; ///< The mqtt version used by the broker.
  58 + bool m_sessionPresent; ///< Flag that indicates whether a session was present for the client id used in the connect.
  59 +};
  60 +
  61 +struct Unspecified
  62 +{
  63 +};
  64 +
  65 +/*!
  66 + * \brief Class for paho mqtt success response data.
  67 + * The paho success response data uses a union and can have different information depending on the command.
  68 + */
  69 +class MqttSuccess
  70 +{
  71 +public:
  72 + /*!
  73 + * \brief Response data for commands without specific data.
  74 + * \param token The token that identifies to which command this response belongs.
  75 + */
  76 + explicit MqttSuccess(MQTTAsync_token token);
  77 +
  78 + /*!
  79 + * \brief Response data for a subscribe command.
  80 + * \param token The token that identifies to which command this response belongs.
  81 + * \param qos Actual quality of service of the subscription.
  82 + */
  83 + MqttSuccess(MQTTAsync_token token, int qos);
  84 +
  85 + /*!
  86 + * \brief Response data for a subscribe many command.
  87 + * \param token The token that identifies to which command this response belongs.
  88 + * \param qosMany Actual quality of service of the subscription for each topic filter.
  89 + */
  90 + MqttSuccess(MQTTAsync_token token, const std::vector<int>& qosMany);
  91 +
  92 + /*!
  93 + * \brief Response data for a publish command.
  94 + * \param token The token that identifies to which command this response belongs.
  95 + * \param pubMsg The message that was published.
  96 + */
  97 + MqttSuccess(MQTTAsync_token token, const MqttMessage& pubMsg);
  98 +
  99 + /*!
  100 + * \brief Response data for a connect command.
  101 + * \param token The token that identifies to which command this response belongs.
  102 + * \param connData The connection data.
  103 + */
  104 + MqttSuccess(MQTTAsync_token token, const ConnectionData& connData);
  105 +
  106 + /*!
  107 + * \return the command token.
  108 + */
  109 + MQTTAsync_token token() const { return m_token; }
  110 +
  111 + /*!
  112 + * \return the qos
  113 + * \throw exception when command is not a subscribe command.
  114 + */
  115 + int qos() const;
  116 +
  117 + /*!
  118 + * \return a vector of qos values (matching the topics in the subscribe many command).
  119 + * \throw exception when command is not a subscribe many command.
  120 + */
  121 + std::vector<int> qosMany() const;
  122 +
  123 + /*!
  124 + * \return Message that has been published.
  125 + * \throw exception when command is not a publish command.
  126 + */
  127 + MqttMessage publishData() const;
  128 +
  129 + /*!
  130 + * return Connection data.
  131 + * throw exception when command is not a connect command.
  132 + */
  133 + ConnectionData connectionData() const;
  134 +
  135 +private:
  136 + MQTTAsync_token m_token; ///< Command token.
  137 + boost::variant<int, std::vector<int>, MqttMessage, ConnectionData, Unspecified> m_data; ///< Data for the various commands.
  138 +};
  139 +
  140 +} // End namespace mqtt
  141 +} // End namespace components
  142 +} // End namespace osdev
  143 +
  144 +#endif // OSDEV_COMPONENTS_MQTT_MQTTSUCCESS_H
... ...
src/mqtttypeconverter.cpp 0 → 100644
  1 +++ a/src/mqtttypeconverter.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "mqtttypeconverter.h"
  20 +
  21 +// std
  22 +#include <chrono>
  23 +
  24 +// boost
  25 +#include <boost/lexical_cast.hpp>
  26 +#include <boost/uuid/uuid_io.hpp>
  27 +
  28 +// date
  29 +#include "date.h"
  30 +
  31 +#include "stringutils.h"
  32 +
  33 +using namespace osdev::components::mqtt;
  34 +
  35 +namespace osdev {
  36 +namespace components {
  37 +namespace mqtt {
  38 +namespace MqttTypeConverter {
  39 +
  40 +std::string toStdString(const MqttId& mqttId)
  41 +{
  42 + return boost::lexical_cast<std::string>(mqttId);
  43 +}
  44 +
  45 +MqttId toMlogicId(const std::string& mqttId)
  46 +{
  47 + return boost::lexical_cast<MqttId>(mqttId);
  48 +}
  49 +
  50 +StdTime toStdTime(const time_t& posixTime)
  51 +{
  52 + return std::chrono::system_clock::from_time_t(posixTime);
  53 +}
  54 +
  55 +time_t toPosixTime(const StdTime& stdTime)
  56 +{
  57 + return std::chrono::system_clock::to_time_t(stdTime);
  58 +}
  59 +
  60 +OptionalTime toOptionalTime(const std::string& posixTimeString)
  61 +{
  62 + static const OptionalTime nonExisting;
  63 +
  64 + try {
  65 + auto lotPosixTime = boost::lexical_cast<std::time_t>(posixTimeString);
  66 + return toStdTime(lotPosixTime);
  67 + }
  68 + catch (const boost::bad_lexical_cast&) {
  69 + return nonExisting;
  70 + }
  71 +}
  72 +
  73 +std::string toShortGuidAppendedString(const std::string& str, const MqttId& mqttId)
  74 +{
  75 + std::string idStr = toStdString(mqttId);
  76 + return str + "-" + idStr.substr(0, 4);
  77 +}
  78 +
  79 +} // End namespace MqttTypeConverter
  80 +} // End namespace mqtt
  81 +} // End namespace components
  82 +} // End namespace osdev
... ...
src/mqtttypeconverter.h 0 → 100644
  1 +++ a/src/mqtttypeconverter.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MLOGICTYPECONVERTER_H
  2 +#define OSDEV_COMPONENTS_MQTT_MLOGICTYPECONVERTER_H
  3 +
  4 +// std
  5 +#include <chrono>
  6 +#include <ctime>
  7 +#include "date.h"
  8 +#include <string>
  9 +
  10 +// boost
  11 +#include <boost/uuid/uuid.hpp>
  12 +
  13 +#include "commondefs.h"
  14 +
  15 +namespace osdev {
  16 +namespace components {
  17 +namespace mqtt {
  18 +
  19 +/**
  20 + * @brief Utility namespace to convert between mqtt common types and other frequently used types.
  21 + */
  22 +namespace MqttTypeConverter {
  23 +
  24 +/**
  25 + * @brief Converts from MqttId to std::string.
  26 + * @param mqttId The mqttId to convert.
  27 + * @return The std::string with contents of the provided mqttId. Format is 12345678-9abc-def0-1234-56789abcdef0.
  28 + */
  29 +std::string toStdString(const MqttId& mqttId);
  30 +
  31 +/**
  32 + * @brief Converts from system clock timepoint to std::string.
  33 + * @tparam Duration std::chrono::duration instance.
  34 + * Duration::Period is used to determine the precision
  35 + * of the subsecond part of the returned ISO8601 string.
  36 + * Uses the duration of the StdTime type by default.
  37 + * @param tp The timepoint to converter.
  38 + * @return ISO8601 string representation of stdTime.
  39 + */
  40 +template <typename Duration>
  41 +std::string toStdString(const std::chrono::time_point<std::chrono::system_clock, Duration>& tp)
  42 +{
  43 + return date::format("%FT%T%Ez", tp);
  44 +}
  45 +
  46 +/**
  47 + * @brief Converts from std::string to MqttId.
  48 + * @param mqttId The MqttId string to convert.
  49 + * @return the converted string to MqttId.
  50 + */
  51 +MqttId toMqttId(const std::string& mqttId);
  52 +
  53 +/**
  54 + * @brief Creates a descriptive string based on the specified input parameters.
  55 + * @param str The prefix of the string.
  56 + * @param mqttId The id of which to use the first 4 characters.
  57 + * @return str + "-" + <first 4 characters of mqttId>
  58 + * Example: "Unassigned-a2c4".
  59 + */
  60 +std::string toShortGuidAppendedString(const std::string& str, const MqttId& mqttId);
  61 +
  62 +/**
  63 + * @brief Converts from PosixTime (time_t) to StdTime.
  64 + * @param posixTime The Posix Time (time_t) to convert.
  65 + * @return The StdTime with same value as the provided posixTime.
  66 + */
  67 +StdTime toStdTime(const std::time_t& posixTime);
  68 +
  69 +/**
  70 + * @brief Converts from StdTime to PosixTime (time_t).
  71 + * @param stdTime The StdTime to convert.
  72 + * @return The PosixTime with the same value as the provided stdTime.
  73 + */
  74 +time_t toPosixTime(const osdev::components::mqtt::StdTime& stdTime);
  75 +
  76 +/**
  77 + * @brief Converts the specified posixTimeString to an OptionalTime.
  78 + * @param posixTimeString A posix time as string.
  79 + * @return The converted posixTimeString.
  80 + * @retval boost::none if the specified posixTimeString could not be converted to a StdTime.
  81 + */
  82 +osdev::components::mqtt::OptionalTime toOptionalTime(const std::string& posixTimeString);
  83 +
  84 +} // End namespace MqttTypeConverter
  85 +} // End namespace mqtt
  86 +} // End namespace components
  87 +} // End namespace osdev
  88 +
  89 +#endif // OSDEV_COMPONENTS_MQTT_MQTTTYPECONVERTER_H
... ...
src/mqttutil.cpp 0 → 100644
  1 +++ a/src/mqttutil.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "mqttutil.h"
  20 +
  21 +// boost
  22 +#include <boost/algorithm/string/split.hpp>
  23 +#include <boost/regex.hpp>
  24 +
  25 +namespace osdev {
  26 +namespace components {
  27 +namespace mqtt {
  28 +
  29 +bool isValidTopic( const std::string &topic )
  30 +{
  31 + if (topic.empty() || topic.size() > 65535) {
  32 + return false;
  33 + }
  34 +
  35 + auto posHash = topic.find('#');
  36 + if (std::string::npos != posHash && posHash < topic.size() - 1) {
  37 + return false;
  38 + }
  39 +
  40 + std::size_t pos = 0;
  41 + while ((pos = topic.find('+', pos)) != std::string::npos) {
  42 + if (pos > 0 && topic[pos - 1] != '/') {
  43 + return false;
  44 + }
  45 + if (pos < topic.size() - 1 && topic[pos + 1] != '/') {
  46 + return false;
  47 + }
  48 + ++pos;
  49 + }
  50 + return true;
  51 +}
  52 +
  53 +bool hasWildcard( const std::string &topic )
  54 +{
  55 + return ( topic.size() > 0 && (topic.find( '+' ) != std::string::npos || topic.size() - 1 == '#' ) );
  56 +}
  57 +
  58 +bool testForOverlap( const std::string &existingTopic, const std::string &newTopic )
  59 +{
  60 + if (existingTopic == newTopic) {
  61 + return true;
  62 + }
  63 +
  64 + // A topic that starts with a $ can never overlap with topic that does not start with a $.
  65 + // Topics that start!!! with a $ are so called system reserved topics and not meant for users to publish to.
  66 + if ((existingTopic[0] == '$' && newTopic[0] != '$') ||
  67 + (existingTopic[0] != '$' && newTopic[0] == '$')) {
  68 + return false;
  69 + }
  70 +
  71 + std::vector<std::string> existingTopicList;
  72 + std::vector<std::string> newTopicList;
  73 + boost::algorithm::split(existingTopicList, existingTopic, [](char ch) { return ch == '/'; });
  74 + boost::algorithm::split(newTopicList, newTopic, [](char ch) { return ch == '/'; });
  75 +
  76 + auto szExistingTopicList = existingTopicList.size();
  77 + auto szNewTopicList = newTopicList.size();
  78 + // Walk through the topic term by term until it is proved for certain that the topics either have no overlap
  79 + // or do have overlap.
  80 + for (std::size_t idx = 0; idx < std::minmax(szExistingTopicList, szNewTopicList).first; ++idx) {
  81 + if ("#" == existingTopicList[idx] || "#" == newTopicList[idx]) {
  82 + return true; // Match all wildcard found so there is always a possible overlap.
  83 + }
  84 + else if ("+" == existingTopicList[idx] || "+" == newTopicList[idx]) {
  85 + // these terms can match each other based on wildcard.
  86 + // Topics are still in the race for overlap, proceed to the next term
  87 + }
  88 + else if (existingTopicList[idx] != newTopicList[idx]) {
  89 + return false; // no match possible because terms are not wildcards and differ from each other.
  90 + }
  91 + else {
  92 + // term is an exact match. The topics are still in the race for overlap, proceed to the next term.
  93 + }
  94 + }
  95 + // Still no certain prove of overlap. If the number of terms for both topics are the same at this point then they overlap.
  96 + // If they are not the same than a match all wildcard on the longer topic can still make them match because of a special rule
  97 + // that states that a topic with a match all wildcard also matches a topic without that term.
  98 + // Example: topic /level/1 matches wildcard topic /level/1/#
  99 + // A match single wildcard at the end or at the term before a match all wildcard must also be taken into account.
  100 + // Example: /level/+ can overlap with /level/1/#
  101 + // /level/1 can overlap with /level/+/#
  102 + if (szNewTopicList != szExistingTopicList) {
  103 + if (szNewTopicList == szExistingTopicList + 1 && "#" == newTopicList[szNewTopicList - 1]) {
  104 + return (newTopicList[szNewTopicList - 2] == existingTopicList[szExistingTopicList - 1] || "+" == existingTopicList[szExistingTopicList - 1] || "+" == newTopicList[szNewTopicList - 2]);
  105 + }
  106 + if (szExistingTopicList == szNewTopicList + 1 && "#" == existingTopicList[szExistingTopicList - 1]) {
  107 + return (existingTopicList[szExistingTopicList - 2] == newTopicList[szNewTopicList - 1] || "+" == newTopicList[szNewTopicList - 1] || "+" == existingTopicList[szExistingTopicList - 2]);
  108 + }
  109 + return false;
  110 + }
  111 +
  112 + return true;
  113 +}
  114 +
  115 +std::string convertTopicToRegex(const std::string& topic)
  116 +{
  117 + // escape the regex characters in msgTemplate
  118 + static const boost::regex esc("[.^$|()\\[\\]{}*+?\\\\]");
  119 + static const std::string rep("\\\\$&"); // $&, refers to whatever is matched by the whole expression
  120 +
  121 + std::string out = boost::regex_replace(topic, esc, rep);
  122 +
  123 + static const boost::regex multiTopicRegex("#$");
  124 + static const boost::regex singleTopicRegex(R"((/|^|(?<=/))\\\+(/|$))");
  125 +
  126 + out = boost::regex_replace(out, multiTopicRegex, ".*");
  127 + return boost::regex_replace(out, singleTopicRegex, "$1[^/]*?$2");
  128 +}
  129 +
  130 +} // End namespace mqtt
  131 +} // End namespace components
  132 +} // End namespace osdev
... ...
src/mqttutil.h 0 → 100644
  1 +++ a/src/mqttutil.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MQTTUTIL_H
  2 +#define OSDEV_COMPONENTS_MQTT_MQTTUTIL_H
  3 +
  4 +// std
  5 +#include <string>
  6 +
  7 +namespace osdev {
  8 +namespace components {
  9 +namespace mqtt {
  10 +
  11 +/*!
  12 + * \brief Determine if topic is a valid mqtt topic filter.
  13 + * \param topic - The topic to test.
  14 + * \return True when topic is valid, false otherwise.
  15 + */
  16 +bool isValidTopic( const std::string &topic );
  17 +
  18 +/*!
  19 + * \brief Test a topic against another topicfilter for overlap.
  20 + * \param existingTopic - The topic to test against
  21 + * \param newTopic - The topic to test.
  22 + * \return True when topics overlap, false otherwise
  23 + */
  24 +bool testForOverlap( const std::string & existingTopic, const std::string &newTopic );
  25 +
  26 +/*!
  27 + * \brief Test a topic for occurence of wildcards
  28 + * \param topic - The topic to test
  29 + * \return True if topics contains wildcards, false otherwise
  30 + */
  31 +bool hasWildcard( const std::string &topic );
  32 +
  33 +/*!
  34 + * \brief Create a regular expression string based on a topicfilter that can be used
  35 + * to match topic strings against topics with no wildcards.
  36 + * \pre The topic filter is valid.
  37 + * \return The regular expression string. If the topic filter is not valid then the
  38 + * returned string is also not valid.
  39 + */
  40 +std::string convertTopicToRegex( const std::string &topic );
  41 +
  42 +} // End namespace mqtt
  43 +} // End namespace components
  44 +} // osdev
  45 +
  46 +#endif // OSDEV_COMPONENTS_MQTT_MQTTUTIL_H
... ...
src/scopeguard.cpp 0 → 100644
  1 +++ a/src/scopeguard.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "scopeguard.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +ScopeGuard::ScopeGuard()
  24 + : m_cleanupFunc()
  25 +{
  26 +}
  27 +
  28 +ScopeGuard::ScopeGuard(const CleanUpFunction& cleanupFunc)
  29 + : m_cleanupFunc(cleanupFunc)
  30 +{
  31 +}
  32 +
  33 +ScopeGuard::~ScopeGuard() noexcept
  34 +{
  35 + try {
  36 + if (m_cleanupFunc) {
  37 + m_cleanupFunc();
  38 + }
  39 + }
  40 + catch (...) {
  41 + }
  42 +}
... ...
src/scopeguard.h 0 → 100644
  1 +++ a/src/scopeguard.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_SCOPEGUARD_H
  2 +#define OSDEV_COMPONENTS_MQTT_SCOPEGUARD_H
  3 +
  4 +// std
  5 +#include <functional>
  6 +
  7 +#include "macrodefs.h"
  8 +#include "utils.h"
  9 +
  10 +#define OSDEV_COMPONENTS_SCOPEGUARD(variableName, ...) \
  11 + osdev::components::mqtt::ScopeGuard OSDEV_COMPONENTS_COMBINE(Scope__Guard__##variableName##__, __LINE__)(__VA_ARGS__); \
  12 + osdev::components::mqtt::apply_unused_parameters(OSDEV_COMPONENTS_COMBINE(Scope__Guard__##variableName##__, __LINE__));
  13 +
  14 +namespace osdev {
  15 +namespace components {
  16 +namespace mqtt {
  17 +
  18 +using CleanUpFunction = std::function<void() noexcept>;
  19 +
  20 +/**
  21 + * @brief Ensures that a cleanup function is called at the end of the current scope.
  22 + */
  23 +class ScopeGuard
  24 +{
  25 +public:
  26 + /**
  27 + * @brief Constructs an empty scopeguard.
  28 + * The scopeguard can be set by moving another ScopeGuard into this one.
  29 + */
  30 + ScopeGuard();
  31 +
  32 + /**
  33 + * @brief Constructs a RAII instance that will call cleanupFunc in it's destructor.
  34 + * @param cleanupFunc The cleanup function to call at the end of the current scope.
  35 + * This cleanup function must not throw exceptions. If it does, the behavior is undefined.
  36 + */
  37 + ScopeGuard(const CleanUpFunction& cleanupFunc);
  38 +
  39 + // Movable, not copyable
  40 + ScopeGuard(const ScopeGuard&) = delete;
  41 + ScopeGuard& operator=(const ScopeGuard&) = delete;
  42 + ScopeGuard(ScopeGuard&&) = default;
  43 + ScopeGuard& operator=(ScopeGuard&&) = default;
  44 +
  45 + ~ScopeGuard() noexcept;
  46 +
  47 +private:
  48 + CleanUpFunction m_cleanupFunc;
  49 +};
  50 +
  51 +} // End namespace mqtt
  52 +} // End namespace components
  53 +} // End namespace osdev
  54 +
  55 +#endif // OSDEV_COMPONENTS_MQTT_SCOPEGUARD_H
... ...
src/serverstate.cpp 0 → 100644
  1 +++ a/src/serverstate.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "serverstate.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +const std::string ServerState::s_identifier("ServerState");
  24 +
  25 +std::atomic<StateChangeCallbackHandle> ServerState::s_nextServerStateCallbackHandle(1);
  26 +
  27 +ServerState::ServerState(const IStateCallback* stateCallbackIf)
  28 + : sig_serverStateChanged()
  29 + , m_stateCallbackIf(stateCallbackIf)
  30 + , m_serverStateCallbackMap()
  31 + , m_state(StateEnum::Unknown)
  32 +{
  33 +}
  34 +
  35 +ServerState::~ServerState()
  36 +{
  37 + try {
  38 + this->clearAllStateChangeCallbacks();
  39 + }
  40 + catch (...) {
  41 + }
  42 +}
  43 +
  44 +StateChangeCallbackHandle ServerState::registerStateChangeCallback(const IStateCallback::SlotStateChange& cb)
  45 +{
  46 + const auto handle = s_nextServerStateCallbackHandle++;
  47 + m_serverStateCallbackMap.emplace(std::piecewise_construct,
  48 + std::make_tuple(handle),
  49 + std::make_tuple(sig_serverStateChanged.connect(cb)));
  50 +
  51 + return handle;
  52 +}
  53 +
  54 +void ServerState::unregisterStateChangeCallback(StateChangeCallbackHandle handle)
  55 +{
  56 + m_serverStateCallbackMap.erase(handle);
  57 +}
  58 +
  59 +void ServerState::clearAllStateChangeCallbacks()
  60 +{
  61 + this->emitStateChanged(StateEnum::Unregister);
  62 + m_serverStateCallbackMap.clear();
  63 +}
  64 +
  65 +void ServerState::emitStateChanged(StateEnum newState)
  66 +{
  67 + m_state = newState; ///< store state, to allow clients to retrieve the last received state.
  68 +
  69 + if (nullptr != m_stateCallbackIf) {
  70 + sig_serverStateChanged(m_stateCallbackIf, newState);
  71 + }
  72 +}
  73 +
  74 +StateEnum ServerState::state() const
  75 +{
  76 + return m_state;
  77 +}
  78 +
  79 +std::string ServerState::stateCallbackClientId() const
  80 +{
  81 + static const std::string nullId = "null";
  82 + return (nullptr == m_stateCallbackIf) ? nullId : m_stateCallbackIf->clientId();
  83 +}
... ...
src/serverstate.h 0 → 100644
  1 +++ a/src/serverstate.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_SERVERSTATE_H
  2 +#define OSDEV_COMPONENTS_MQTT_SERVERSTATE_H
  3 +
  4 +// std
  5 +#include <atomic>
  6 +#include <map>
  7 +
  8 +// boost
  9 +#include <boost/signals2/connection.hpp>
  10 +
  11 +// osdev::components::mqtt
  12 +#include "istatecallback.h"
  13 +
  14 +namespace osdev {
  15 +namespace components {
  16 +namespace mqtt {
  17 +
  18 +/*!
  19 + * \brief Class for administrating ServerState callbacks.
  20 + * ServiceClientBase uses this object to notify the server state listeners
  21 + * of changes in the serverstate of the server that the client is connected to.
  22 + */
  23 +class ServerState
  24 +{
  25 +public:
  26 + /*!
  27 + * \brief Constructs a ServerState object. This object has a one to one relation with an IStateCallback object.
  28 + * \param stateCallbackIf identification of the interface that generates the signal.
  29 + */
  30 + explicit ServerState(const IStateCallback* stateCallbackIf);
  31 +
  32 + /*!
  33 + * \brief Destroy the ServerState.
  34 + * Calls clearAllStateChangeCallbacks() to unregister itself from the listeners.
  35 + */
  36 + virtual ~ServerState();
  37 +
  38 + // non copyable, non movable
  39 + ServerState(const ServerState&) = delete;
  40 + ServerState& operator=(ServerState&) = delete;
  41 + ServerState(ServerState&&) = delete;
  42 + ServerState& operator=(ServerState&&) = delete;
  43 +
  44 + /*!
  45 + * \brief Registers a statechange callback method.
  46 + * \param cb - The callback method.
  47 + * \return handle that identifies the callback method.
  48 + */
  49 + StateChangeCallbackHandle registerStateChangeCallback(const IStateCallback::SlotStateChange& cb);
  50 +
  51 + /*!
  52 + * \brief Unregisters a state change callback method.
  53 + * \param handle Handle that identifies the callback method.
  54 + */
  55 + void unregisterStateChangeCallback(StateChangeCallbackHandle handle);
  56 +
  57 + /*!
  58 + * \brief Removes all callback methods.
  59 + * An Unregister state is signalled to the listeners.
  60 + */
  61 + void clearAllStateChangeCallbacks();
  62 +
  63 + /*!
  64 + * \brief Emit the State changed signal.
  65 + * \param newState - The new state.
  66 + */
  67 + void emitStateChanged(StateEnum newState);
  68 +
  69 + /*!
  70 + * \brief Return the last state received from server.
  71 + * \return state of server
  72 + */
  73 + StateEnum state() const;
  74 +
  75 + /*!
  76 + * \brief Returns the handle that will be given to the next callback that is registered.
  77 + */
  78 + static StateChangeCallbackHandle nextHandle()
  79 + {
  80 + return s_nextServerStateCallbackHandle;
  81 + }
  82 +
  83 +private:
  84 + /*!
  85 + * Type for holding connections to server state callback functions.
  86 + */
  87 + using ServerStateCallbackMap = std::map<StateChangeCallbackHandle, boost::signals2::scoped_connection>;
  88 +
  89 + IStateCallback::SigStateChange sig_serverStateChanged; ///< Signal emitted when server state has changed.
  90 +
  91 + const IStateCallback* m_stateCallbackIf; ///< Identification of the the interface that generates the signal (not owned)
  92 + ServerStateCallbackMap m_serverStateCallbackMap; ///< Map with serverstate callback connections.
  93 +
  94 + static std::atomic<StateChangeCallbackHandle> s_nextServerStateCallbackHandle; ///< Handle given to next serverstate callback registration (0 is not valid).
  95 +
  96 + StateEnum m_state; ///< Store the last state received from the server.
  97 +
  98 + /*!
  99 + * \return The clientId of the IStateCallback interface, or "null" if it is nullptr.
  100 + */
  101 + std::string stateCallbackClientId() const;
  102 +
  103 + static const std::string s_identifier;
  104 +};
  105 +
  106 +} // End namespace mqtt
  107 +} // End namespace components
  108 +} // End namespace osdev
  109 +
  110 +#endif // OSDEV_COMPONENTS_MQTT_SERVERSTATE_H
... ...
src/sharedreaderlock.cpp 0 → 100644
  1 +++ a/src/sharedreaderlock.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "sharedreaderlock.h"
  20 +
  21 +
  22 +// #include "mlogic/common/invalidoperationexception.h"
  23 +#include "lockguard.h"
  24 +#include "mqttstream.h"
  25 +
  26 +
  27 +// #include "mlogic/common/logger/loggerprovider.h"
  28 +
  29 +using namespace osdev::components::mqtt;
  30 +
  31 +// What happens if a read lock owner locks again
  32 +// - another read lock : map the threads owning read locks and update a count.
  33 +// - a write lock : upgrade the read lock to an exclusive write lock (including all recursive locks).
  34 +//
  35 +// The same for a write lock owner.
  36 +// - another write lock : map the threads owning a write lock and update the count.
  37 +// - a read lock : treat the lock as an exclusive write lock
  38 +//
  39 +// What happens if a thread that never locks does an unlock.
  40 +// - when no locks are active : nothing happens
  41 +// - during active read locks : nothing happens
  42 +// - during active write lock : nothing happens
  43 +//
  44 +// What happens when multiple threads want a write lock.
  45 +// - Only one writer can be active at any time. Other writers will have to wait until the active writer has unlocked.
  46 +//
  47 +
  48 +namespace {
  49 +
  50 +/**
  51 + * @param lockMap The map to check for active locks.
  52 + * @return true when the lockMap contains an active lock, false otherwise.
  53 + */
  54 +bool hasActiveLock(const std::map<std::thread::id, LockData>& lockMap)
  55 +{
  56 + for (const auto& lockPair : lockMap) {
  57 + if (lockPair.second.active()) {
  58 + return true;
  59 + }
  60 + }
  61 + return false;
  62 +}
  63 +
  64 +} // namespace
  65 +SharedReaderLock::SharedReaderLock()
  66 + : m_mutex()
  67 + , m_readLockMap()
  68 + , m_writeLockMap()
  69 + , m_readersCV()
  70 + , m_writersCV()
  71 +{
  72 +}
  73 +
  74 +SharedReaderLock::~SharedReaderLock()
  75 +{
  76 + OSDEV_COMPONENTS_LOCKGUARD(m_mutex);
  77 + if (!m_readLockMap.empty() || !m_writeLockMap.empty())
  78 + {
  79 + // toLogFile ("SharedReaderLock", "Cannot destroy this lock because threads are still registered. Readers : %1, Writers : %2", m_readLockMap, m_writeLockMap);
  80 + // (InvalidOperationException, "Cannot destroy SharedReaderLock");
  81 + }
  82 +}
  83 +
  84 +void SharedReaderLock::lockShared()
  85 +{
  86 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE(m_mutex);
  87 +
  88 + if (m_writeLockMap.end() != m_writeLockMap.find(std::this_thread::get_id()))
  89 + { // If the thread owns a write lock than update the write lock count
  90 + m_writeLockMap[std::this_thread::get_id()].increase();
  91 + return;
  92 + }
  93 +
  94 + auto resultPair = m_readLockMap.insert(std::make_pair(std::this_thread::get_id(), LockData{}));
  95 + if (!resultPair.second)
  96 + { // thread has a read lock already...
  97 + resultPair.first->second.increase(); // add recursive reader lock
  98 + return; // Already owner of the shared lock, so proceed.
  99 + }
  100 +
  101 + m_readersCV.wait(OSDEV_COMPONENTS_UNIQUELOCK(m_mutex), [this] { return m_writeLockMap.empty(); });
  102 +
  103 + // Reread the iterator because it might have been invalidated.
  104 + auto it = m_readLockMap.find(std::this_thread::get_id());
  105 + if (m_readLockMap.end() == it)
  106 + {
  107 + // normally Throw(InvalidOperationException, "Thread id must be registered in the shared reader map at this point");
  108 + }
  109 + it->second.increase();
  110 + // the reader is now registered and is a shared owner of the SharedReaderLock
  111 +}
  112 +
  113 +void SharedReaderLock::lockExclusive()
  114 +{
  115 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE(m_mutex);
  116 +
  117 + // First check if the thread already owns a read lock.
  118 + auto readLockIter = m_readLockMap.find(std::this_thread::get_id());
  119 +
  120 + if (m_readLockMap.end() != readLockIter) {
  121 + auto resultPair = m_writeLockMap.insert(std::make_pair(readLockIter->first, LockData::initialize(readLockIter->second)));
  122 + if (!resultPair.second)
  123 + {
  124 + // throw (InvalidOperationException, "Thread id cannot be registered in the write map at this point");
  125 + }
  126 + // toLogFile ("SharedReaderLock", "Upgrade to exclusive lock");
  127 + m_readLockMap.erase(readLockIter);
  128 + // This is an upgrade of the read lock to an exclusive write lock.
  129 + // proceed to the wait call on m_writersCV.
  130 + }
  131 + else
  132 + {
  133 + auto resultPair = m_writeLockMap.insert(std::make_pair(std::this_thread::get_id(), LockData{}));
  134 + if (!resultPair.second)
  135 + { // thread has a write lock already...
  136 + resultPair.first->second.increase(); // add recursive write lock
  137 + return; // Already owner of the exclusive lock, so proceed.
  138 + }
  139 + }
  140 +
  141 + m_writersCV.wait(OSDEV_COMPONENTS_UNIQUELOCK(m_mutex), [this] { return !hasActiveLock(m_readLockMap) && !hasActiveLock(m_writeLockMap); });
  142 +
  143 + // Reread the iterator because it might have been invalidated.
  144 + auto it = m_writeLockMap.find(std::this_thread::get_id());
  145 + if (m_writeLockMap.end() == it)
  146 + {
  147 + // throw (InvalidOperationException, "Thread id must be registered in the writer map at this point");
  148 + }
  149 + it->second.increase();
  150 + // the thread is now registered and is an exclusive owner of the SharedReaderLock
  151 +}
  152 +
  153 +void SharedReaderLock::unlock()
  154 +{
  155 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE(m_mutex);
  156 +
  157 + auto readLockIter = m_readLockMap.find(std::this_thread::get_id());
  158 + auto writeLockIter = m_writeLockMap.find(std::this_thread::get_id());
  159 +
  160 + if (m_writeLockMap.end() != writeLockIter && m_readLockMap.end() != readLockIter)
  161 + {
  162 + return; // thread does not have a lock. Do nothing.
  163 + }
  164 +
  165 + if (m_readLockMap.end() != readLockIter)
  166 + {
  167 + if (!readLockIter->second.decrease())
  168 + { // The lock is not active anymore so remove it
  169 + m_readLockMap.erase(readLockIter);
  170 +
  171 + if (!hasActiveLock(m_readLockMap) && !m_writeLockMap.empty())
  172 + { // no active read lock anymore and a writer that waits for an exclusive lock.
  173 + // toLogFileDebug("SharedReaderLock", "notify_one writersCV");
  174 + OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK(m_mutex);
  175 + m_writersCV.notify_one();
  176 + }
  177 + }
  178 + return;
  179 + }
  180 +
  181 + if (m_writeLockMap.end() != writeLockIter)
  182 + {
  183 + if (!writeLockIter->second.decrease())
  184 + {
  185 + // remove the exclusive write lock
  186 + m_writeLockMap.erase(writeLockIter);
  187 +
  188 + if (m_writeLockMap.empty())
  189 + {
  190 + // toLogFile ("SharedReaderLock", "notify_all readersCV");
  191 + OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK(m_mutex);
  192 + m_readersCV.notify_all();
  193 + }
  194 + else
  195 + {
  196 + // toLogFile ("SharedReaderLock", "notify_one writersCV");
  197 + OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK(m_mutex);
  198 + m_writersCV.notify_one();
  199 + }
  200 + }
  201 + return;
  202 + }
  203 +}
... ...
src/sharedreaderlock.h 0 → 100644
  1 +++ a/src/sharedreaderlock.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_SHAREDREADERLOCK_H
  2 +#define OSDEV_COMPONENTS_MQTT_SHAREDREADERLOCK_H
  3 +
  4 +// std
  5 +#include <condition_variable>
  6 +#include <map>
  7 +#include <mutex>
  8 +#include <thread>
  9 +
  10 +// mlogic::common
  11 +#include "scopeguard.h"
  12 +
  13 +namespace osdev {
  14 +namespace components {
  15 +namespace mqtt {
  16 +
  17 +#define OSDEV_COMPONENTS_SHAREDLOCK_SCOPE(lockvar) \
  18 + lockvar.lockShared(); \
  19 + OSDEV_COMPONENTS_SCOPEGUARD(lockvar, [&]() { lockvar.unlock(); });
  20 +
  21 +#define OSDEV_COMPONENTS_EXCLUSIVELOCK_SCOPE(lockvar) \
  22 + lockvar.lockExclusive(); \
  23 + OSDEV_COMPONENTS_SCOPEGUARD(lockvar, [&]() { lockvar.unlock(); });
  24 +
  25 +/**
  26 + * @brief Class is used to administrate the lock data.
  27 + */
  28 +class LockData
  29 +{
  30 +public:
  31 + /**
  32 + * @brief Default constructable. Lock is not active and the count is 0.
  33 + */
  34 + LockData()
  35 + : m_count(0)
  36 + , m_active(false)
  37 + {
  38 + }
  39 +
  40 + // Copyable, movable
  41 + LockData(const LockData&) = default;
  42 + LockData& operator=(const LockData&) = default;
  43 + LockData(LockData&&) = default;
  44 + LockData& operator=(LockData&&) = default;
  45 +
  46 + /**
  47 + * @return true when the lock is active, false otherwise.
  48 + * @note A lock becomes active the first time that increase() is called.
  49 + */
  50 + inline bool active() const
  51 + {
  52 + return m_active;
  53 + }
  54 +
  55 + /**
  56 + * @brief Increases the lock count by one.
  57 + * An inactive lock becomes active by this call.
  58 + */
  59 + inline void increase()
  60 + {
  61 + m_active = true;
  62 + ++m_count;
  63 + }
  64 +
  65 + /**
  66 + * @brief Decreases the lock count by one.
  67 + * The count is only decreased for active locks. When the lock count becomes 0 the lock
  68 + * is deactivated.
  69 + * @return true when the lock is still active after decrease and false when it is deactivated.
  70 + */
  71 + inline bool decrease()
  72 + {
  73 + if (m_active) {
  74 + --m_count;
  75 + m_active = (0 != m_count);
  76 + }
  77 + return m_active;
  78 + }
  79 +
  80 + /**
  81 + * @brief Conversion operator that returns the lock count.
  82 + */
  83 + inline operator std::size_t() const
  84 + {
  85 + return m_count;
  86 + }
  87 +
  88 + /**
  89 + * @brief Static method for initializing a lock data based on already existing lock data.
  90 + * The new lock data is not active.
  91 + * @note This is used to promote a shared lock to an exclusive lock.
  92 + */
  93 + inline static LockData initialize(const LockData& other)
  94 + {
  95 + auto newLockData(other);
  96 + newLockData.m_active = false;
  97 + return newLockData;
  98 + }
  99 +
  100 +private:
  101 + std::size_t m_count; ///< The lock count.
  102 +
  103 + /**
  104 + * @brief Flag to indicate whether the lock is active.
  105 + * This flag is necessary because when the lock is promoted
  106 + * the lock count is not zero but the lock still should be activated again.
  107 + */
  108 + bool m_active;
  109 +};
  110 +
  111 +/**
  112 + * @brief Lock class that allows multiple readers to own the lock in a shared way.
  113 + * A writer will want exclusive ownership so that it can mutate the content that
  114 + * is protected by this lock.
  115 + *
  116 + * Reader and writer should be interpreted as to how threads interact with the content that this lock protects. It is up
  117 + * to the caller to enforce the correct behaviour. In other words don't take a shared lock and change the content!
  118 + *
  119 + * The administration of this class uses the std::thread::id to register which thread holds what kind of lock.
  120 + * This id is reused, so be really careful to pair each lock with an unlock, otherwise newly spawned threads might
  121 + * end up having a lock without taking one.
  122 + */
  123 +class SharedReaderLock
  124 +{
  125 +public:
  126 + /**
  127 + * Default constructable.
  128 + * The lock is not locked.
  129 + */
  130 + SharedReaderLock();
  131 +
  132 + /**
  133 + * Destructor will throw when there are threads registered.
  134 + */
  135 + ~SharedReaderLock();
  136 +
  137 + // Non copyable, non movable
  138 + SharedReaderLock(const SharedReaderLock&) = delete;
  139 + SharedReaderLock& operator=(const SharedReaderLock&) = delete;
  140 + SharedReaderLock(SharedReaderLock&&) = delete;
  141 + SharedReaderLock& operator=(SharedReaderLock&&) = delete;
  142 +
  143 + /**
  144 + * @brief Lock in a shared way. For read only operations.
  145 + * Multiple threads can have shared ownership on this lock.
  146 + * It is guaranteed that a call to lockExclusive will wait until all read locks are unlocked.
  147 + * When a call to lockExclusive is made and is waiting, no new reader locks are accepted.
  148 + * A thread that owns a shared lock can lock again. The lock will be unlocked for this thread when as many unlock calls are made.
  149 + * A thread that owns a shared lock can upgrade the lock to an exclusive lock by calling lockExclusive. The thread has to wait
  150 + * for exclusive ownership and has the exclusive lock until all unlocks are made (if it had done multiple shared locks before an exclusive lock).
  151 + */
  152 + void lockShared();
  153 +
  154 + /**
  155 + * @brief Lock in an exclusive way. For write operations.
  156 + * Only one thread can have exclusive ownership of this lock.
  157 + * While a thread waits for exlusive ownership shared locks are denied. This lock is unfair in the
  158 + * sense that it favours write locks.
  159 + * A thread that owns exclusive ownership can make another exclusive lock or a even a shared lock. Both are
  160 + * treated as an exclusive lock that updates the lock count. As many unlocks need to be called to unlock the exclusive lock.
  161 + */
  162 + void lockExclusive();
  163 +
  164 + /**
  165 + * @brief Unlock the lock. The thread id is used to determine which lock needs to be unlocked.
  166 + * If a thread does not own this lock at all then nothing happens.
  167 + */
  168 + void unlock();
  169 +
  170 +private:
  171 + std::mutex m_mutex; ///< Mutex that protects the lock administration.
  172 + std::map<std::thread::id, LockData> m_readLockMap; ///< Map with read lock data.
  173 + std::map<std::thread::id, LockData> m_writeLockMap; ///< Map with write lock data.
  174 +
  175 + std::condition_variable m_readersCV; ///< lockShared waits on this condition variable.
  176 + std::condition_variable m_writersCV; ///< lockExclusive waits on this condition variable.
  177 +};
  178 +
  179 +} // End namespace mqtt
  180 +} // End namespace components
  181 +} // End namespace osdev
  182 +
  183 +#endif // OSDEV_COMPONENTS_MQTT_SHAREDREADERLOCK_H
... ...
src/stringify.h 0 → 100644
  1 +++ a/src/stringify.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDSEV_COMPONENTS_MQTT_STRINGIFY_H
  20 +#define OSDSEV_COMPONENTS_MQTT_STRINGIFY_H
  21 +
  22 +// std
  23 +#include <sstream>
  24 +
  25 +// osdev::components::mqtt
  26 +#include "mqttstream.h"
  27 +
  28 +namespace osdev {
  29 +namespace components {
  30 +namespace mqtt {
  31 +
  32 +/**
  33 + * @brief Stringifies all objects for which an output stream operator is available.
  34 + * @note This method is meant to be used for printing and not for marshalling!
  35 + * @tparam T The object type.
  36 + * @param value The value to stringify.
  37 + * @return stringified value.
  38 + */
  39 +template <typename T>
  40 +std::string toString(const T& value)
  41 +{
  42 + std::ostringstream oss;
  43 + oss << value;
  44 + return oss.str();
  45 +}
  46 +
  47 +} // End namespace mqtt
  48 +} // End namespace components
  49 +} // End namespace osdev
  50 +
  51 +#endif // OSDEV_COMPONENTS_MQTT_STRINGIFY_H
... ...
src/stringutils.cpp 0 → 100644
  1 +++ a/src/stringutils.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "stringutils.h"
  20 +
  21 +// std
  22 +#include <algorithm>
  23 +
  24 +namespace osdev {
  25 +namespace components {
  26 +namespace mqtt {
  27 +
  28 +void removeCharsFromStringInPlace(std::string& str, const std::string& charsToRemove)
  29 +{
  30 + for (std::size_t i = 0; i < charsToRemove.length(); ++i) {
  31 + str.erase(std::remove(str.begin(), str.end(), charsToRemove[i]), str.end());
  32 + }
  33 +}
  34 +
  35 +bool is_numeric(const std::string& str)
  36 +{
  37 + return std::all_of(str.begin(), str.end(), [](const std::string::value_type& c) { return ::isdigit(c); });
  38 +}
  39 +
  40 +} // End namespace mqtt
  41 +} // End namespace components
  42 +} // End namespace osdev
... ...
src/stringutils.h 0 → 100644
  1 +++ a/src/stringutils.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_STRINGUTILS_H
  20 +#define OSDEV_COMPONENTS_MQTT_STRINGUTILS_H
  21 +
  22 +// See boost/algorithm/string.hpp for more string utility functions
  23 +
  24 +// std
  25 +#include <sstream>
  26 +#include <string>
  27 +#include <vector>
  28 +
  29 +namespace osdev {
  30 +namespace components {
  31 +namespace mqtt {
  32 +
  33 +/**
  34 + * @brief Removes characters from the specified string. Modifies the specified string in place.
  35 + * @param str The string to modify.
  36 + * @param charsToRemove The characters to remove from str.
  37 + */
  38 +void removeCharsFromStringInPlace(std::string& str, const std::string& charsToRemove);
  39 +
  40 +/**
  41 + * @brief Determines whether the specified string is all digits.
  42 + * @param str The string for which to determine if it's numeric.
  43 + * @return True if the specified string is numeric; otherwise, false.
  44 + */
  45 +bool is_numeric(const std::string& str);
  46 +
  47 +} // End namespace mqtt
  48 +} // End namespace components
  49 +} // End namespace osdev
  50 +
  51 +#endif // OSDEV_COMPONENTS_MQTT_STRINGUTILS_H
... ...
src/synchronizedqueue.h 0 → 100644
  1 +++ a/src/synchronizedqueue.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_SYNCHRONIZEDQUEUE_H
  20 +#define OSDEV_COMPONENTS_MQTT_SYNCHRONIZEDQUEUE_H
  21 +
  22 +// std
  23 +#include <atomic>
  24 +#include <condition_variable>
  25 +#include <memory>
  26 +#include <mutex>
  27 +#include <queue>
  28 +#include <string>
  29 +#include <type_traits>
  30 +
  31 +// osdev::components::mqtt
  32 +#include "lockguard.h"
  33 +#include "metaprogrammingdefs.h"
  34 +
  35 +namespace osdev {
  36 +namespace components {
  37 +namespace mqtt {
  38 +
  39 +OSDEV_COMPONENTS_HASMETHOD_TRAIT(capacity, )
  40 +
  41 +/*!
  42 + * \brief Generic Queue template for defining a thin
  43 + * wrapper around std::queue to make overflow detection possible.
  44 + * This template has no definition and will lead to a compile
  45 + * time error when it is chosen.
  46 + */
  47 +template <typename T, typename C, typename enable = void>
  48 +class Queue;
  49 +
  50 +/*!
  51 + * \brief A specialization when the underlying container has a capacity method.
  52 + * To detect overflow the capacity of the underlying container is needed.
  53 + * Not all containers have a capacity.
  54 + * When the capacity method is not available SFINAE will discard this template.
  55 + */
  56 +template <typename T, typename C>
  57 +class Queue<T, C, typename std::enable_if<has_capacity<C>::value>::type> : public std::queue<T, C>
  58 +{
  59 +public:
  60 + using size_type = typename std::queue<T, C>::size_type;
  61 +
  62 + typename C::size_type capacity() const
  63 + {
  64 + return this->c.capacity();
  65 + }
  66 +};
  67 +
  68 +/*!
  69 + * \brief A specialization for when the underlying container does not support a capacity
  70 + * In this case max_size is returned which results in overflow not being detected.
  71 + */
  72 +template <typename T, typename C>
  73 +class Queue<T, C, typename std::enable_if<!has_capacity<C>::value>::type> : public std::queue<T, C>
  74 +{
  75 +public:
  76 + using size_type = typename std::queue<T, C>::size_type;
  77 + typename C::size_type capacity() const
  78 + {
  79 + return this->c.max_size();
  80 + }
  81 +};
  82 +
  83 +/*!
  84 + * \brief Represents a synchronized queue
  85 + * @tparam T The type of the items in the queue
  86 + * @tparam C The underlying character. The container must satisfy the
  87 + * requirements of a SequenceContainer.
  88 + * Addittionally container must supply a pop_front and a max_size method.
  89 + *
  90 + * The underlying container determines the overflow behaviour.
  91 + * A circular buffer leads to a lossy queue that drops items when the
  92 + * queue is full while a std::deque will never overflow.
  93 + *
  94 + * The queue has the following states: started, paused, stopped.
  95 + * In the started state the queue acceptsincoming items and it allows items
  96 + * to be popped when data is available.
  97 + * In the paused state incoming items are allowed. The pop method will
  98 + * block until the queue is unpaused or stopped.
  99 + * In the stopped state incoming items are not allowed and dropped.
  100 + * The pop method will return false.
  101 + */
  102 +template <typename T, typename C = std::deque<T>>
  103 +class SynchronizedQueue
  104 +{
  105 +public:
  106 + using QueueType = Queue<T, C>;
  107 +
  108 + /*!
  109 + * \brief Constructs an empty queue
  110 + * \param id - Identification string for this queue ( used in logging ).
  111 + * \param paused - The state in which to setup the queue ( pause or active),
  112 + * ( Default is active )
  113 + */
  114 + explicit SynchronizedQueue( const std::string &id, bool paused = false )
  115 + : m_id( id )
  116 + , m_queueMutex()
  117 + , m_dataAvailableOrStopCV()
  118 + , m_queue()
  119 + , m_stop( false )
  120 + , m_pause( paused )
  121 + , m_numOverflows( 0 )
  122 + {}
  123 +
  124 + /*!
  125 + * \brief Stops the queue on destruction
  126 + */
  127 + ~SynchronizedQueue()
  128 + {
  129 + this->stop();
  130 + }
  131 +
  132 + /*!
  133 + * @brief Pushes the item in the queue
  134 + * @tparam TItem - The cv qualified type of the value to push.
  135 + * In a stopped state the queue drops incoming data.
  136 + * In a paused / active state the queue accepts incoming data.
  137 + * @tparam item - The item to push to the queue.
  138 + */
  139 + template <typename TItem>
  140 + void push(TItem &&item)
  141 + {
  142 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC( m_queueMutex, m_id );
  143 + if( m_stop )
  144 + {
  145 + return;
  146 + }
  147 +
  148 + if( m_queue.capacity() == m_queue.size() )
  149 + {
  150 + if( m_numOverflows++ % 100 == 0 )
  151 + {
  152 + // Log a warning that there is a number of overflows.
  153 + }
  154 + }
  155 + m_queue.push( std::forward<TItem>(item) );
  156 + OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK_SPECIFIC(m_queueMutex, m_id);
  157 + m_dataAvailableOrStopCV.notify_one();
  158 + }
  159 +
  160 + /*!
  161 + * @brief pop - Pops an item from the queue. This method blocks when te state is paused or when there is no data available.
  162 + * @param item - The item to which to copy the popped item.
  163 + * @return True if an item was popped: otherwise false
  164 + */
  165 + bool pop(T& item)
  166 + {
  167 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC( m_queueMutex, m_id );
  168 + m_dataAvailableOrStopCV.wait(OSDEV_COMPONENTS_UNIQUELOCK(m_queueMutex), [this]()
  169 + { return ((this->m_queue.size() > 0 && !m_pause) || this->m_stop); });
  170 + if( m_stop )
  171 + {
  172 + return false;
  173 + }
  174 + item = std::move(m_queue.front());
  175 + m_queue.pop();
  176 + return true;
  177 + }
  178 +
  179 + bool pop(std::vector<T> &items)
  180 + {
  181 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC( m_queueMutex, m_id );
  182 + m_dataAvailableOrStopCV.wait(OSDEV_COMPONENTS_UNIQUELOCK(m_queueMutex), [this]()
  183 + { return ((this->m_queue.size() > 0 && !m_pause) || this->m_stop); });
  184 + if( m_stop )
  185 + {
  186 + return false;
  187 + }
  188 + items.clear();
  189 + items.reserve(m_queue.size());
  190 + while( m_queue.size() > 0 )
  191 + {
  192 + items.emplace_back(std::move(m_queue.front() ) );
  193 + m_queue.pop();
  194 + }
  195 + return true;
  196 + }
  197 +
  198 + /*!
  199 + * \return The current size of the queue
  200 + */
  201 + typename QueueType::size_type size() const
  202 + {
  203 + OSDEV_COMPONENTS_LOCKGUARD_SPECIFIC(m_queueMutex, m_id);
  204 + return m_queue.size();
  205 + }
  206 +
  207 +
  208 + /*!
  209 + * \brief Start the Queue
  210 + * The queue is only started when it is in a stopped state.
  211 + * \param paused - If true, the queue will be started in a paused
  212 + * state which means that no items will be popped.
  213 + */
  214 + void start(bool paused)
  215 + {
  216 + // Reason that a lock is used: See documentation of std::condition_variable
  217 + //
  218 + // Even is the shared variable is atomic (m_stop in this case), it must be modified under the mutex
  219 + // in order to correctly publish the modification to the waiting thread.
  220 + //
  221 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC(m_queueMutex, m_id);
  222 + if( !m_stop )
  223 + {
  224 + // already started
  225 + return;
  226 + }
  227 + m_stop = false;
  228 + m_pause = paused;
  229 + OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK_SPECIFIC(m_queueMutex, m_id);
  230 + if( !paused )
  231 + {
  232 + m_dataAvailableOrStopCV.notify_all();
  233 + }
  234 + }
  235 +
  236 + /*!
  237 + * \brief Pause or unpause the queue.
  238 + * When the queue is paused no items will be popped.
  239 + * The state is not altered when the queue is stopped.
  240 + * \param value - Flag that indicates whether the queue is paused or unpaused.
  241 + */
  242 + void pause(bool value)
  243 + {
  244 + // Reason that a lock is used: see documentation of std::condition_variable
  245 + //
  246 + // Even if the shared variable is atomic (m_stop in this case), it must be modified under the mutex
  247 + // in order to correctly publish the modification to the waiting thread.
  248 + //
  249 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC(m_queueMutex, m_id);
  250 + if (m_stop) {
  251 + return;
  252 + }
  253 + m_pause = value;
  254 + OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK_SPECIFIC(m_queueMutex, m_id);
  255 + if (!value) {
  256 + m_dataAvailableOrStopCV.notify_all();
  257 + }
  258 + }
  259 +
  260 + /*!
  261 + * \brief Stop the queue.
  262 + * The pop method will return a false after calling this method.
  263 + * The queue can be restarted with the start method.
  264 + */
  265 + void stop()
  266 + {
  267 + // Reason that a lock is used: see documentation of std::condition_variable
  268 + //
  269 + // Even if the shared variable is atomic (m_stop in this case), it must be modified under the mutex
  270 + // in order to correctly publish the modification to the waiting thread.
  271 + //
  272 + OSDEV_COMPONENTS_UNIQUELOCK_CREATE_SPECIFIC(m_queueMutex, m_id);
  273 + m_stop = true;
  274 + m_pause = false;
  275 + OSDEV_COMPONENTS_UNIQUELOCK_UNLOCK_SPECIFIC(m_queueMutex, m_id);
  276 + m_dataAvailableOrStopCV.notify_all();
  277 + }
  278 +
  279 + /*!
  280 + * \brief Clears the queue.
  281 + * This method also resets the overflow counter.
  282 + */
  283 + void clear()
  284 + {
  285 + OSDEV_COMPONENTS_LOCKGUARD_SPECIFIC(m_queueMutex, m_id);
  286 + QueueType emptyQueue;
  287 + std::swap(m_queue, emptyQueue);
  288 + m_numOverflows = 0;
  289 + }
  290 +
  291 +private:
  292 + const std::string m_id; ///< Queue identification string
  293 + mutable std::mutex m_queueMutex; ///< Protects access to the queue
  294 + std::condition_variable m_dataAvailableOrStopCV; ///< Provides wait functionality for the queue becoming empty
  295 + QueueType m_queue; ///< Holds the items
  296 + std::atomic_bool m_stop; ///< Flag that indicates whether the queue needs to stop.
  297 + std::atomic_bool m_pause; ///< Flag that indicates whether the queue is paused.
  298 +
  299 + /*!
  300 + * \brief Counts the number of items that the buffer overflows.
  301 + * If the underlying buffer is a ring buffer an overflow
  302 + * means that an item will be overwritten. For a normal
  303 + * sequence container it means that the it is enlarged.
  304 + */
  305 + std::uint32_t m_numOverflows;
  306 +};
  307 +
  308 +
  309 +
  310 +
  311 +
  312 +} // End namespace mqtt
  313 +} // End namespace components
  314 +} // End namespace osdev
  315 +
  316 +#endif // OSDEV_COMPONENTS_MQTT_SYNCHRONIZEDQUEUE_H
... ...
src/timemeasurement.cpp 0 → 100644
  1 +++ a/src/timemeasurement.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "timemeasurement.h"
  20 +
  21 +using namespace osdev::components::mqtt::measurement;
  22 +
  23 +TimeMeasurement::TimeMeasurement(const std::string& id, const TimeMeasurementCallback& cb, bool measureOnDestruction)
  24 + : m_id(id)
  25 + , m_start(std::chrono::steady_clock::now())
  26 + , m_last(m_start)
  27 + , m_callback(cb)
  28 + , m_measureOnDestruction(measureOnDestruction)
  29 +{
  30 +}
  31 +
  32 +TimeMeasurement::~TimeMeasurement()
  33 +{
  34 + if (m_measureOnDestruction) {
  35 + this->measure();
  36 + }
  37 +}
  38 +
  39 +void TimeMeasurement::set()
  40 +{
  41 + m_last = std::chrono::steady_clock::now();
  42 +}
  43 +
  44 +void TimeMeasurement::measure()
  45 +{
  46 + auto now = std::chrono::steady_clock::now();
  47 + auto sinceLast = std::chrono::duration_cast<std::chrono::microseconds>(now - m_last);
  48 + auto sinceStart = std::chrono::duration_cast<std::chrono::microseconds>(now - m_start);
  49 + m_last = now;
  50 +
  51 + m_callback(m_id, m_start, sinceStart, sinceLast);
  52 +}
... ...
src/timemeasurement.h 0 → 100644
  1 +++ a/src/timemeasurement.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_MEASUREMENT_TIMEMEASUREMENT_H
  2 +#define OSDEV_COMPONENTS_MQTT_MEASUREMENT_TIMEMEASUREMENT_H
  3 +
  4 +#include <chrono>
  5 +#include <functional>
  6 +#include <ostream>
  7 +#include <string>
  8 +
  9 +namespace osdev {
  10 +namespace components {
  11 +namespace mqtt {
  12 +namespace measurement {
  13 +
  14 +using TimeMeasurementCallback = std::function<void(const std::string& id, std::chrono::steady_clock::time_point start, std::chrono::microseconds sinceStart, std::chrono::microseconds sinceLast)>;
  15 +
  16 +class TimeMeasurement
  17 +{
  18 +public:
  19 + TimeMeasurement(const std::string& id, const TimeMeasurementCallback& callback, bool measureOnDestruction = true);
  20 + ~TimeMeasurement();
  21 +
  22 + TimeMeasurement(const TimeMeasurement&) = delete;
  23 + TimeMeasurement& operator=(const TimeMeasurement&) = delete;
  24 + TimeMeasurement(TimeMeasurement&&) = default;
  25 + TimeMeasurement& operator=(TimeMeasurement&&) = default;
  26 +
  27 + void set();
  28 + void measure();
  29 +
  30 +private:
  31 + std::string m_id;
  32 +
  33 + std::chrono::steady_clock::time_point m_start;
  34 + std::chrono::steady_clock::time_point m_last;
  35 + TimeMeasurementCallback m_callback;
  36 + bool m_measureOnDestruction;
  37 +};
  38 +
  39 +template <typename Rep, typename Dur>
  40 +std::ostream& operator<<(std::ostream& os, const std::chrono::duration<Rep, Dur>& rhs)
  41 +{
  42 + os << rhs.count();
  43 + return os;
  44 +}
  45 +
  46 +} // End namespace measurement
  47 +} // End namespace mqtt
  48 +} // End namespace components
  49 +} // End namespace osdev
  50 +
  51 +#endif // OSDEV_COMPONENTS_MQTT_MEASUREMENT_TIMEMEASUREMENT_H
... ...
src/token.cpp 0 → 100644
  1 +++ a/src/token.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "token.h"
  20 +
  21 +using namespace osdev::components::mqtt;
  22 +
  23 +Token::Token()
  24 + : m_clientId( "" )
  25 + , m_token( -1 )
  26 +{}
  27 +
  28 +Token::Token( const std::string &_clientId, std::int32_t _tokenNr )
  29 + : m_clientId( _clientId )
  30 + , m_token( _tokenNr )
  31 +{}
  32 +
  33 +bool Token::equals( const Token &rhs ) const
  34 +{
  35 + return ( rhs.m_clientId == m_clientId && rhs.m_token == m_token);
  36 +}
  37 +
  38 +bool Token::smallerThan( const Token &rhs ) const
  39 +{
  40 + if( m_clientId < rhs.m_clientId )
  41 + {
  42 + return true;
  43 + }
  44 + else if( rhs.m_clientId < m_clientId )
  45 + {
  46 + return false;
  47 + }
  48 +
  49 + return m_token < rhs.m_token;
  50 +}
  51 +
  52 +namespace osdev {
  53 +namespace components {
  54 +namespace mqtt {
  55 +
  56 +std::ostream& operator<<(std::ostream &os, const Token &rhs )
  57 +{
  58 + return os << rhs.clientId() << '-' << rhs.token();
  59 +}
  60 +
  61 +} // End namespace mqtt
  62 +} // End namespace components
  63 +} // End namespace osdev
... ...
src/token.h 0 → 100644
  1 +++ a/src/token.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_TOKEN_H
  2 +#define OSDEV_COMPONENTS_MQTT_TOKEN_H
  3 +
  4 +// std
  5 +#include <ostream>
  6 +#include <string>
  7 +
  8 +// paho
  9 +#include <MQTTAsync.h>
  10 +
  11 +namespace osdev {
  12 +namespace components {
  13 +namespace mqtt {
  14 +
  15 +/*!
  16 + * \brief The Token class defines an operation token
  17 + */
  18 +class Token
  19 +{
  20 +public:
  21 + /*! @brief Construct an invalid token.
  22 + * The token number is -1 in that case. The client is undefined, in this case empty.
  23 + */
  24 + Token();
  25 +
  26 + /*! @brief Constructs token for an operation originating from specific client wrapper.
  27 + * @param clientId - Identifies the client wrapper
  28 + * @param tokenNr - Identifies the operation done on that client.
  29 + */
  30 + Token( const std::string &clientId, std::int32_t tokenNr );
  31 +
  32 + /*! @return True when token has a valid token number, false otherwise. */
  33 + bool isValid() const { return -1 == m_token; }
  34 +
  35 + /*! @return The operation token */
  36 + const std::string& clientId() const { return m_clientId; }
  37 +
  38 + /*! @return The operation token */
  39 + std::int32_t token() const { return m_token; }
  40 +
  41 + /*! @return True if Tokens have the same clientId and token number, false otherwise. */
  42 + bool equals( const Token &rhs ) const;
  43 +
  44 + /*!
  45 + * @brief Token is ordered.
  46 + * First on lexical test of clientId and with same clientId on token number.
  47 + */
  48 + bool smallerThan( const Token &rhs ) const;
  49 +
  50 +private:
  51 + std::string m_clientId; ///< Identified the client
  52 + std::int32_t m_token; ///< Identifies the operation on that client.
  53 +};
  54 +
  55 +/**
  56 + * @return True if Tokens have the same clientId and token number, false otherwise.
  57 + */
  58 +inline bool operator==( const Token &lhs, const Token &rhs )
  59 +{
  60 + return lhs.equals( rhs );
  61 +}
  62 +
  63 +inline bool operator==( const Token &lhs, std::int32_t rhs )
  64 +{
  65 + return lhs.token() == rhs;
  66 +}
  67 +
  68 +inline bool operator==( std::int32_t lhs, const Token &rhs )
  69 +{
  70 + return lhs == rhs;
  71 +}
  72 +
  73 +template <typename TLeft, typename TRight>
  74 +inline bool operator!=( const TLeft &lhs, const TRight &rhs )
  75 +{
  76 + return !( lhs == rhs );
  77 +}
  78 +
  79 +/*!
  80 + * @return True if Token lhs is smaller than token rhs
  81 + */
  82 +inline bool operator<( const Token &lhs, std::int32_t rhs )
  83 +{
  84 + return lhs.token() < rhs;
  85 +}
  86 +
  87 +inline bool operator<( std::int32_t lhs, const Token &rhs )
  88 +{
  89 + return lhs < rhs.token();
  90 +}
  91 +
  92 +inline bool operator<( const Token &lhs, const Token &rhs )
  93 +{
  94 + return lhs.smallerThan( rhs );
  95 +}
  96 +
  97 +/*!
  98 + * @brief Stream operator for a Token
  99 + */
  100 +std::ostream& operator<<( std::ostream &os, const Token &rhs );
  101 +
  102 +} // End namespace mqtt
  103 +} // End namespace components
  104 +} // End namespace osdev
  105 +
  106 +#endif // OSDEV_COMPONENTS_MQTT_TOKEN_H
... ...
src/uriparser.cpp 0 → 100644
  1 +++ a/src/uriparser.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "uriparser.h"
  20 +
  21 +// std
  22 +#include <iostream>
  23 +#include <sstream>
  24 +#include <utility>
  25 +
  26 +// gnu-c
  27 +#include <netdb.h>
  28 +
  29 +// boost
  30 +#include <boost/algorithm/string/case_conv.hpp>
  31 +#include <boost/algorithm/string/split.hpp>
  32 +#include <boost/lexical_cast.hpp>
  33 +#include <boost/regex.hpp>
  34 +
  35 +// osdev::components::mqtt
  36 +#include "bimap.h"
  37 +#include "stringutils.h"
  38 +#include "uriutils.h"
  39 +
  40 +// mlogic::common::logger
  41 +// #include "mlogic/common/logger/loggerprovider.h"
  42 +// #include "mlogic/common/invalidargumentexception.h"
  43 +// #include "mlogic/common/nullptrexception.h"
  44 +// #include "mlogic/common/systemexception.h"
  45 +
  46 +
  47 +using namespace osdev::components::mqtt;
  48 +
  49 +namespace {
  50 +
  51 +/**
  52 + * @brief Copies an item from the from container to the to container.
  53 + * @tparam TFrom The type of the container from which to copy.
  54 + * @tparam TTo The type of the container to which to copy.
  55 + * @param itemName The name of the item to copy.
  56 + * @param from The container from which to copy.
  57 + * @param to The cointainer to which to copy.
  58 + * @param transformation Apply transformation function to the input. Default no transformation.
  59 + */
  60 +template <typename TFrom, typename TTo>
  61 +void copyItem(const std::string& itemName, const TFrom& from, TTo& to, const std::function<std::string(const std::string&)>& transformation = std::function<std::string(const std::string&)>())
  62 +{
  63 + if (transformation) {
  64 + to.insert(std::make_pair(itemName, transformation(from[itemName])));
  65 + }
  66 + else {
  67 + to.insert(std::make_pair(itemName, from[itemName]));
  68 + }
  69 +}
  70 +
  71 +const std::string& getItem(const std::map<std::string, std::string>& source, const std::string& itemName)
  72 +{
  73 + static const std::string s_empty;
  74 + const auto cit = source.find(itemName);
  75 + if (cit != source.end()) {
  76 + return cit->second;
  77 + }
  78 + return s_empty;
  79 +}
  80 +
  81 +static const boost::bimap<char, std::string>& getReservedCharacterMap()
  82 +{
  83 + static const auto s_lookupTable = makeBimap<char, std::string>(
  84 + { { ':', percentEncode<':'>() },
  85 + { '/', percentEncode<'/'>() },
  86 + { '?', percentEncode<'?'>() },
  87 + { '#', percentEncode<'#'>() },
  88 + { '[', percentEncode<'['>() },
  89 + { ']', percentEncode<']'>() },
  90 + { '@', percentEncode<'@'>() },
  91 + { '!', percentEncode<'!'>() },
  92 + { '$', percentEncode<'$'>() },
  93 + { '&', percentEncode<'&'>() },
  94 + { '\'', percentEncode<'\''>() },
  95 + { '(', percentEncode<'('>() },
  96 + { ')', percentEncode<')'>() },
  97 + { '*', percentEncode<'*'>() },
  98 + { '+', percentEncode<'+'>() },
  99 + { ',', percentEncode<','>() },
  100 + { ';', percentEncode<';'>() },
  101 + { '=', percentEncode<'='>() },
  102 +
  103 + { '"', percentEncode<'"'>() },
  104 + { '%', percentEncode<'%'>() },
  105 + { '-', percentEncode<'-'>() },
  106 + { '.', percentEncode<'.'>() },
  107 + { '<', percentEncode<'<'>() },
  108 + { '>', percentEncode<'>'>() },
  109 + { '\\', percentEncode<'\\'>() },
  110 + { '^', percentEncode<'^'>() },
  111 + { '_', percentEncode<'_'>() },
  112 + { '`', percentEncode<'`'>() },
  113 + { '{', percentEncode<'{'>() },
  114 + { '|', percentEncode<'|'>() },
  115 + { '}', percentEncode<'}'>() },
  116 + { '~', percentEncode<'~'>() } });
  117 +
  118 + return s_lookupTable;
  119 +}
  120 +
  121 +std::string decode(const std::string& in)
  122 +{
  123 + static constexpr size_t encodingTokenSize = 3; // example: %20 encodes a space character.
  124 + const auto& reservedLookup = getReservedCharacterMap();
  125 +
  126 + std::string out = in;
  127 + std::size_t pos = 0;
  128 + while ((pos = out.find('%', pos)) != std::string::npos) {
  129 + if (pos + encodingTokenSize > out.size()) {
  130 + // MLOGIC_COMMON_THROW(InvalidArgumentException, "Invalid encoding at end of string");
  131 + }
  132 + const auto cit = reservedLookup.right.find(out.substr(pos, 3));
  133 + if (reservedLookup.right.end() != cit) {
  134 + // string& replace (size_t pos, size_t len, size_t n, char c)
  135 + // where n is the number of fill characters (1 in this case).
  136 + out.replace(pos, encodingTokenSize, 1, cit->second);
  137 + }
  138 + ++pos;
  139 + }
  140 + return out;
  141 +}
  142 +
  143 +std::string encode(const std::string& in)
  144 +{
  145 + const auto& reservedLookup = getReservedCharacterMap();
  146 +
  147 + std::string out = in;
  148 + for (size_t pos = 0; pos < out.size(); ++pos) {
  149 + const auto cit = reservedLookup.left.find(out[pos]);
  150 + if (reservedLookup.left.end() != cit) {
  151 + out.replace(pos, 1, cit->second);
  152 + pos += 2;
  153 + }
  154 + }
  155 + return out;
  156 +}
  157 +
  158 +} // anonymous
  159 +
  160 +
  161 +// static
  162 +ParsedUri UriParser::parse(const std::string& uri)
  163 +{
  164 + // Before turning to regular expressions, the following ibraries were evaluated to achieve this functionality:
  165 + // Qt QUrlParser: http://doc.qt.io/qt-4.8/qurl.html
  166 + // cpp-netlib: https://github.com/cpp-netlib/uri
  167 + // uriparser: http://uriparser.sourceforge.net/
  168 + // From the above, cpp-netlib was the most compelling because of its pending standardization for C++(17?).
  169 + // However, none of these libraries could handle strings for port service names, so a custom implementation seems necessary.
  170 + // As an additional validation step, one of the above libraries could be used after the port service name was replaced (see below).
  171 + //
  172 + // Split the uri in two stages. First break down the uri in its components: scheme (cannot be empty), authority (cannot be empty), path, query, fragment.
  173 + // The path, query and fragment parts are optional. Because the scheme and authority part cannot be empty only a subset of uri's is handled by this function.
  174 + // In the second stage the authority is parsed to get the hostname and the port. In order to use an ipv6 host address it must be
  175 + // wrapped in brackets. The addresses are not validated whether they are correct. When a open bracket is found the part that is between brackets
  176 + // must contain at least two colons. This is enough to discern something that resembles an ipv6 address from the other possibilities.
  177 + static const std::string regexUriString(
  178 + R"regex(^(?<scheme>[^:/?#]+)://(?<authority>[^/?#]+)(?<path>[^?#]*)(?<q1>\?(?<query>[^#]*))?(?<f1>#(?<fragment>.*))?)regex");
  179 +
  180 + // This regex only checks for non occurrence of the @ symbol since the input is the result from the regexUri where it is already validated that characters "/>#" do not exist
  181 + // in the authority part. This regexAuthority uses the if then else construct "(?(?=regex)then|else)" to choose between ipv6 or something else.
  182 + static const std::string regexAuthorityString(
  183 + R"regex(^((?<user>[^:@]+)(:(?<password>[^@]+))?@)?(?(?=(?<ipv6>\[))\[(?=(.*:){2,}.*)(?<host>[^@]+)\]|(?<host>[^:@]+))(?<p1>:(?<port>[^:@]+))?$)regex");
  184 +
  185 + static boost::regex uriRegex(regexUriString);
  186 + boost::cmatch whatUri;
  187 + auto uriMatches = boost::regex_match(
  188 + uri.c_str(),
  189 + whatUri,
  190 + uriRegex);
  191 + if (!uriMatches)
  192 + {
  193 + // ErrorLogToFile("UriParser", "Invalid uri: '%1'", uri);
  194 + // throw (InvalidArgumentException, "No match for the specified uri.");
  195 + }
  196 +
  197 + static boost::regex authorityRegex(regexAuthorityString);
  198 + boost::cmatch whatAuthority;
  199 + std::string authority = whatUri["authority"];
  200 + auto authorityMatches = boost::regex_match(
  201 + authority.c_str(),
  202 + whatAuthority,
  203 + authorityRegex);
  204 + if (!authorityMatches)
  205 + {
  206 + // ErrorToLogFile("UriParser", "Uri contains invalid authority part: %1", authority);
  207 + // Throw (InvalidArgumentException, "Uri contains invalid authority part.");
  208 + }
  209 +
  210 + static const auto toLower = [](const std::string& in) -> std::string { return boost::to_lower_copy(in); };
  211 +
  212 + ParsedUri parsedUri;
  213 + copyItem("scheme", whatUri, parsedUri, toLower);
  214 + copyItem("user", whatAuthority, parsedUri, &decode);
  215 + copyItem("password", whatAuthority, parsedUri, &decode);
  216 + copyItem("ipv6", whatAuthority, parsedUri); // Acts as a flag. Empty means not ipv6, not empty means ipv6 (this is not validated in the parse however!)
  217 + copyItem("host", whatAuthority, parsedUri, toLower);
  218 + copyItem("port", whatAuthority, parsedUri, toLower);
  219 + copyItem("path", whatUri, parsedUri);
  220 + copyItem("query", whatUri, parsedUri);
  221 + copyItem("fragment", whatUri, parsedUri, &decode);
  222 +
  223 + return parsedUri;
  224 +}
  225 +
  226 +// static
  227 +ParsedQuery UriParser::parseQuery(const ParsedUri& parsedUri)
  228 +{
  229 + const auto cit = parsedUri.find("query");
  230 + if (parsedUri.end() == cit) {
  231 + return {};
  232 + }
  233 +
  234 + const auto& queryString = cit->second;
  235 +
  236 + std::vector<std::string> keyValues;
  237 + boost::algorithm::split(keyValues, queryString, [](char ch) { return ch == '&'; });
  238 +
  239 + std::map<std::string, std::string> retval;
  240 +
  241 + for (const auto& query : keyValues) {
  242 + auto pos = query.find('=');
  243 + if (std::string::npos != pos) {
  244 + retval[decode(query.substr(0, pos))] = pos + 1 < query.size() ? decode(query.substr(pos + 1)) : "";
  245 + }
  246 + }
  247 + return retval;
  248 +}
  249 +
  250 +// static
  251 +ParsedPath UriParser::parsePath(const ParsedUri& parsedUri)
  252 +{
  253 + const auto cit = parsedUri.find("path");
  254 + if (parsedUri.end() == cit) {
  255 + return {};
  256 + }
  257 +
  258 + const auto& pathString = cit->second;
  259 +
  260 + ParsedPath pathElements;
  261 + if (!pathString.empty()) {
  262 + const auto path = pathString.substr(1);
  263 + boost::algorithm::split(pathElements, path, [](char ch) { return ch == '/'; }); // empty string will lead to a single pathElement
  264 + std::transform(pathElements.begin(), pathElements.end(), pathElements.begin(), &decode);
  265 + }
  266 +
  267 + return pathElements;
  268 +}
  269 +
  270 +// static
  271 +std::string UriParser::normalize(const std::string& uri)
  272 +{
  273 + auto parsedUri = parse(uri);
  274 +
  275 + auto& portString = parsedUri["port"];
  276 + if (!portString.empty() && !is_numeric(portString)) {
  277 + auto portnumber = getPortnumber(portString);
  278 + portString = boost::lexical_cast<std::string>(portnumber);
  279 + }
  280 +
  281 + return toString(parsedUri);
  282 +}
  283 +
  284 +// static
  285 +std::string UriParser::toString(const ParsedUri& parsedUri)
  286 +{
  287 + const auto& schemeString = getItem(parsedUri, "scheme");
  288 + const auto& hostString = getItem(parsedUri, "host");
  289 +
  290 + if( schemeString.empty() )
  291 + {
  292 + // Do something sensible
  293 + }
  294 +
  295 + if( hostString.empty() )
  296 + {
  297 + // Do something sensible.
  298 + }
  299 +
  300 + const auto& user = getItem(parsedUri, "user");
  301 + const auto& password = getItem(parsedUri, "password");
  302 + const auto& portString = getItem(parsedUri, "port");
  303 + const auto& pathString = getItem(parsedUri, "path");
  304 + const auto& queryString = getItem(parsedUri, "query");
  305 + const auto& fragmentString = getItem(parsedUri, "fragment");
  306 +
  307 + // wrap hostname in brackets only if the incoming url uses them
  308 + bool ipv6 = !getItem(parsedUri, "ipv6").empty();
  309 +
  310 + std::ostringstream oss;
  311 + oss << schemeString << "://";
  312 +
  313 + if (!user.empty()) {
  314 + oss << encode(user);
  315 + if (!password.empty()) {
  316 + oss << ":" << encode(password);
  317 + }
  318 + oss << "@";
  319 + }
  320 +
  321 + if (ipv6) {
  322 + oss << '[';
  323 + }
  324 + oss << hostString;
  325 + if (ipv6) {
  326 + oss << ']';
  327 + }
  328 +
  329 + if (!portString.empty()) {
  330 + oss << ':' << portString;
  331 + }
  332 +
  333 + if (!pathString.empty()) {
  334 + auto pathElements = UriParser::parsePath(parsedUri);
  335 + for (const auto& element : pathElements) {
  336 + oss << "/" << encode(element);
  337 + }
  338 + }
  339 + if (!queryString.empty()) {
  340 + auto queryElements = UriParser::parseQuery(parsedUri);
  341 + oss << '?';
  342 + for (auto cit = queryElements.cbegin(); queryElements.cend() != cit; ++cit) {
  343 + oss << encode(cit->first) << '=' << encode(cit->second);
  344 + if (next(cit) != queryElements.cend()) {
  345 + oss << '&';
  346 + }
  347 + }
  348 + }
  349 + if (!fragmentString.empty()) {
  350 + oss << '#' << encode(fragmentString);
  351 + }
  352 + return oss.str();
  353 +}
  354 +
  355 +// static
  356 +int UriParser::getPortnumber(const std::string& service, const std::string& protocolName)
  357 +{
  358 + const unsigned int bufSize = 1024;
  359 + struct servent data;
  360 + struct servent* result;
  361 + char buf[bufSize]; // contains the strings that data points to
  362 + ::getservbyname_r(service.c_str(), protocolName.c_str(), &data, buf, bufSize, &result);
  363 + if (nullptr == result)
  364 + {
  365 + // ErrorLogToFile ("UriParser", "Could not determine the portnumber for the specified service: %1 for protocol: %2. Please check the portnumber configuration in /etc/services.", service, protocolName);
  366 + // throw (?) (InvalidArgumentException, "Could not determine the portnumber for the specified service and/or protocol.");
  367 + }
  368 +
  369 +// htonx functions need -Wold-style-cast disabled
  370 +#pragma GCC diagnostic push
  371 +#pragma GCC diagnostic ignored "-Wold-style-cast"
  372 + return static_cast<int>(ntohs(static_cast<uint16_t>(data.s_port)));
  373 +#pragma GCC diagnostic pop
  374 +}
... ...
src/uriparser.h 0 → 100644
  1 +++ a/src/uriparser.h
  1 +#ifndef OSDEV_COMPONENTS_MQTT_URIPARSER_H
  2 +#define OSDEV_COMPONENTS_MQTT_URIPARSER_H
  3 +
  4 +// std
  5 +#include <string>
  6 +
  7 +#include "commondefs.h"
  8 +
  9 +namespace osdev {
  10 +namespace components {
  11 +namespace mqtt {
  12 +
  13 +/**
  14 + * @brief Helper class to parse, normalize, and replace the port service name by portnumber in a uri.
  15 + * example:
  16 + * opc.tcp://user:secret\@processmodel:processmodel/path1/path2?query3=value4#fragment5
  17 + * will result in
  18 + * opc.tcp://user:secret\@processmodel:12345/path1/path2?query3=value4#fragment5
  19 + * @note This assumes that the port service name was registered in the etc/services file under protocol tcp.
  20 + * Lookup of a portnumber for protocols other than tcp are not supported.
  21 + *
  22 + * IPv6 addresses in a uri are only parsable when they are wrapped in brackets ([]) (RFC 3986, section 3.2.2)
  23 + *
  24 + * @note This class is designed to handle a subset of all possible uris. The scheme and the authority part are
  25 + * expected not to be empty. The authority part can contain user and password information.
  26 + *
  27 + * @note Only scheme, hostname and port are converted to lowercase (see RFC 3986 6.2.2.1. Case Normalization)
  28 + *
  29 + * @note Special characters are escaped with percent encoding. This applies to user, password, path, query and fragment parts.
  30 + * The path and query part is escaped on a per element basis. This means that ParsedUri contains the raw query and path strings.
  31 + * The decoding is done when the methods parseQuery and parsePath are called.
  32 + */
  33 +class UriParser
  34 +{
  35 +public:
  36 + /**
  37 + * @brief Parses the specified uri string.
  38 + * @param uri The uri string to parse.
  39 + * @return A map containing the parsed uri.
  40 + * @note The path and query parts can still contain percent encoded special characters.
  41 + */
  42 + static ParsedUri parse(const std::string& uri);
  43 +
  44 + /**
  45 + * @brief Parse the query part of a parsed uri. Percent encoded special characters are decoded.
  46 + * @param parsedUri A ParsedUri object.
  47 + * @return key/value map.
  48 + */
  49 + static ParsedQuery parseQuery(const ParsedUri& parsedUri);
  50 +
  51 + /**
  52 + * @brief Parse the path part of a parsed uri. Percent encoded special characters are decoded.
  53 + * @param parsedUri A ParsedUri object.
  54 + * @return vector of path elements.
  55 + */
  56 + static ParsedPath parsePath(const ParsedUri& parsedUri);
  57 +
  58 + /**
  59 + * @brief Parses, normalizes, and replaces the port service name by portnumber in the specified uri string.
  60 + * @param uri The uri string to parse and normalize.
  61 + * @return The normalized uri string, with the port service name replaced by the portnumber.
  62 + */
  63 + static std::string normalize(const std::string& uri);
  64 +
  65 + /**
  66 + * @brief Converts a parsed uri back to a string.
  67 + * @param parsedUri The parsed uri to convert to string.
  68 + * @return The uri as string.
  69 + */
  70 + static std::string toString(const ParsedUri& parsedUri);
  71 +
  72 + /**
  73 + * @brief Get portnumber associated with a service.
  74 + * @param serviceName Name of the service for which a portnumber is searched.
  75 + * @param protocolName Name of the protocol for which the portname was registered.
  76 + * @return portnumber.
  77 + * @throws InvalidArgumentException when service is unknown.
  78 + * @throws SystemException when underlying systemcall fails.
  79 + */
  80 + static int getPortnumber(const std::string& serviceName, const std::string& protocolName = "tcp");
  81 +};
  82 +
  83 +} // End namespace mqtt
  84 +} // End namespace components
  85 +} // End namespace osdev
  86 +
  87 +#endif // OSDEV_COMPONENTS_MQTT_URIPARSER_H
... ...
src/uriutils.h 0 → 100644
  1 +++ a/src/uriutils.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_URIUTILS_H
  20 +#define OSDEV_COMPONENTS_MQTT_URIUTILS_H
  21 +
  22 +// std
  23 +#include <string>
  24 +
  25 +#include "compiletimedigits.h"
  26 +
  27 +namespace osdev {
  28 +namespace components {
  29 +namespace mqtt {
  30 +
  31 +/**
  32 + * @brief Get the percent encoded string for a given character.
  33 + * @tparam N The character to encode.
  34 + * @return pointer to the encoded character string.
  35 + */
  36 +template <unsigned N>
  37 +inline const char* percentEncode()
  38 +{
  39 + static const auto* s_code =
  40 + (compiletime_string<'%'>{} + typename apply_bounded_range< //
  41 + 0, //
  42 + numberOfDigits<16>(N), //
  43 + string_builder< //
  44 + adapt_for_string_builder< //
  45 + ProduceDigits<N, 16>>>::template produce>::result{})
  46 + .chars;
  47 + return s_code;
  48 +}
  49 +
  50 +} // End namespace mqtt
  51 +} // End namespace components
  52 +} // End namespace osdev
  53 +
  54 +#endif // OSDEV_COMPONENTS_MQTT_URIUTILS_H
... ...
src/utils.cpp 0 → 100644
  1 +++ a/src/utils.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "utils.h"
  20 +
  21 +// std
  22 +#include <libgen.h>
  23 +#include <unistd.h>
  24 +#include <sstream>
  25 +
  26 +#include <linux/limits.h>
  27 +
  28 +using namespace osdev::components::mqtt;
  29 +
  30 +std::string getPathToBinary(int pid)
  31 +{
  32 + static const int sMaxPath = PATH_MAX;
  33 + pid_t p = pid;
  34 + if (-1 == pid)
  35 + {
  36 + p = getpid();
  37 + }
  38 + std::ostringstream oss;
  39 + oss << "/proc/" << p << "/exe";
  40 + char buf[sMaxPath] = { 0 };
  41 + ssize_t r = readlink(oss.str().c_str(), buf, sMaxPath);
  42 + if (-1 == r)
  43 + {
  44 + // "readlink failed"
  45 + }
  46 + if (r >= sMaxPath)
  47 + {
  48 + buf[sMaxPath - 1] = '\0'; // make the buffer 0 terminated
  49 + // "path is too long"
  50 + }
  51 + return dirname(buf);
  52 +}
... ...
src/utils.h 0 → 100644
  1 +++ a/src/utils.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#ifndef OSDEV_COMPONENTS_MQTT_UTILS_H
  20 +#define OSDEV_COMPONENTS_MQTT_UTILS_H
  21 +
  22 +// std
  23 +#include <algorithm>
  24 +#include <functional>
  25 +#include <memory>
  26 +#include <vector>
  27 +
  28 +namespace osdev {
  29 +namespace components {
  30 +namespace mqtt {
  31 +
  32 +/**
  33 + * @brief Does nothing.
  34 + * Utility template function to explicitly use parameters, that would otherwise be unused.
  35 + * This helps to prevent the -Wunused-parameter warning.
  36 + */
  37 +template <typename... Args>
  38 +void apply_unused_parameters(const Args&...)
  39 +{
  40 +}
  41 +
  42 +/**
  43 + * @brief Converts (dynamic_cast) a std::unique_ptr from TFrom to TTo.
  44 + * @param from The std::unique_ptr to convert from TFrom to TTo.
  45 + * @return The converted std::unique_ptr.
  46 + */
  47 +template <typename TTo, typename TFrom>
  48 +std::unique_ptr<TTo> dynamic_unique_ptr_cast(std::unique_ptr<TFrom>&& from)
  49 +{
  50 + auto to = dynamic_cast<TTo*>(from.get());
  51 + if (nullptr == to) {
  52 + return std::unique_ptr<TTo>(nullptr);
  53 + }
  54 +
  55 + from.release();
  56 + return std::unique_ptr<TTo>(to);
  57 +}
  58 +
  59 +/**
  60 + * @brief Converts (dynamic_cast) a std::unique_ptr from TFrom to TTo.
  61 + * @param from The std::unique_ptr to convert from TFrom to TTo.
  62 + * @return The converted std::unique_ptr.
  63 + */
  64 +template <typename TTo, typename TFrom, typename TDeleter>
  65 +std::unique_ptr<TTo, TDeleter> dynamic_unique_ptr_cast(std::unique_ptr<TFrom, TDeleter>&& from)
  66 +{
  67 + auto to = dynamic_cast<TTo*>(from.get());
  68 + if (nullptr == to) {
  69 + return std::unique_ptr<TTo, TDeleter>(nullptr, from.get_deleter());
  70 + }
  71 +
  72 + from.release();
  73 + return std::unique_ptr<TTo, TDeleter>(to, std::move(from.get_deleter()));
  74 +}
  75 +
  76 +/**
  77 + * @brief Helper class for iteration of keys of a map.
  78 + */
  79 +template <typename TMap>
  80 +class KeyIterator : public TMap::iterator
  81 +{
  82 +public:
  83 + typedef typename TMap::iterator MapIterator;
  84 + typedef typename MapIterator::value_type::first_type KeyType;
  85 +
  86 + KeyIterator(const MapIterator& other)
  87 + : TMap::iterator(other)
  88 + {
  89 + }
  90 +
  91 + KeyType& operator*()
  92 + {
  93 + return TMap::iterator::operator*().first;
  94 + }
  95 +};
  96 +
  97 +/**
  98 + * @brief Helper function to get the begin KeyIterator from a map.
  99 + */
  100 +template <typename MapType>
  101 +KeyIterator<MapType> KeyBegin(MapType& map)
  102 +{
  103 + return KeyIterator<MapType>(map.begin());
  104 +}
  105 +
  106 +/**
  107 + * @brief Helper function to get the end KeyIterator from a map.
  108 + */
  109 +template <typename MapType>
  110 +KeyIterator<MapType> KeyEnd(MapType& map)
  111 +{
  112 + return KeyIterator<MapType>(map.end());
  113 +}
  114 +
  115 +/**
  116 + * @brief Helper class for iteration of keys of a const map.
  117 + */
  118 +template <typename TMap>
  119 +class KeyConstIterator : public TMap::const_iterator
  120 +{
  121 + typedef typename TMap::const_iterator TMapIterator;
  122 + typedef typename TMapIterator::value_type::first_type TKeyType;
  123 +
  124 +public:
  125 + KeyConstIterator(const TMapIterator& other)
  126 + : TMapIterator(other)
  127 + {
  128 + }
  129 +
  130 + const TKeyType& operator*()
  131 + {
  132 + return TMapIterator::operator*().first;
  133 + }
  134 +};
  135 +
  136 +/**
  137 + * @brief Helper function to get the cbegin KeyConstIterator from a const map.
  138 + */
  139 +template <typename TMap>
  140 +KeyConstIterator<TMap> KeyBegin(const TMap& map)
  141 +{
  142 + return KeyConstIterator<TMap>(map.cbegin());
  143 +}
  144 +
  145 +/**
  146 + * @brief Helper function to get the cend KeyConstIterator from a const map.
  147 + */
  148 +template <typename TMap>
  149 +KeyConstIterator<TMap> KeyEnd(const TMap& map)
  150 +{
  151 + return KeyConstIterator<TMap>(map.cend());
  152 +}
  153 +
  154 +/**
  155 + * @brief Helper function to get the difference of the keys in two maps.
  156 + * @param map1 The first map for which to examine the keys.
  157 + * @param map2 The second map for which to examine the keys.
  158 + * @return Collection of keys present in map1, but not in map2.
  159 + * @note The types of the keys in the maps must be identical.
  160 + */
  161 +template <typename TMap1, typename TMap2>
  162 +std::vector<typename TMap1::key_type> keyDifference(
  163 + const TMap1& map1,
  164 + const TMap2& map2,
  165 + const std::function<bool(const typename TMap1::key_type& key1, const typename TMap1::key_type& key2)>& keyCompare = std::less<typename TMap1::key_type>())
  166 +{
  167 + static_assert(std::is_same<
  168 + typename TMap1::key_type,
  169 + typename TMap2::key_type>::value,
  170 + "Inconsistent key types.");
  171 +
  172 + typedef typename TMap1::key_type Key;
  173 + std::vector<Key> onlyInMap1;
  174 + std::set_difference(
  175 + KeyBegin(map1),
  176 + KeyEnd(map1),
  177 + KeyBegin(map2),
  178 + KeyEnd(map2),
  179 + std::inserter(onlyInMap1, onlyInMap1.begin()),
  180 + keyCompare);
  181 + return onlyInMap1;
  182 +}
  183 +
  184 +/**
  185 + * @brief Helper function to get the intersection of the keys in two maps.
  186 + * @param map1 The first map for which to examine the keys.
  187 + * @param map2 The second map for which to examine the keys.
  188 + * @return Collection of keys present in both maps.
  189 + * @note The types of the keys in the maps must be identical.
  190 + */
  191 +template <typename TMap1, typename TMap2>
  192 +std::vector<typename TMap1::key_type> keyIntersection(
  193 + const TMap1& map1,
  194 + const TMap2& map2,
  195 + const std::function<bool(const typename TMap1::key_type& key1, const typename TMap1::key_type& key2)>& keyCompare = std::less<typename TMap1::key_type>())
  196 +{
  197 + static_assert(std::is_same<
  198 + typename TMap1::key_type,
  199 + typename TMap2::key_type>::value,
  200 + "Inconsistent key types.");
  201 +
  202 + typedef typename TMap1::key_type Key;
  203 + std::vector<Key> inBothMaps;
  204 + std::set_intersection(
  205 + KeyBegin(map1),
  206 + KeyEnd(map1),
  207 + KeyBegin(map2),
  208 + KeyEnd(map2),
  209 + std::inserter(inBothMaps, inBothMaps.begin()),
  210 + keyCompare);
  211 + return inBothMaps;
  212 +}
  213 +
  214 +/**
  215 + * @brief Determine the absolute path of the binary that belongs to a process.
  216 + * @param pid Process Id ifor which to determine the path to the binary. If pid equals -1 then the pid of the current process is used.
  217 + * @return Absolute path to the binary.
  218 + * @throw SystemException if call to readlink fails.
  219 + * @throw PathException if path is to long.
  220 + * @note Works only for processes owned by the user that is calling this function.
  221 + */
  222 +std::string getPathToBinary(int pid = -1);
  223 +
  224 +} // End namespace mqtt
  225 +} // End namespace components
  226 +} // End namespace osdev
  227 +
  228 +#endif // OSDEV_COMPONENTS_MQTT_UTILS_H
... ...
tests/CMakeLists.txt 0 → 100644
  1 +++ a/tests/CMakeLists.txt
... ...
tests/pub/CMakeLists.txt 0 → 100644
  1 +++ a/tests/pub/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.0)
  2 +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake)
  3 +
  4 +include(projectheader)
  5 +project_header(test_mqtt_pub)
  6 +
  7 +include_directories( SYSTEM
  8 + ${CMAKE_CURRENT_SOURCE_DIR}/../../src
  9 +)
  10 +
  11 +include(compiler)
  12 +set(SRC_LIST
  13 + ${CMAKE_CURRENT_SOURCE_DIR}/publisher.cpp
  14 + ${CMAKE_CURRENT_SOURCE_DIR}/publisher.h
  15 + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
  16 +)
  17 +
  18 +add_executable( ${PROJECT_NAME}
  19 + ${SRC_LIST}
  20 +)
  21 +
  22 +target_link_libraries(
  23 + ${PROJECT_NAME}
  24 + mqtt
  25 +)
  26 +
  27 +set_target_properties( ${PROJECT_NAME} PROPERTIES
  28 + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
  29 + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
  30 + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/archive
  31 +)
  32 +
  33 +include(installation)
  34 +install_application()
... ...
tests/pub/main.cpp 0 → 100644
  1 +++ a/tests/pub/main.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +
  20 +// std
  21 +#include <iostream>
  22 +#include <unistd.h>
  23 +#include <chrono>
  24 +
  25 +#include "publisher.h"
  26 +
  27 +enum TIME_RES
  28 +{
  29 + T_MICRO,
  30 + T_MILLI,
  31 + T_SECONDS
  32 +};
  33 +
  34 +std::uint64_t getEpochUSecs()
  35 +{
  36 + auto tsUSec =std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::system_clock::now());
  37 + return static_cast<std::uint64_t>(tsUSec.time_since_epoch().count());
  38 +}
  39 +
  40 +
  41 +void sleepcp( int number, TIME_RES resolution = T_MILLI ) // Cross-platform sleep function
  42 +{
  43 + int factor = 0; // Should not happen..
  44 +
  45 + switch( resolution )
  46 + {
  47 + case T_MICRO:
  48 + factor = 1;
  49 + break;
  50 +
  51 + case T_MILLI:
  52 + factor = 1000;
  53 + break;
  54 +
  55 + case T_SECONDS:
  56 + factor = 1000000;
  57 + break;
  58 + }
  59 +
  60 + usleep( number * factor );
  61 +}
  62 +
  63 +int main( int argc, char* argv[] )
  64 +{
  65 + // We're not using the command parameters, so we just want to keep the compiler happy.
  66 + (void)argc;
  67 + (void)argv;
  68 +
  69 + uint64_t messageNumber = 0;
  70 +
  71 + // Create the publisher, run it and publish a message every 0.001 sec.
  72 + std::cout << "Create the publisher object : ";
  73 + Publisher *pPublisher = new Publisher();
  74 + if( pPublisher != nullptr )
  75 + {
  76 + std::cout << "{OK}" << std::endl;
  77 + std::cout << "Connecting to the broker : ";
  78 + pPublisher->connect( "localhost", 1883, "", "" );
  79 +
  80 + // Assume we are connected now, start publishing.
  81 + while( 1 )
  82 + {
  83 + std::string payload = "<Timestamp value=\"" + std::to_string( getEpochUSecs() ) + "\" /><MessageNumber value=\"" + std::to_string( messageNumber ) + "\" />" ;
  84 + pPublisher->publish( std::string( "test/publisher/TestPublisher" ), payload );
  85 +
  86 + sleepcp( 1, T_SECONDS );
  87 + if( messageNumber > 2000000000 )
  88 + messageNumber = -1;
  89 +
  90 + messageNumber++;
  91 + }
  92 + }
  93 + else
  94 + return -1;
  95 +
  96 +}
... ...
tests/pub/publisher.cpp 0 → 100644
  1 +++ a/tests/pub/publisher.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +
  20 +// osdev::components::mqtt
  21 +#include "token.h"
  22 +
  23 +// mqtt_tests
  24 +#include "publisher.h"
  25 +
  26 +Publisher::Publisher()
  27 + : m_mqtt_client( "TestPublisher" )
  28 +{
  29 +
  30 +}
  31 +
  32 +void Publisher::connect( const std::string &hostname, int portnumber, const std::string &username, const std::string &password )
  33 +{
  34 + m_mqtt_client.connect( hostname, portnumber, osdev::components::mqtt::Credentials( username, password ) );
  35 + std::cout << "Client state : " << m_mqtt_client.state() << std::endl;
  36 +}
  37 +
  38 +void Publisher::publish( const std::string &message_topic, const std::string &message_payload )
  39 +{
  40 + osdev::components::mqtt::MqttMessage message( message_topic, true, false, message_payload );
  41 + std::cout << "[Publisher::publish] - Publising message : " << message_payload << " to topic : " << message_topic << std::endl;
  42 + osdev::components::mqtt::Token t_result = m_mqtt_client.publish( message, 0 );
  43 +}
... ...
tests/pub/publisher.h 0 → 100644
  1 +++ a/tests/pub/publisher.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#pragma once
  20 +
  21 +// std
  22 +#include <memory>
  23 +#include <string>
  24 +
  25 +// osdev::components::mqtt
  26 +#include "mqttclient.h"
  27 +#include "compat-c++14.h"
  28 +
  29 +class Publisher
  30 +{
  31 +public:
  32 + Publisher();
  33 +
  34 + virtual ~Publisher() {}
  35 +
  36 + void connect( const std::string &hostname, int portnumber = 1883, const std::string &username = std::string(), const std::string &password = std::string() );
  37 +
  38 + void publish( const std::string &message_topic, const std::string &message_payload );
  39 +
  40 +private:
  41 + osdev::components::mqtt::MqttClient m_mqtt_client;
  42 +};
... ...
tests/sub/CMakeLists.txt 0 → 100644
  1 +++ a/tests/sub/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.0)
  2 +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake)
  3 +
  4 +include(projectheader)
  5 +project_header(test_mqtt_sub)
  6 +
  7 +include_directories( SYSTEM
  8 + ${CMAKE_CURRENT_SOURCE_DIR}/../../src
  9 +)
  10 +
  11 +include(compiler)
  12 +set(SRC_LIST
  13 + ${CMAKE_CURRENT_SOURCE_DIR}/subscriber.cpp
  14 + ${CMAKE_CURRENT_SOURCE_DIR}/subscriber.h
  15 + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
  16 +)
  17 +
  18 +add_executable( ${PROJECT_NAME}
  19 + ${SRC_LIST}
  20 +)
  21 +
  22 +target_link_libraries(
  23 + ${PROJECT_NAME}
  24 + mqtt
  25 +)
  26 +
  27 +set_target_properties( ${PROJECT_NAME} PROPERTIES
  28 + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
  29 + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
  30 + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/archive
  31 +)
  32 +
  33 +include(installation)
  34 +install_application()
... ...
tests/sub/main.cpp 0 → 100644
  1 +++ a/tests/sub/main.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +
  20 +// std
  21 +#include <iostream>
  22 +#include <unistd.h>
  23 +#include <chrono>
  24 +
  25 +#include "subscriber.h"
  26 +
  27 +enum TIME_RES
  28 +{
  29 + T_MICRO,
  30 + T_MILLI,
  31 + T_SECONDS
  32 +};
  33 +
  34 +std::uint64_t getEpochUSecs()
  35 +{
  36 + auto tsUSec =std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::system_clock::now());
  37 + return static_cast<std::uint64_t>(tsUSec.time_since_epoch().count());
  38 +}
  39 +
  40 +
  41 +void sleepcp( int number, TIME_RES resolution = T_MILLI ) // Cross-platform sleep function
  42 +{
  43 + int factor = 0; // Should not happen..
  44 +
  45 + switch( resolution )
  46 + {
  47 + case T_MICRO:
  48 + factor = 1;
  49 + break;
  50 +
  51 + case T_MILLI:
  52 + factor = 1000;
  53 + break;
  54 +
  55 + case T_SECONDS:
  56 + factor = 1000000;
  57 + break;
  58 + }
  59 +
  60 + usleep( number * factor );
  61 +}
  62 +
  63 +int main( int argc, char* argv[] )
  64 +{
  65 + // Satisfy the compiler
  66 + (void)argc;
  67 + (void)argv;
  68 +
  69 + std::cout << "Creating the subscriber : ";
  70 + // Create the subscriber
  71 + Subscriber *pSubscriber = new Subscriber();
  72 + if( pSubscriber != nullptr )
  73 + {
  74 + std::cout << "[OK]" << std::endl;
  75 + std::cout << "Connecting to the test-broker : " << std::endl;
  76 + pSubscriber->connect( "localhost", 1883, "", "" );
  77 + std::cout << "Subscribing to the test-topic....." << std::endl;
  78 + pSubscriber->subscribe( "test/publisher/TestPublisher" );
  79 +
  80 + // Start a loop to give the subscriber the possibility to do its work.
  81 + while( 1 )
  82 + {
  83 + sleepcp( 1, T_SECONDS ); // Sleep 1 Sec to give the scheduler the change to interfene.
  84 + }
  85 + }
  86 + else
  87 + {
  88 + std::cout << "[FAILED]" << std::endl;
  89 + return -1;
  90 + }
  91 + return 0;
  92 +}
... ...
tests/sub/subscriber.cpp 0 → 100644
  1 +++ a/tests/sub/subscriber.cpp
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#include "subscriber.h"
  20 +#include "mqttmessage.h"
  21 +#include "credentials.h"
  22 +
  23 +Subscriber::Subscriber()
  24 + : m_mqtt_client( "TestSubscriber" )
  25 +{
  26 +
  27 +}
  28 +
  29 +void Subscriber::connect( const std::string &hostname, int portnumber, const std::string &username, const std::string &password )
  30 +{
  31 + m_mqtt_client.connect( hostname, portnumber, osdev::components::mqtt::Credentials( username, password ) );
  32 + std::cout << "Client state : " << m_mqtt_client.state() << std::endl;
  33 +}
  34 +
  35 +void Subscriber::subscribe( const std::string &message_topic )
  36 +{
  37 + m_mqtt_client.subscribe( message_topic, 1, [this](const osdev::components::mqtt::MqttMessage &message)
  38 + {
  39 + this->receive_data(message.topic(), message.payload() );
  40 + });
  41 +}
  42 +
  43 +void Subscriber::receive_data( const std::string &message_topic, const std::string &message_payload )
  44 +{
  45 + std::cout << "[Subscriber::receive_data] - Received message : " << message_payload << " from topic : " << message_topic << std::endl;
  46 +}
... ...
tests/sub/subscriber.h 0 → 100644
  1 +++ a/tests/sub/subscriber.h
  1 +/* Copyright (C) 2019
  2 + *
  3 + * This file is part of the osdev components suite
  4 + *
  5 + * This program is free software; you can redistribute it and/or modify it
  6 + * under the terms of the GNU General Public License as published by the
  7 + * Free Software Foundation; either version 2, or (at your option) any
  8 + * later version.
  9 + *
  10 + * This program is distributed in the hope that it will be useful,
  11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 + * GNU General Public License for more details.
  14 + *
  15 + * You should have received a copy of the GNU General Public License
  16 + * along with this program; if not, write to the Free Software
  17 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  18 + */
  19 +#pragma once
  20 +
  21 +// std
  22 +#include <memory>
  23 +#include <string>
  24 +
  25 +// osdev::components::mqtt
  26 +#include "mqttclient.h"
  27 +#include "compat-c++14.h"
  28 +
  29 +class Subscriber
  30 +{
  31 +public:
  32 + Subscriber();
  33 +
  34 + virtual ~Subscriber() {}
  35 +
  36 + void connect( const std::string &hostname, int portnumber, const std::string &username, const std::string &password );
  37 +
  38 + void subscribe( const std::string &message_topic );
  39 +
  40 +private:
  41 + void receive_data( const std::string &message_topic, const std::string &message_payload );
  42 +
  43 +private:
  44 + osdev::components::mqtt::MqttClient m_mqtt_client;
  45 +};
... ...