| @@ -0,0 +1,103 @@ | |||
| /* | |||
| * This file is part of Hylia. | |||
| * | |||
| * Hylia is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * Hylia is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with Hylia. If not, see <http://www.gnu.org/licenses/>. | |||
| */ | |||
| #include "hylia.h" | |||
| #include "AudioEngine.hpp" | |||
| #include "ableton/link/HostTimeFilter.hpp" | |||
| #include <chrono> | |||
| class HyliaTransport { | |||
| public: | |||
| HyliaTransport(double bpm, double bufferSize, double sampleRate) | |||
| : link(bpm), | |||
| engine(link), | |||
| outputLatency(0), | |||
| sampleTime(0) | |||
| { | |||
| outputLatency = std::chrono::microseconds(llround(1.0e6 * bufferSize / sampleRate)); | |||
| } | |||
| void setEnabled(bool enabled, double bpm) | |||
| { | |||
| link.enable(enabled); | |||
| if (enabled) | |||
| { | |||
| sampleTime = 0; | |||
| engine.setTempo(bpm); | |||
| } | |||
| } | |||
| void setTempo(double tempo) | |||
| { | |||
| engine.setTempo(tempo); | |||
| } | |||
| void process(uint32_t frames, LinkTimeInfo* info) | |||
| { | |||
| const auto hostTime = hostTimeFilter.sampleTimeToHostTime(sampleTime); | |||
| const auto bufferBeginAtOutput = hostTime + outputLatency; | |||
| engine.timelineCallback(bufferBeginAtOutput, info); | |||
| sampleTime += frames; | |||
| } | |||
| private: | |||
| ableton::Link link; | |||
| ableton::linkaudio::AudioEngine engine; | |||
| ableton::link::HostTimeFilter<ableton::platforms::stl::Clock> hostTimeFilter; | |||
| std::chrono::microseconds outputLatency; | |||
| uint32_t sampleTime; | |||
| }; | |||
| hylia_t* hylia_create(double bpm, uint32_t buffer_size, uint32_t sample_rate) | |||
| { | |||
| HyliaTransport* t; | |||
| try { | |||
| t = new HyliaTransport(bpm, buffer_size, sample_rate); | |||
| } catch (...) { | |||
| return nullptr; | |||
| } | |||
| return (hylia_t*)t; | |||
| } | |||
| void hylia_enable(hylia_t* link, bool on, double bpm) | |||
| { | |||
| ((HyliaTransport*)link)->setEnabled(on, bpm); | |||
| } | |||
| void hylia_set_tempo(hylia_t* link, double bpm) | |||
| { | |||
| ((HyliaTransport*)link)->setTempo(bpm); | |||
| } | |||
| void hylia_process(hylia_t* link, uint32_t frames, hylia_time_info_t* info) | |||
| { | |||
| ((HyliaTransport*)link)->process(frames, (LinkTimeInfo*)info); | |||
| } | |||
| void hylia_cleanup(hylia_t* link) | |||
| { | |||
| delete (HyliaTransport*)link; | |||
| } | |||
| #include "AudioEngine.cpp" | |||
| @@ -0,0 +1,45 @@ | |||
| /* | |||
| * This file is part of Hylia. | |||
| * | |||
| * Hylia is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * Hylia is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with Hylia. If not, see <http://www.gnu.org/licenses/>. | |||
| */ | |||
| #ifndef MOD_LINK_H_INCLUDED | |||
| #define MOD_LINK_H_INCLUDED | |||
| #include <stdint.h> | |||
| #ifdef __cplusplus | |||
| extern "C" { | |||
| #else | |||
| #include <stdbool.h> | |||
| #endif | |||
| typedef struct _hylia_t hylia_t; | |||
| typedef struct _hylia_time_info_t { | |||
| double bpm, beats, phase; | |||
| } hylia_time_info_t; | |||
| hylia_t* hylia_create(double bpm, uint32_t buffer_size, uint32_t sample_rate); | |||
| void hylia_enable(hylia_t* link, bool on, double bpm); | |||
| void hylia_process(hylia_t* link, uint32_t frames, hylia_time_info_t* info); | |||
| void hylia_set_tempo(hylia_t* link, double tempo); | |||
| void hylia_cleanup(hylia_t* link); | |||
| #ifdef __cplusplus | |||
| } | |||
| #endif | |||
| #endif // MOD_LINK_H_INCLUDED | |||
| @@ -0,0 +1,107 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #include "AudioEngine.hpp" | |||
| // Make sure to define this before <cmath> is included for Windows | |||
| #define _USE_MATH_DEFINES | |||
| #include <cmath> | |||
| namespace ableton | |||
| { | |||
| namespace linkaudio | |||
| { | |||
| AudioEngine::AudioEngine(Link& link) | |||
| : mLink(link) | |||
| , mSharedEngineData({0., false, 4.}) | |||
| { | |||
| } | |||
| double AudioEngine::beatTime() const | |||
| { | |||
| const auto timeline = mLink.captureAppTimeline(); | |||
| return timeline.beatAtTime(mLink.clock().micros(), mSharedEngineData.quantum); | |||
| } | |||
| void AudioEngine::setTempo(double tempo) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mEngineDataGuard); | |||
| mSharedEngineData.requestedTempo = tempo; | |||
| } | |||
| double AudioEngine::quantum() const | |||
| { | |||
| return mSharedEngineData.quantum; | |||
| } | |||
| void AudioEngine::setQuantum(double quantum) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mEngineDataGuard); | |||
| mSharedEngineData.quantum = quantum; | |||
| mSharedEngineData.resetBeatTime = true; | |||
| } | |||
| AudioEngine::EngineData AudioEngine::pullEngineData() | |||
| { | |||
| auto engineData = EngineData{}; | |||
| if (mEngineDataGuard.try_lock()) | |||
| { | |||
| engineData.requestedTempo = mSharedEngineData.requestedTempo; | |||
| mSharedEngineData.requestedTempo = 0; | |||
| engineData.resetBeatTime = mSharedEngineData.resetBeatTime; | |||
| engineData.quantum = mSharedEngineData.quantum; | |||
| mSharedEngineData.resetBeatTime = false; | |||
| mEngineDataGuard.unlock(); | |||
| } | |||
| return engineData; | |||
| } | |||
| void AudioEngine::timelineCallback(const std::chrono::microseconds hostTime, LinkTimeInfo* const info) | |||
| { | |||
| const auto engineData = pullEngineData(); | |||
| auto timeline = mLink.captureAudioTimeline(); | |||
| if (engineData.resetBeatTime) | |||
| { | |||
| // Reset the timeline so that beat 0 lands at the beginning of | |||
| // this buffer and clear the flag. | |||
| timeline.requestBeatAtTime(0, hostTime, engineData.quantum); | |||
| } | |||
| if (engineData.requestedTempo > 0) | |||
| { | |||
| // Set the newly requested tempo from the beginning of this buffer | |||
| timeline.setTempo(engineData.requestedTempo, hostTime); | |||
| } | |||
| // Timeline modifications are complete, commit the results | |||
| mLink.commitAudioTimeline(timeline); | |||
| // Save timeline info | |||
| info->bpm = timeline.tempo(); | |||
| info->beats = timeline.beatAtTime(hostTime, engineData.quantum); | |||
| info->phase = timeline.phaseAtTime(hostTime, engineData.quantum); | |||
| } | |||
| } // namespace linkaudio | |||
| } // namespace ableton | |||
| @@ -0,0 +1,66 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| // Make sure to define this before <cmath> is included for Windows | |||
| #define _USE_MATH_DEFINES | |||
| #include "ableton/Link.hpp" | |||
| #include <mutex> | |||
| struct LinkTimeInfo { | |||
| double bpm, beats, phase; | |||
| }; | |||
| namespace ableton | |||
| { | |||
| namespace linkaudio | |||
| { | |||
| class AudioEngine | |||
| { | |||
| public: | |||
| AudioEngine(Link& link); | |||
| double beatTime() const; | |||
| void setTempo(double tempo); | |||
| double quantum() const; | |||
| void setQuantum(double quantum); | |||
| void timelineCallback(const std::chrono::microseconds hostTime, LinkTimeInfo* const info); | |||
| private: | |||
| struct EngineData | |||
| { | |||
| double requestedTempo; | |||
| bool resetBeatTime; | |||
| double quantum; | |||
| }; | |||
| EngineData pullEngineData(); | |||
| Link& mLink; | |||
| EngineData mSharedEngineData; | |||
| std::mutex mEngineDataGuard; | |||
| friend class AudioPlatform; | |||
| }; | |||
| } // namespace linkaudio | |||
| } // namespace ableton | |||
| @@ -0,0 +1,306 @@ | |||
| /*! @file Link.hpp | |||
| * @copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * @brief Library for cross-device shared tempo and quantized beat grid | |||
| * | |||
| * @license: | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/Config.hpp> | |||
| #include <chrono> | |||
| #include <mutex> | |||
| namespace ableton | |||
| { | |||
| /*! @class Link | |||
| * @brief Class that represents a participant in a Link session. | |||
| * | |||
| * @discussion Each Link instance has its own beat timeline that | |||
| * starts running from beat 0 at the initial tempo when | |||
| * constructed. A Link instance is initially disabled after | |||
| * construction, which means that it will not communicate on the | |||
| * network. Once enabled, a Link instance initiates network | |||
| * communication in an effort to discover other peers. When peers are | |||
| * discovered, they immediately become part of a shared Link session. | |||
| * | |||
| * Each method of the Link type documents its thread-safety and | |||
| * realtime-safety properties. When a method is marked thread-safe, | |||
| * it means it is safe to call from multiple threads | |||
| * concurrently. When a method is marked realtime-safe, it means that | |||
| * it does not block and is appropriate for use in the thread that | |||
| * performs audio IO. | |||
| * | |||
| * Link provides one Timeline capture/commit method pair for use in the | |||
| * audio thread and one for all other application contexts. In | |||
| * general, modifying the Link timeline should be done in the audio | |||
| * thread for the most accurate timing results. The ability to modify | |||
| * the Link timeline from application threads should only be used in | |||
| * cases where an application's audio thread is not actively running | |||
| * or if it doesn't generate audio at all. Modifying the Link | |||
| * timeline from both the audio thread and an application thread | |||
| * concurrently is not advised and will potentially lead to | |||
| * unexpected behavior. | |||
| */ | |||
| class Link | |||
| { | |||
| public: | |||
| using Clock = link::platform::Clock; | |||
| class Timeline; | |||
| /*! @brief Construct with an initial tempo. */ | |||
| Link(double bpm); | |||
| /*! @brief Link instances cannot be copied or moved */ | |||
| Link(const Link&) = delete; | |||
| Link& operator=(const Link&) = delete; | |||
| Link(Link&&) = delete; | |||
| Link& operator=(Link&&) = delete; | |||
| /*! @brief Is Link currently enabled? | |||
| * Thread-safe: yes | |||
| * Realtime-safe: yes | |||
| */ | |||
| bool isEnabled() const; | |||
| /*! @brief Enable/disable Link. | |||
| * Thread-safe: yes | |||
| * Realtime-safe: no | |||
| */ | |||
| void enable(bool bEnable); | |||
| /*! @brief How many peers are currently connected in a Link session? | |||
| * Thread-safe: yes | |||
| * Realtime-safe: yes | |||
| */ | |||
| std::size_t numPeers() const; | |||
| /*! @brief Register a callback to be notified when the number of | |||
| * peers in the Link session changes. | |||
| * Thread-safe: yes | |||
| * Realtime-safe: no | |||
| * | |||
| * @discussion The callback is invoked on a Link-managed thread. | |||
| * | |||
| * @param callback The callback signature is: | |||
| * void (std::size_t numPeers) | |||
| */ | |||
| template <typename Callback> | |||
| void setNumPeersCallback(Callback callback); | |||
| /*! @brief Register a callback to be notified when the session | |||
| * tempo changes. | |||
| * Thread-safe: yes | |||
| * Realtime-safe: no | |||
| * | |||
| * @discussion The callback is invoked on a Link-managed thread. | |||
| * | |||
| * @param callback The callback signature is: void (double bpm) | |||
| */ | |||
| template <typename Callback> | |||
| void setTempoCallback(Callback callback); | |||
| /*! @brief The clock used by Link. | |||
| * Thread-safe: yes | |||
| * Realtime-safe: yes | |||
| * | |||
| * @discussion The Clock type is a platform-dependent | |||
| * representation of the system clock. It exposes a ticks() method | |||
| * that returns the current ticks of the system clock as well as | |||
| * micros(), which is a normalized representation of the current system | |||
| * time in std::chrono::microseconds. It also provides conversion | |||
| * functions ticksToMicros() and microsToTicks() to faciliate | |||
| * converting between these units. | |||
| */ | |||
| Clock clock() const; | |||
| /*! @brief Capture the current Link timeline from the audio thread. | |||
| * Thread-safe: no | |||
| * Realtime-safe: yes | |||
| * | |||
| * @discussion This method should ONLY be called in the audio thread | |||
| * and must not be accessed from any other threads. The returned | |||
| * Timeline stores a snapshot of the current Link state, so it | |||
| * should be captured and used in a local scope. Storing the | |||
| * Timeline for later use in a different context is not advised | |||
| * because it will provide an outdated view on the Link state. | |||
| */ | |||
| Timeline captureAudioTimeline() const; | |||
| /*! @brief Commit the given timeline to the Link session from the | |||
| * audio thread. | |||
| * Thread-safe: no | |||
| * Realtime-safe: yes | |||
| * | |||
| * @discussion This method should ONLY be called in the audio | |||
| * thread. The given timeline will replace the current Link | |||
| * timeline. Modifications to the session based on the new timeline | |||
| * will be communicated to other peers in the session. | |||
| */ | |||
| void commitAudioTimeline(Timeline timeline); | |||
| /*! @brief Capture the current Link timeline from an application | |||
| * thread. | |||
| * Thread-safe: yes | |||
| * Realtime-safe: no | |||
| * | |||
| * @discussion Provides a mechanism for capturing the Link timeline | |||
| * from an application thread (other than the audio thread). The | |||
| * returned Timeline stores a snapshot of the current Link state, | |||
| * so it should be captured and used in a local scope. Storing the | |||
| * Timeline for later use in a different context is not advised | |||
| * because it will provide an outdated view on the Link state. | |||
| */ | |||
| Timeline captureAppTimeline() const; | |||
| /*! @brief Commit the given timeline to the Link session from an | |||
| * application thread. | |||
| * Thread-safe: yes | |||
| * Realtime-safe: no | |||
| * | |||
| * @discussion The given timeline will replace the current Link | |||
| * timeline. Modifications to the session based on the new timeline | |||
| * will be communicated to other peers in the session. | |||
| */ | |||
| void commitAppTimeline(Timeline timeline); | |||
| /*! @class Timeline | |||
| * @brief Representation of a mapping between time and beats for | |||
| * varying quanta. | |||
| * | |||
| * @discussion A Timeline object is intended for use in a local | |||
| * scope within a single thread - none of its methods are | |||
| * thread-safe. All of its methods are non-blocking, so it is safe | |||
| * to use from a realtime thread. | |||
| */ | |||
| class Timeline | |||
| { | |||
| public: | |||
| Timeline(const link::Timeline timeline, const bool bRespectQuantum); | |||
| /*! @brief: The tempo of the timeline, in bpm */ | |||
| double tempo() const; | |||
| /*! @brief: Set the timeline tempo to the given bpm value, taking | |||
| * effect at the given time. | |||
| */ | |||
| void setTempo(double bpm, std::chrono::microseconds atTime); | |||
| /*! @brief: Get the beat value corresponding to the given time | |||
| * for the given quantum. | |||
| * | |||
| * @discussion: The magnitude of the resulting beat value is | |||
| * unique to this Link instance, but its phase with respect to | |||
| * the provided quantum is shared among all session | |||
| * peers. For non-negative beat values, the following | |||
| * property holds: fmod(beatAtTime(t, q), q) == phaseAtTime(t, q) | |||
| */ | |||
| double beatAtTime(std::chrono::microseconds time, double quantum) const; | |||
| /*! @brief: Get the session phase at the given time for the given | |||
| * quantum. | |||
| * | |||
| * @discussion: The result is in the interval [0, quantum). The | |||
| * result is equivalent to fmod(beatAtTime(t, q), q) for | |||
| * non-negative beat values. This method is convenient if the | |||
| * client is only interested in the phase and not the beat | |||
| * magnitude. Also, unlike fmod, it handles negative beat values | |||
| * correctly. | |||
| */ | |||
| double phaseAtTime(std::chrono::microseconds time, double quantum) const; | |||
| /*! @brief: Get the time at which the given beat occurs for the | |||
| * given quantum. | |||
| * | |||
| * @discussion: The inverse of beatAtTime, assuming a constant | |||
| * tempo. beatAtTime(timeAtBeat(b, q), q) === b. | |||
| */ | |||
| std::chrono::microseconds timeAtBeat(double beat, double quantum) const; | |||
| /*! @brief: Attempt to map the given beat to the given time in the | |||
| * context of the given quantum. | |||
| * | |||
| * @discussion: This method behaves differently depending on the | |||
| * state of the session. If no other peers are connected, | |||
| * then this instance is in a session by itself and is free to | |||
| * re-map the beat/time relationship whenever it pleases. In this | |||
| * case, beatAtTime(time, quantum) == beat after this method has | |||
| * been called. | |||
| * | |||
| * If there are other peers in the session, this instance | |||
| * should not abruptly re-map the beat/time relationship in the | |||
| * session because that would lead to beat discontinuities among | |||
| * the other peers. In this case, the given beat will be mapped | |||
| * to the next time value greater than the given time with the | |||
| * same phase as the given beat. | |||
| * | |||
| * This method is specifically designed to enable the concept of | |||
| * "quantized launch" in client applications. If there are no other | |||
| * peers in the session, then an event (such as starting | |||
| * transport) happens immediately when it is requested. If there | |||
| * are other peers, however, we wait until the next time at which | |||
| * the session phase matches the phase of the event, thereby | |||
| * executing the event in-phase with the other peers in the | |||
| * session. The client only needs to invoke this method to | |||
| * achieve this behavior and should not need to explicitly check | |||
| * the number of peers. | |||
| */ | |||
| void requestBeatAtTime(double beat, std::chrono::microseconds time, double quantum); | |||
| /*! @brief: Rudely re-map the beat/time relationship for all peers | |||
| * in a session. | |||
| * | |||
| * @discussion: DANGER: This method should only be needed in | |||
| * certain special circumstances. Most applications should not | |||
| * use it. It is very similar to requestBeatAtTime except that it | |||
| * does not fall back to the quantizing behavior when it is in a | |||
| * session with other peers. Calling this method will | |||
| * unconditionally map the given beat to the given time and | |||
| * broadcast the result to the session. This is very anti-social | |||
| * behavior and should be avoided. | |||
| * | |||
| * One of the few legitimate uses of this method is to | |||
| * synchronize a Link session with an external clock source. By | |||
| * periodically forcing the beat/time mapping according to an | |||
| * external clock source, a peer can effectively bridge that | |||
| * clock into a Link session. Much care must be taken at the | |||
| * application layer when implementing such a feature so that | |||
| * users do not accidentally disrupt Link sessions that they may | |||
| * join. | |||
| */ | |||
| void forceBeatAtTime(double beat, std::chrono::microseconds time, double quantum); | |||
| private: | |||
| friend Link; | |||
| link::Timeline mOriginalTimeline; | |||
| bool mbRespectQuantum; | |||
| link::Timeline mTimeline; | |||
| }; | |||
| private: | |||
| std::mutex mCallbackMutex; | |||
| link::PeerCountCallback mPeerCountCallback; | |||
| link::TempoCallback mTempoCallback; | |||
| Clock mClock; | |||
| link::platform::Controller mController; | |||
| }; | |||
| } // ableton | |||
| #include <ableton/Link.ipp> | |||
| @@ -0,0 +1,173 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/Phase.hpp> | |||
| namespace ableton | |||
| { | |||
| inline Link::Link(const double bpm) | |||
| : mPeerCountCallback([](std::size_t) {}) | |||
| , mTempoCallback([](link::Tempo) {}) | |||
| , mClock{} | |||
| , mController(link::Tempo(bpm), | |||
| [this](const std::size_t peers) { | |||
| std::lock_guard<std::mutex> lock(mCallbackMutex); | |||
| mPeerCountCallback(peers); | |||
| }, | |||
| [this](const link::Tempo tempo) { | |||
| std::lock_guard<std::mutex> lock(mCallbackMutex); | |||
| mTempoCallback(tempo); | |||
| }, | |||
| mClock, | |||
| util::injectVal(link::platform::IoContext{})) | |||
| { | |||
| } | |||
| inline bool Link::isEnabled() const | |||
| { | |||
| return mController.isEnabled(); | |||
| } | |||
| inline void Link::enable(const bool bEnable) | |||
| { | |||
| mController.enable(bEnable); | |||
| } | |||
| inline std::size_t Link::numPeers() const | |||
| { | |||
| return mController.numPeers(); | |||
| } | |||
| template <typename Callback> | |||
| void Link::setNumPeersCallback(Callback callback) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mCallbackMutex); | |||
| mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); }; | |||
| } | |||
| template <typename Callback> | |||
| void Link::setTempoCallback(Callback callback) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mCallbackMutex); | |||
| mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); }; | |||
| } | |||
| inline Link::Clock Link::clock() const | |||
| { | |||
| return mClock; | |||
| } | |||
| inline Link::Timeline Link::captureAudioTimeline() const | |||
| { | |||
| return Link::Timeline{mController.timelineRtSafe(), numPeers() > 0}; | |||
| } | |||
| inline void Link::commitAudioTimeline(const Link::Timeline timeline) | |||
| { | |||
| if (timeline.mOriginalTimeline != timeline.mTimeline) | |||
| { | |||
| mController.setTimelineRtSafe(timeline.mTimeline, mClock.micros()); | |||
| } | |||
| } | |||
| inline Link::Timeline Link::captureAppTimeline() const | |||
| { | |||
| return Link::Timeline{mController.timeline(), numPeers() > 0}; | |||
| } | |||
| inline void Link::commitAppTimeline(const Link::Timeline timeline) | |||
| { | |||
| if (timeline.mOriginalTimeline != timeline.mTimeline) | |||
| { | |||
| mController.setTimeline(timeline.mTimeline, mClock.micros()); | |||
| } | |||
| } | |||
| // Link::Timeline | |||
| inline Link::Timeline::Timeline(const link::Timeline timeline, const bool bRespectQuantum) | |||
| : mOriginalTimeline(timeline) | |||
| , mbRespectQuantum(bRespectQuantum) | |||
| , mTimeline(timeline) | |||
| { | |||
| } | |||
| inline double Link::Timeline::tempo() const | |||
| { | |||
| return mTimeline.tempo.bpm(); | |||
| } | |||
| inline void Link::Timeline::setTempo( | |||
| const double bpm, const std::chrono::microseconds atTime) | |||
| { | |||
| const auto desiredTl = | |||
| link::clampTempo(link::Timeline{link::Tempo(bpm), mTimeline.toBeats(atTime), atTime}); | |||
| mTimeline.tempo = desiredTl.tempo; | |||
| mTimeline.timeOrigin = desiredTl.fromBeats(mTimeline.beatOrigin); | |||
| } | |||
| inline double Link::Timeline::beatAtTime( | |||
| const std::chrono::microseconds time, const double quantum) const | |||
| { | |||
| return link::toPhaseEncodedBeats(mTimeline, time, link::Beats{quantum}).floating(); | |||
| } | |||
| inline double Link::Timeline::phaseAtTime( | |||
| const std::chrono::microseconds time, const double quantum) const | |||
| { | |||
| return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum}) | |||
| .floating(); | |||
| } | |||
| inline std::chrono::microseconds Link::Timeline::timeAtBeat( | |||
| const double beat, const double quantum) const | |||
| { | |||
| return link::fromPhaseEncodedBeats(mTimeline, link::Beats{beat}, link::Beats{quantum}); | |||
| } | |||
| inline void Link::Timeline::requestBeatAtTime( | |||
| const double beat, std::chrono::microseconds time, const double quantum) | |||
| { | |||
| if (mbRespectQuantum) | |||
| { | |||
| time = timeAtBeat(link::nextPhaseMatch(link::Beats{beatAtTime(time, quantum)}, | |||
| link::Beats{beat}, link::Beats{quantum}) | |||
| .floating(), | |||
| quantum); | |||
| } | |||
| forceBeatAtTime(beat, time, quantum); | |||
| } | |||
| inline void Link::Timeline::forceBeatAtTime( | |||
| const double beat, const std::chrono::microseconds time, const double quantum) | |||
| { | |||
| // There are two components to the beat adjustment: a phase shift | |||
| // and a beat magnitude adjustment. | |||
| const auto curBeatAtTime = link::Beats{beatAtTime(time, quantum)}; | |||
| const auto closestInPhase = | |||
| link::closestPhaseMatch(curBeatAtTime, link::Beats{beat}, link::Beats{quantum}); | |||
| mTimeline = shiftClientTimeline(mTimeline, closestInPhase - curBeatAtTime); | |||
| // Now adjust the magnitude | |||
| mTimeline.beatOrigin = mTimeline.beatOrigin + (link::Beats{beat} - closestInPhase); | |||
| } | |||
| } // ableton | |||
| @@ -0,0 +1,103 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/Injected.hpp> | |||
| #include <chrono> | |||
| #include <vector> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| // Callback takes a range of asio::ip:address which is | |||
| // guaranteed to be sorted and unique | |||
| template <typename Callback, typename IoContext> | |||
| class InterfaceScanner | |||
| { | |||
| public: | |||
| using Timer = typename util::Injected<IoContext>::type::Timer; | |||
| InterfaceScanner(const std::chrono::seconds period, | |||
| util::Injected<Callback> callback, | |||
| util::Injected<IoContext> io) | |||
| : mPeriod(period) | |||
| , mCallback(std::move(callback)) | |||
| , mIo(std::move(io)) | |||
| , mTimer(mIo->makeTimer()) | |||
| { | |||
| } | |||
| void enable(const bool bEnable) | |||
| { | |||
| if (bEnable) | |||
| { | |||
| scan(); | |||
| } | |||
| else | |||
| { | |||
| mTimer.cancel(); | |||
| } | |||
| } | |||
| void scan() | |||
| { | |||
| using namespace std; | |||
| debug(mIo->log()) << "Scanning network interfaces"; | |||
| // Rescan the hardware for available network interface addresses | |||
| vector<asio::ip::address> addrs = mIo->scanNetworkInterfaces(); | |||
| // Sort and unique them to guarantee consistent comparison | |||
| sort(begin(addrs), end(addrs)); | |||
| addrs.erase(unique(begin(addrs), end(addrs)), end(addrs)); | |||
| // Pass them to the callback | |||
| (*mCallback)(std::move(addrs)); | |||
| // setup the next scanning | |||
| mTimer.expires_from_now(mPeriod); | |||
| using ErrorCode = typename Timer::ErrorCode; | |||
| mTimer.async_wait([this](const ErrorCode e) { | |||
| if (!e) | |||
| { | |||
| scan(); | |||
| } | |||
| }); | |||
| } | |||
| private: | |||
| const std::chrono::seconds mPeriod; | |||
| util::Injected<Callback> mCallback; | |||
| util::Injected<IoContext> mIo; | |||
| Timer mTimer; | |||
| }; | |||
| // Factory function | |||
| template <typename Callback, typename IoContext> | |||
| InterfaceScanner<Callback, IoContext> makeInterfaceScanner( | |||
| const std::chrono::seconds period, | |||
| util::Injected<Callback> callback, | |||
| util::Injected<IoContext> io) | |||
| { | |||
| using namespace std; | |||
| return {period, move(callback), move(io)}; | |||
| } | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,123 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioService.hpp> | |||
| #include <ableton/util/Injected.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| inline asio::ip::udp::endpoint multicastEndpoint() | |||
| { | |||
| return {asio::ip::address::from_string("224.76.78.75"), 20808}; | |||
| } | |||
| // Type tags for dispatching between unicast and multicast packets | |||
| struct MulticastTag | |||
| { | |||
| }; | |||
| struct UnicastTag | |||
| { | |||
| }; | |||
| template <typename IoContext, std::size_t MaxPacketSize> | |||
| class IpV4Interface | |||
| { | |||
| public: | |||
| using Socket = typename util::Injected<IoContext>::type::template Socket<MaxPacketSize>; | |||
| IpV4Interface(util::Injected<IoContext> io, const asio::ip::address_v4& addr) | |||
| : mIo(std::move(io)) | |||
| , mMulticastReceiveSocket(mIo->template openMulticastSocket<MaxPacketSize>(addr)) | |||
| , mSendSocket(mIo->template openUnicastSocket<MaxPacketSize>(addr)) | |||
| { | |||
| } | |||
| IpV4Interface(const IpV4Interface&) = delete; | |||
| IpV4Interface& operator=(const IpV4Interface&) = delete; | |||
| IpV4Interface(IpV4Interface&& rhs) | |||
| : mIo(std::move(rhs.mIo)) | |||
| , mMulticastReceiveSocket(std::move(rhs.mMulticastReceiveSocket)) | |||
| , mSendSocket(std::move(rhs.mSendSocket)) | |||
| { | |||
| } | |||
| std::size_t send( | |||
| const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) | |||
| { | |||
| return mSendSocket.send(pData, numBytes, to); | |||
| } | |||
| template <typename Handler> | |||
| void receive(Handler handler, UnicastTag) | |||
| { | |||
| mSendSocket.receive(SocketReceiver<UnicastTag, Handler>{std::move(handler)}); | |||
| } | |||
| template <typename Handler> | |||
| void receive(Handler handler, MulticastTag) | |||
| { | |||
| mMulticastReceiveSocket.receive( | |||
| SocketReceiver<MulticastTag, Handler>(std::move(handler))); | |||
| } | |||
| asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return mSendSocket.endpoint(); | |||
| } | |||
| private: | |||
| template <typename Tag, typename Handler> | |||
| struct SocketReceiver | |||
| { | |||
| SocketReceiver(Handler handler) | |||
| : mHandler(std::move(handler)) | |||
| { | |||
| } | |||
| template <typename It> | |||
| void operator()( | |||
| const asio::ip::udp::endpoint& from, const It messageBegin, const It messageEnd) | |||
| { | |||
| mHandler(Tag{}, from, messageBegin, messageEnd); | |||
| } | |||
| Handler mHandler; | |||
| }; | |||
| util::Injected<IoContext> mIo; | |||
| Socket mMulticastReceiveSocket; | |||
| Socket mSendSocket; | |||
| }; | |||
| template <std::size_t MaxPacketSize, typename IoContext> | |||
| IpV4Interface<IoContext, MaxPacketSize> makeIpV4Interface( | |||
| util::Injected<IoContext> io, const asio::ip::address_v4& addr) | |||
| { | |||
| return {std::move(io), addr}; | |||
| } | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,50 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| // Message types used in the Ableton service discovery protocol. There | |||
| // are two logical messages: a state dump and a bye bye. | |||
| // | |||
| // A state dump provides all relevant information about the peer's | |||
| // current state as well as a Time To Live (TTL) value that indicates | |||
| // how many seconds this state should be considered valid. | |||
| // | |||
| // The bye bye indicates that the sender is leaving the session. | |||
| template <typename NodeState> | |||
| struct PeerState | |||
| { | |||
| NodeState peerState; | |||
| int ttl; | |||
| }; | |||
| template <typename NodeId> | |||
| struct ByeBye | |||
| { | |||
| NodeId peerId; | |||
| }; | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,405 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #if LINK_PLATFORM_MACOSX | |||
| #include <ableton/platforms/darwin/Darwin.hpp> | |||
| #elif LINK_PLATFORM_LINUX | |||
| #include <ableton/platforms/linux/Linux.hpp> | |||
| #endif | |||
| #include <chrono> | |||
| #include <cstdint> | |||
| #include <type_traits> | |||
| #include <utility> | |||
| #include <vector> | |||
| #if LINK_PLATFORM_WINDOWS | |||
| #include <WS2tcpip.h> | |||
| #include <WinSock2.h> | |||
| #include <Windows.h> | |||
| #endif | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| // Concept: NetworkByteStreamSerializable | |||
| // | |||
| // A type that can be encoded to a stream of bytes and decoded from a | |||
| // stream of bytes in network byte order. The following type is for | |||
| // documentation purposes only. | |||
| struct NetworkByteStreamSerializable | |||
| { | |||
| friend std::uint32_t sizeInByteStream(const NetworkByteStreamSerializable&); | |||
| // The byte stream pointed to by 'out' must have sufficient space to | |||
| // hold this object, as defined by sizeInByteStream. | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const NetworkByteStreamSerializable&, It out); | |||
| }; | |||
| // Deserialization aspect of the concept. Outside of the demonstration | |||
| // type above because clients must specify the type | |||
| // explicitly. Default implementation just defers to a class static | |||
| // method on T. For types that can't provide such a method, specialize | |||
| // this template. | |||
| template <typename T> | |||
| struct Deserialize | |||
| { | |||
| // Throws std::runtime_exception if parsing the type from the given | |||
| // byte range fails. Returns a pair of the correctly parsed value | |||
| // and an iterator to the next byte to parse. | |||
| template <typename It> | |||
| static std::pair<T, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| return T::fromNetworkByteStream(std::move(begin), std::move(end)); | |||
| } | |||
| }; | |||
| // Default size implementation. Works for primitive types. | |||
| template <typename T> | |||
| std::uint32_t sizeInByteStream(T) | |||
| { | |||
| return sizeof(T); | |||
| } | |||
| namespace detail | |||
| { | |||
| // utilities for implementing concept for primitive types | |||
| template <typename T, typename It> | |||
| It copyToByteStream(T t, It out) | |||
| { | |||
| using namespace std; | |||
| return copy_n( | |||
| reinterpret_cast<typename iterator_traits<It>::pointer>(&t), sizeof(t), out); | |||
| } | |||
| template <typename T, typename It> | |||
| std::pair<T, It> copyFromByteStream(It begin, const It end) | |||
| { | |||
| using namespace std; | |||
| using ItDiff = typename iterator_traits<It>::difference_type; | |||
| if (distance(begin, end) < static_cast<ItDiff>(sizeof(T))) | |||
| { | |||
| throw range_error("Parsing type from byte stream failed"); | |||
| } | |||
| else | |||
| { | |||
| T t; | |||
| const auto n = sizeof(t); | |||
| copy_n(begin, n, reinterpret_cast<uint8_t*>(&t)); | |||
| return make_pair(t, begin + n); | |||
| } | |||
| } | |||
| } // namespace detail | |||
| // Model the concept for unsigned integral types | |||
| // uint8_t | |||
| template <typename It> | |||
| It toNetworkByteStream(const uint8_t byte, It out) | |||
| { | |||
| return detail::copyToByteStream(byte, std::move(out)); | |||
| } | |||
| template <> | |||
| struct Deserialize<uint8_t> | |||
| { | |||
| template <typename It> | |||
| static std::pair<uint8_t, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| return detail::copyFromByteStream<uint8_t>(std::move(begin), std::move(end)); | |||
| } | |||
| }; | |||
| // uint16_t | |||
| template <typename It> | |||
| It toNetworkByteStream(uint16_t s, It out) | |||
| { | |||
| return detail::copyToByteStream(htons(s), std::move(out)); | |||
| } | |||
| template <> | |||
| struct Deserialize<uint16_t> | |||
| { | |||
| template <typename It> | |||
| static std::pair<uint16_t, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = detail::copyFromByteStream<uint16_t>(std::move(begin), std::move(end)); | |||
| result.first = ntohs(result.first); | |||
| return result; | |||
| } | |||
| }; | |||
| // uint32_t | |||
| template <typename It> | |||
| It toNetworkByteStream(uint32_t l, It out) | |||
| { | |||
| return detail::copyToByteStream(htonl(l), std::move(out)); | |||
| } | |||
| template <> | |||
| struct Deserialize<uint32_t> | |||
| { | |||
| template <typename It> | |||
| static std::pair<uint32_t, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = detail::copyFromByteStream<uint32_t>(std::move(begin), std::move(end)); | |||
| result.first = ntohl(result.first); | |||
| return result; | |||
| } | |||
| }; | |||
| // int32_t in terms of uint32_t | |||
| template <typename It> | |||
| It toNetworkByteStream(int32_t l, It out) | |||
| { | |||
| return toNetworkByteStream(reinterpret_cast<const uint32_t&>(l), std::move(out)); | |||
| } | |||
| template <> | |||
| struct Deserialize<int32_t> | |||
| { | |||
| template <typename It> | |||
| static std::pair<int32_t, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = | |||
| Deserialize<uint32_t>::fromNetworkByteStream(std::move(begin), std::move(end)); | |||
| return std::make_pair(reinterpret_cast<const int32_t&>(result.first), result.second); | |||
| } | |||
| }; | |||
| // uint64_t | |||
| template <typename It> | |||
| It toNetworkByteStream(uint64_t ll, It out) | |||
| { | |||
| return detail::copyToByteStream(htonll(ll), std::move(out)); | |||
| } | |||
| template <> | |||
| struct Deserialize<uint64_t> | |||
| { | |||
| template <typename It> | |||
| static std::pair<uint64_t, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = detail::copyFromByteStream<uint64_t>(std::move(begin), std::move(end)); | |||
| result.first = ntohll(result.first); | |||
| return result; | |||
| } | |||
| }; | |||
| // int64_t in terms of uint64_t | |||
| template <typename It> | |||
| It toNetworkByteStream(int64_t ll, It out) | |||
| { | |||
| return toNetworkByteStream(reinterpret_cast<const uint64_t&>(ll), std::move(out)); | |||
| } | |||
| template <> | |||
| struct Deserialize<int64_t> | |||
| { | |||
| template <typename It> | |||
| static std::pair<int64_t, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = | |||
| Deserialize<uint64_t>::fromNetworkByteStream(std::move(begin), std::move(end)); | |||
| return std::make_pair(reinterpret_cast<const int64_t&>(result.first), result.second); | |||
| } | |||
| }; | |||
| // overloads for std::chrono durations | |||
| template <typename Rep, typename Ratio> | |||
| std::uint32_t sizeInByteStream(const std::chrono::duration<Rep, Ratio> dur) | |||
| { | |||
| return sizeInByteStream(dur.count()); | |||
| } | |||
| template <typename Rep, typename Ratio, typename It> | |||
| It toNetworkByteStream(const std::chrono::duration<Rep, Ratio> dur, It out) | |||
| { | |||
| return toNetworkByteStream(dur.count(), std::move(out)); | |||
| } | |||
| template <typename Rep, typename Ratio> | |||
| struct Deserialize<std::chrono::duration<Rep, Ratio>> | |||
| { | |||
| template <typename It> | |||
| static std::pair<std::chrono::duration<Rep, Ratio>, It> fromNetworkByteStream( | |||
| It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto result = Deserialize<Rep>::fromNetworkByteStream(move(begin), move(end)); | |||
| return make_pair(std::chrono::duration<Rep, Ratio>{result.first}, result.second); | |||
| } | |||
| }; | |||
| namespace detail | |||
| { | |||
| // Generic serialize/deserialize utilities for containers | |||
| template <typename Container> | |||
| std::uint32_t containerSizeInByteStream(const Container& container) | |||
| { | |||
| std::uint32_t totalSize = 0; | |||
| for (const auto& val : container) | |||
| { | |||
| totalSize += sizeInByteStream(val); | |||
| } | |||
| return totalSize; | |||
| } | |||
| template <typename Container, typename It> | |||
| It containerToNetworkByteStream(const Container& container, It out) | |||
| { | |||
| for (const auto& val : container) | |||
| { | |||
| out = toNetworkByteStream(val, out); | |||
| } | |||
| return out; | |||
| } | |||
| template <typename T, typename BytesIt, typename InsertIt> | |||
| BytesIt deserializeContainer(BytesIt bytesBegin, | |||
| const BytesIt bytesEnd, | |||
| InsertIt contBegin, | |||
| const std::uint32_t maxElements) | |||
| { | |||
| using namespace std; | |||
| std::uint32_t numElements = 0; | |||
| while (bytesBegin < bytesEnd && numElements < maxElements) | |||
| { | |||
| T newVal; | |||
| tie(newVal, bytesBegin) = Deserialize<T>::fromNetworkByteStream(bytesBegin, bytesEnd); | |||
| *contBegin++ = newVal; | |||
| ++numElements; | |||
| } | |||
| return bytesBegin; | |||
| } | |||
| } // detail | |||
| // Need specific overloads for each container type, but use above | |||
| // utilities for common implementation | |||
| // array | |||
| template <typename T, std::size_t Size> | |||
| std::uint32_t sizeInByteStream(const std::array<T, Size>& arr) | |||
| { | |||
| return detail::containerSizeInByteStream(arr); | |||
| } | |||
| template <typename T, std::size_t Size, typename It> | |||
| It toNetworkByteStream(const std::array<T, Size>& arr, It out) | |||
| { | |||
| return detail::containerToNetworkByteStream(arr, std::move(out)); | |||
| } | |||
| template <typename T, std::size_t Size> | |||
| struct Deserialize<std::array<T, Size>> | |||
| { | |||
| template <typename It> | |||
| static std::pair<std::array<T, Size>, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| array<T, Size> result{}; | |||
| auto resultIt = | |||
| detail::deserializeContainer<T>(move(begin), move(end), move(result.begin()), Size); | |||
| return make_pair(move(result), move(resultIt)); | |||
| } | |||
| }; | |||
| // vector | |||
| template <typename T, typename Alloc> | |||
| std::uint32_t sizeInByteStream(const std::vector<T, Alloc>& vec) | |||
| { | |||
| return detail::containerSizeInByteStream(vec); | |||
| } | |||
| template <typename T, typename Alloc, typename It> | |||
| It toNetworkByteStream(const std::vector<T, Alloc>& vec, It out) | |||
| { | |||
| return detail::containerToNetworkByteStream(vec, std::move(out)); | |||
| } | |||
| template <typename T, typename Alloc> | |||
| struct Deserialize<std::vector<T, Alloc>> | |||
| { | |||
| template <typename It> | |||
| static std::pair<std::vector<T, Alloc>, It> fromNetworkByteStream( | |||
| It bytesBegin, It bytesEnd) | |||
| { | |||
| using namespace std; | |||
| vector<T, Alloc> result; | |||
| // Use the number of bytes remaining in the stream as the upper | |||
| // bound on the number of elements that could be deserialized | |||
| // since we don't have a better heuristic. | |||
| auto resultIt = detail::deserializeContainer<T>(move(bytesBegin), move(bytesEnd), | |||
| back_inserter(result), static_cast<uint32_t>(distance(bytesBegin, bytesEnd))); | |||
| return make_pair(move(result), move(resultIt)); | |||
| } | |||
| }; | |||
| // 3-tuple | |||
| template <typename X, typename Y, typename Z> | |||
| std::uint32_t sizeInByteStream(const std::tuple<X, Y, Z>& tup) | |||
| { | |||
| return sizeInByteStream(std::get<0>(tup)) + sizeInByteStream(std::get<1>(tup)) | |||
| + sizeInByteStream(std::get<2>(tup)); | |||
| } | |||
| template <typename X, typename Y, typename Z, typename It> | |||
| It toNetworkByteStream(const std::tuple<X, Y, Z>& tup, It out) | |||
| { | |||
| return toNetworkByteStream( | |||
| std::get<2>(tup), toNetworkByteStream(std::get<1>(tup), | |||
| toNetworkByteStream(std::get<0>(tup), std::move(out)))); | |||
| } | |||
| template <typename X, typename Y, typename Z> | |||
| struct Deserialize<std::tuple<X, Y, Z>> | |||
| { | |||
| template <typename It> | |||
| static std::pair<std::tuple<X, Y, Z>, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto xres = Deserialize<X>::fromNetworkByteStream(begin, end); | |||
| auto yres = Deserialize<Y>::fromNetworkByteStream(xres.second, end); | |||
| auto zres = Deserialize<Z>::fromNetworkByteStream(yres.second, end); | |||
| return make_pair( | |||
| std::tuple<X, Y, Z>{move(xres.first), move(yres.first), move(zres.first)}, | |||
| move(zres.second)); | |||
| } | |||
| }; | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,293 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/NetworkByteStreamSerializable.hpp> | |||
| #include <functional> | |||
| #include <unordered_map> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| struct PayloadEntryHeader | |||
| { | |||
| using Key = std::uint32_t; | |||
| using Size = std::uint32_t; | |||
| Key key; | |||
| Size size; | |||
| friend Size sizeInByteStream(const PayloadEntryHeader& header) | |||
| { | |||
| return sizeInByteStream(header.key) + sizeInByteStream(header.size); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const PayloadEntryHeader& header, It out) | |||
| { | |||
| return toNetworkByteStream( | |||
| header.size, toNetworkByteStream(header.key, std::move(out))); | |||
| } | |||
| template <typename It> | |||
| static std::pair<PayloadEntryHeader, It> fromNetworkByteStream(It begin, const It end) | |||
| { | |||
| using namespace std; | |||
| Key key; | |||
| Size size; | |||
| tie(key, begin) = Deserialize<Key>::fromNetworkByteStream(begin, end); | |||
| tie(size, begin) = Deserialize<Size>::fromNetworkByteStream(begin, end); | |||
| return make_pair(PayloadEntryHeader{move(key), move(size)}, move(begin)); | |||
| } | |||
| }; | |||
| template <typename EntryType> | |||
| struct PayloadEntry | |||
| { | |||
| PayloadEntry(EntryType entryVal) | |||
| : value(std::move(entryVal)) | |||
| { | |||
| header = {EntryType::key, sizeInByteStream(value)}; | |||
| } | |||
| PayloadEntryHeader header; | |||
| EntryType value; | |||
| friend std::uint32_t sizeInByteStream(const PayloadEntry& entry) | |||
| { | |||
| return sizeInByteStream(entry.header) + sizeInByteStream(entry.value); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const PayloadEntry& entry, It out) | |||
| { | |||
| return toNetworkByteStream( | |||
| entry.value, toNetworkByteStream(entry.header, std::move(out))); | |||
| } | |||
| }; | |||
| namespace detail | |||
| { | |||
| template <typename It> | |||
| using HandlerMap = | |||
| std::unordered_map<typename PayloadEntryHeader::Key, std::function<void(It, It)>>; | |||
| // Given an index of handlers and a byte range, parse the bytes as a | |||
| // sequence of payload entries and invoke the appropriate handler for | |||
| // each entry type. Entries that are encountered that do not have a | |||
| // corresponding handler in the map are ignored. Throws | |||
| // std::runtime_error if parsing fails for any entry. Note that if an | |||
| // exception is thrown, some of the handlers may have already been called. | |||
| template <typename It> | |||
| void parseByteStream(HandlerMap<It>& map, It bsBegin, const It bsEnd) | |||
| { | |||
| using namespace std; | |||
| while (bsBegin < bsEnd) | |||
| { | |||
| // Try to parse an entry header at this location in the byte stream | |||
| PayloadEntryHeader header; | |||
| It valueBegin; | |||
| tie(header, valueBegin) = | |||
| Deserialize<PayloadEntryHeader>::fromNetworkByteStream(bsBegin, bsEnd); | |||
| // Ensure that the reported size of the entry does not exceed the | |||
| // length of the byte stream | |||
| It valueEnd = valueBegin + header.size; | |||
| if (bsEnd < valueEnd) | |||
| { | |||
| throw range_error("Payload with incorrect size."); | |||
| } | |||
| // The next entry will start at the end of this one | |||
| bsBegin = valueEnd; | |||
| // Use the appropriate handler for this entry, if available | |||
| auto handlerIt = map.find(header.key); | |||
| if (handlerIt != end(map)) | |||
| { | |||
| handlerIt->second(move(valueBegin), move(valueEnd)); | |||
| } | |||
| } | |||
| } | |||
| } // namespace detail | |||
| // Payload encoding | |||
| template <typename... Entries> | |||
| struct Payload; | |||
| template <typename First, typename Rest> | |||
| struct Payload<First, Rest> | |||
| { | |||
| Payload(First first, Rest rest) | |||
| : mFirst(std::move(first)) | |||
| , mRest(std::move(rest)) | |||
| { | |||
| } | |||
| Payload(PayloadEntry<First> first, Rest rest) | |||
| : mFirst(std::move(first)) | |||
| , mRest(std::move(rest)) | |||
| { | |||
| } | |||
| template <typename RhsFirst, typename RhsRest> | |||
| using PayloadSum = | |||
| Payload<First, typename Rest::template PayloadSum<RhsFirst, RhsRest>>; | |||
| // Concatenate payloads together into a single payload | |||
| template <typename RhsFirst, typename RhsRest> | |||
| friend PayloadSum<RhsFirst, RhsRest> operator+( | |||
| Payload lhs, Payload<RhsFirst, RhsRest> rhs) | |||
| { | |||
| return {std::move(lhs.mFirst), std::move(lhs.mRest) + std::move(rhs)}; | |||
| } | |||
| friend std::size_t sizeInByteStream(const Payload& payload) | |||
| { | |||
| return sizeInByteStream(payload.mFirst) + sizeInByteStream(payload.mRest); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const Payload& payload, It streamIt) | |||
| { | |||
| return toNetworkByteStream( | |||
| payload.mRest, toNetworkByteStream(payload.mFirst, std::move(streamIt))); | |||
| } | |||
| PayloadEntry<First> mFirst; | |||
| Rest mRest; | |||
| }; | |||
| template <> | |||
| struct Payload<> | |||
| { | |||
| template <typename RhsFirst, typename RhsRest> | |||
| using PayloadSum = Payload<RhsFirst, RhsRest>; | |||
| template <typename RhsFirst, typename RhsRest> | |||
| friend PayloadSum<RhsFirst, RhsRest> operator+(Payload, Payload<RhsFirst, RhsRest> rhs) | |||
| { | |||
| return rhs; | |||
| } | |||
| friend std::size_t sizeInByteStream(const Payload&) | |||
| { | |||
| return 0; | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const Payload&, It streamIt) | |||
| { | |||
| return streamIt; | |||
| } | |||
| }; | |||
| template <typename... Entries> | |||
| struct PayloadBuilder; | |||
| // Payload factory function | |||
| template <typename... Entries> | |||
| auto makePayload(Entries... entries) | |||
| -> decltype(PayloadBuilder<Entries...>{}(std::move(entries)...)) | |||
| { | |||
| return PayloadBuilder<Entries...>{}(std::move(entries)...); | |||
| } | |||
| template <typename First, typename... Rest> | |||
| struct PayloadBuilder<First, Rest...> | |||
| { | |||
| auto operator()(First first, Rest... rest) | |||
| -> Payload<First, decltype(makePayload(std::move(rest)...))> | |||
| { | |||
| return {std::move(first), makePayload(std::move(rest)...)}; | |||
| } | |||
| }; | |||
| template <> | |||
| struct PayloadBuilder<> | |||
| { | |||
| Payload<> operator()() | |||
| { | |||
| return {}; | |||
| } | |||
| }; | |||
| // Parse payloads to values | |||
| template <typename... Entries> | |||
| struct ParsePayload; | |||
| template <typename First, typename... Rest> | |||
| struct ParsePayload<First, Rest...> | |||
| { | |||
| template <typename It, typename... Handlers> | |||
| static void parse(It begin, It end, Handlers... handlers) | |||
| { | |||
| detail::HandlerMap<It> map; | |||
| collectHandlers(map, std::move(handlers)...); | |||
| detail::parseByteStream(map, std::move(begin), std::move(end)); | |||
| } | |||
| template <typename It, typename FirstHandler, typename... RestHandlers> | |||
| static void collectHandlers( | |||
| detail::HandlerMap<It>& map, FirstHandler handler, RestHandlers... rest) | |||
| { | |||
| using namespace std; | |||
| map[First::key] = [handler](const It begin, const It end) { | |||
| const auto res = First::fromNetworkByteStream(begin, end); | |||
| if (res.second != end) | |||
| { | |||
| std::ostringstream stringStream; | |||
| stringStream << "Parsing payload entry " << First::key | |||
| << " did not consume the expected number of bytes. " | |||
| << " Expected: " << distance(begin, end) | |||
| << ", Actual: " << distance(begin, res.second); | |||
| throw range_error(stringStream.str()); | |||
| } | |||
| handler(res.first); | |||
| }; | |||
| ParsePayload<Rest...>::collectHandlers(map, std::move(rest)...); | |||
| } | |||
| }; | |||
| template <> | |||
| struct ParsePayload<> | |||
| { | |||
| template <typename It> | |||
| static void collectHandlers(detail::HandlerMap<It>&) | |||
| { | |||
| } | |||
| }; | |||
| template <typename... Entries, typename It, typename... Handlers> | |||
| void parsePayload(It begin, It end, Handlers... handlers) | |||
| { | |||
| using namespace std; | |||
| ParsePayload<Entries...>::parse(move(begin), move(end), move(handlers)...); | |||
| } | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,253 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/UdpMessenger.hpp> | |||
| #include <ableton/discovery/v1/Messages.hpp> | |||
| #include <ableton/platforms/asio/AsioService.hpp> | |||
| #include <ableton/util/SafeAsyncHandler.hpp> | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| template <typename Messenger, typename PeerObserver, typename IoContext> | |||
| class PeerGateway | |||
| { | |||
| public: | |||
| // The peer types are defined by the observer but must match with those | |||
| // used by the Messenger | |||
| using ObserverT = typename util::Injected<PeerObserver>::type; | |||
| using NodeState = typename ObserverT::GatewayObserverNodeState; | |||
| using NodeId = typename ObserverT::GatewayObserverNodeId; | |||
| using Timer = typename util::Injected<IoContext>::type::Timer; | |||
| using TimerError = typename Timer::ErrorCode; | |||
| PeerGateway(util::Injected<Messenger> messenger, | |||
| util::Injected<PeerObserver> observer, | |||
| util::Injected<IoContext> io) | |||
| : mpImpl(new Impl(std::move(messenger), std::move(observer), std::move(io))) | |||
| { | |||
| mpImpl->listen(); | |||
| } | |||
| PeerGateway(const PeerGateway&) = delete; | |||
| PeerGateway& operator=(const PeerGateway&) = delete; | |||
| PeerGateway(PeerGateway&& rhs) | |||
| : mpImpl(std::move(rhs.mpImpl)) | |||
| { | |||
| } | |||
| void updateState(NodeState state) | |||
| { | |||
| mpImpl->updateState(std::move(state)); | |||
| } | |||
| private: | |||
| using PeerTimeout = std::pair<std::chrono::system_clock::time_point, NodeId>; | |||
| using PeerTimeouts = std::vector<PeerTimeout>; | |||
| struct Impl : std::enable_shared_from_this<Impl> | |||
| { | |||
| Impl(util::Injected<Messenger> messenger, | |||
| util::Injected<PeerObserver> observer, | |||
| util::Injected<IoContext> io) | |||
| : mMessenger(std::move(messenger)) | |||
| , mObserver(std::move(observer)) | |||
| , mIo(std::move(io)) | |||
| , mPruneTimer(mIo->makeTimer()) | |||
| { | |||
| } | |||
| void updateState(NodeState state) | |||
| { | |||
| mMessenger->updateState(std::move(state)); | |||
| try | |||
| { | |||
| mMessenger->broadcastState(); | |||
| } | |||
| catch (const std::runtime_error& err) | |||
| { | |||
| info(mIo->log()) << "State broadcast failed on gateway: " << err.what(); | |||
| } | |||
| } | |||
| void listen() | |||
| { | |||
| mMessenger->receive(util::makeAsyncSafe(this->shared_from_this())); | |||
| } | |||
| // Operators for handling incoming messages | |||
| void operator()(const PeerState<NodeState>& msg) | |||
| { | |||
| onPeerState(msg.peerState, msg.ttl); | |||
| listen(); | |||
| } | |||
| void operator()(const ByeBye<NodeId>& msg) | |||
| { | |||
| onByeBye(msg.peerId); | |||
| listen(); | |||
| } | |||
| void onPeerState(const NodeState& nodeState, const int ttl) | |||
| { | |||
| using namespace std; | |||
| const auto peerId = nodeState.ident(); | |||
| const auto existing = findPeer(peerId); | |||
| if (existing != end(mPeerTimeouts)) | |||
| { | |||
| // If the peer is already present in our timeout list, remove it | |||
| // as it will be re-inserted below. | |||
| mPeerTimeouts.erase(existing); | |||
| } | |||
| auto newTo = make_pair(mPruneTimer.now() + std::chrono::seconds(ttl), peerId); | |||
| mPeerTimeouts.insert( | |||
| upper_bound(begin(mPeerTimeouts), end(mPeerTimeouts), newTo, TimeoutCompare{}), | |||
| move(newTo)); | |||
| sawPeer(*mObserver, nodeState); | |||
| scheduleNextPruning(); | |||
| } | |||
| void onByeBye(const NodeId& peerId) | |||
| { | |||
| const auto it = findPeer(peerId); | |||
| if (it != mPeerTimeouts.end()) | |||
| { | |||
| peerLeft(*mObserver, it->second); | |||
| mPeerTimeouts.erase(it); | |||
| } | |||
| } | |||
| void pruneExpiredPeers() | |||
| { | |||
| using namespace std; | |||
| const auto test = make_pair(mPruneTimer.now(), NodeId{}); | |||
| debug(mIo->log()) << "pruning peers @ " << test.first.time_since_epoch().count(); | |||
| const auto endExpired = | |||
| lower_bound(begin(mPeerTimeouts), end(mPeerTimeouts), test, TimeoutCompare{}); | |||
| for_each(begin(mPeerTimeouts), endExpired, [this](const PeerTimeout& pto) { | |||
| info(mIo->log()) << "pruning peer " << pto.second; | |||
| peerTimedOut(*mObserver, pto.second); | |||
| }); | |||
| mPeerTimeouts.erase(begin(mPeerTimeouts), endExpired); | |||
| scheduleNextPruning(); | |||
| } | |||
| void scheduleNextPruning() | |||
| { | |||
| // Find the next peer to expire and set the timer based on it | |||
| if (!mPeerTimeouts.empty()) | |||
| { | |||
| // Add a second of padding to the timer to avoid over-eager timeouts | |||
| const auto t = mPeerTimeouts.front().first + std::chrono::seconds(1); | |||
| debug(mIo->log()) << "scheduling next pruning for " | |||
| << t.time_since_epoch().count() << " because of peer " | |||
| << mPeerTimeouts.front().second; | |||
| mPruneTimer.expires_at(t); | |||
| mPruneTimer.async_wait([this](const TimerError e) { | |||
| if (!e) | |||
| { | |||
| pruneExpiredPeers(); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| struct TimeoutCompare | |||
| { | |||
| bool operator()(const PeerTimeout& lhs, const PeerTimeout& rhs) const | |||
| { | |||
| return lhs.first < rhs.first; | |||
| } | |||
| }; | |||
| typename PeerTimeouts::iterator findPeer(const NodeId& peerId) | |||
| { | |||
| return std::find_if(begin(mPeerTimeouts), end(mPeerTimeouts), | |||
| [&peerId](const PeerTimeout& pto) { return pto.second == peerId; }); | |||
| } | |||
| util::Injected<Messenger> mMessenger; | |||
| util::Injected<PeerObserver> mObserver; | |||
| util::Injected<IoContext> mIo; | |||
| Timer mPruneTimer; | |||
| PeerTimeouts mPeerTimeouts; // Invariant: sorted by time_point | |||
| }; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| template <typename Messenger, typename PeerObserver, typename IoContext> | |||
| PeerGateway<Messenger, PeerObserver, IoContext> makePeerGateway( | |||
| util::Injected<Messenger> messenger, | |||
| util::Injected<PeerObserver> observer, | |||
| util::Injected<IoContext> io) | |||
| { | |||
| return {std::move(messenger), std::move(observer), std::move(io)}; | |||
| } | |||
| // IpV4 gateway types | |||
| template <typename StateQuery, typename IoContext> | |||
| using IpV4Messenger = | |||
| UdpMessenger<IpV4Interface<typename util::Injected<IoContext>::type&, | |||
| v1::kMaxMessageSize>, | |||
| StateQuery, | |||
| IoContext>; | |||
| template <typename PeerObserver, typename StateQuery, typename IoContext> | |||
| using IpV4Gateway = | |||
| PeerGateway<IpV4Messenger<StateQuery, typename util::Injected<IoContext>::type&>, | |||
| PeerObserver, | |||
| IoContext>; | |||
| // Factory function to bind a PeerGateway to an IpV4Interface with the given address. | |||
| template <typename PeerObserver, typename NodeState, typename IoContext> | |||
| IpV4Gateway<PeerObserver, NodeState, IoContext> makeIpV4Gateway( | |||
| util::Injected<IoContext> io, | |||
| const asio::ip::address_v4& addr, | |||
| util::Injected<PeerObserver> observer, | |||
| NodeState state) | |||
| { | |||
| using namespace std; | |||
| using namespace util; | |||
| const uint8_t ttl = 5; | |||
| const uint8_t ttlRatio = 20; | |||
| auto iface = makeIpV4Interface<v1::kMaxMessageSize>(injectRef(*io), addr); | |||
| auto messenger = | |||
| makeUdpMessenger(injectVal(move(iface)), move(state), injectRef(*io), ttl, ttlRatio); | |||
| return {injectVal(move(messenger)), move(observer), move(io)}; | |||
| } | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,229 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/InterfaceScanner.hpp> | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <map> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| // GatewayFactory must have an operator()(NodeState, IoRef, asio::ip::address) | |||
| // that constructs a new PeerGateway on a given interface address. | |||
| template <typename NodeState, typename GatewayFactory, typename IoContext> | |||
| class PeerGateways | |||
| { | |||
| public: | |||
| using IoType = typename util::Injected<IoContext>::type; | |||
| using Gateway = typename std::result_of<GatewayFactory( | |||
| NodeState, util::Injected<IoType&>, asio::ip::address)>::type; | |||
| using GatewayMap = std::map<asio::ip::address, Gateway>; | |||
| PeerGateways(const std::chrono::seconds rescanPeriod, | |||
| NodeState state, | |||
| GatewayFactory factory, | |||
| util::Injected<IoContext> io) | |||
| : mIo(std::move(io)) | |||
| { | |||
| mpScannerCallback = | |||
| std::make_shared<Callback>(std::move(state), std::move(factory), *mIo); | |||
| mpScanner = std::make_shared<Scanner>( | |||
| rescanPeriod, util::injectShared(mpScannerCallback), util::injectRef(*mIo)); | |||
| } | |||
| ~PeerGateways() | |||
| { | |||
| // Release the callback in the io thread so that gateway cleanup | |||
| // doesn't happen in the client thread | |||
| mIo->async(Deleter{*this}); | |||
| } | |||
| PeerGateways(const PeerGateways&) = delete; | |||
| PeerGateways& operator=(const PeerGateways&) = delete; | |||
| PeerGateways(PeerGateways&&) = delete; | |||
| PeerGateways& operator=(PeerGateways&&) = delete; | |||
| void enable(const bool bEnable) | |||
| { | |||
| auto pCallback = mpScannerCallback; | |||
| auto pScanner = mpScanner; | |||
| if (pCallback && pScanner) | |||
| { | |||
| mIo->async([pCallback, pScanner, bEnable] { | |||
| pCallback->mGateways.clear(); | |||
| pScanner->enable(bEnable); | |||
| }); | |||
| } | |||
| } | |||
| template <typename Handler> | |||
| void withGatewaysAsync(Handler handler) | |||
| { | |||
| auto pCallback = mpScannerCallback; | |||
| if (pCallback) | |||
| { | |||
| mIo->async([pCallback, handler] { | |||
| handler(pCallback->mGateways.begin(), pCallback->mGateways.end()); | |||
| }); | |||
| } | |||
| } | |||
| void updateNodeState(const NodeState& state) | |||
| { | |||
| auto pCallback = mpScannerCallback; | |||
| if (pCallback) | |||
| { | |||
| mIo->async([pCallback, state] { | |||
| pCallback->mState = state; | |||
| for (const auto& entry : pCallback->mGateways) | |||
| { | |||
| entry.second->updateNodeState(state); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| // If a gateway has become non-responsive or is throwing exceptions, | |||
| // this method can be invoked to either fix it or discard it. | |||
| void repairGateway(const asio::ip::address& gatewayAddr) | |||
| { | |||
| auto pCallback = mpScannerCallback; | |||
| auto pScanner = mpScanner; | |||
| if (pCallback && pScanner) | |||
| { | |||
| mIo->async([pCallback, pScanner, gatewayAddr] { | |||
| if (pCallback->mGateways.erase(gatewayAddr)) | |||
| { | |||
| // If we erased a gateway, rescan again immediately so that | |||
| // we will re-initialize it if it's still present | |||
| pScanner->scan(); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| private: | |||
| struct Callback | |||
| { | |||
| Callback(NodeState state, GatewayFactory factory, IoType& io) | |||
| : mState(std::move(state)) | |||
| , mFactory(std::move(factory)) | |||
| , mIo(io) | |||
| { | |||
| } | |||
| template <typename AddrRange> | |||
| void operator()(const AddrRange& range) | |||
| { | |||
| using namespace std; | |||
| // Get the set of current addresses. | |||
| vector<asio::ip::address> curAddrs; | |||
| curAddrs.reserve(mGateways.size()); | |||
| transform(std::begin(mGateways), std::end(mGateways), back_inserter(curAddrs), | |||
| [](const typename GatewayMap::value_type& vt) { return vt.first; }); | |||
| // Now use set_difference to determine the set of addresses that | |||
| // are new and the set of cur addresses that are no longer there | |||
| vector<asio::ip::address> newAddrs; | |||
| set_difference(std::begin(range), std::end(range), std::begin(curAddrs), | |||
| std::end(curAddrs), back_inserter(newAddrs)); | |||
| vector<asio::ip::address> staleAddrs; | |||
| set_difference(std::begin(curAddrs), std::end(curAddrs), std::begin(range), | |||
| std::end(range), back_inserter(staleAddrs)); | |||
| // Remove the stale addresses | |||
| for (const auto& addr : staleAddrs) | |||
| { | |||
| mGateways.erase(addr); | |||
| } | |||
| // Add the new addresses | |||
| for (const auto& addr : newAddrs) | |||
| { | |||
| try | |||
| { | |||
| // Only handle v4 for now | |||
| if (addr.is_v4()) | |||
| { | |||
| info(mIo.log()) << "initializing peer gateway on interface " << addr; | |||
| mGateways.emplace(addr, mFactory(mState, util::injectRef(mIo), addr.to_v4())); | |||
| } | |||
| } | |||
| catch (const runtime_error& e) | |||
| { | |||
| warning(mIo.log()) << "failed to init gateway on interface " << addr | |||
| << " reason: " << e.what(); | |||
| } | |||
| } | |||
| } | |||
| NodeState mState; | |||
| GatewayFactory mFactory; | |||
| IoType& mIo; | |||
| GatewayMap mGateways; | |||
| }; | |||
| using Scanner = InterfaceScanner<std::shared_ptr<Callback>, IoType&>; | |||
| struct Deleter | |||
| { | |||
| Deleter(PeerGateways& gateways) | |||
| : mpScannerCallback(std::move(gateways.mpScannerCallback)) | |||
| , mpScanner(std::move(gateways.mpScanner)) | |||
| { | |||
| } | |||
| void operator()() | |||
| { | |||
| mpScanner.reset(); | |||
| mpScannerCallback.reset(); | |||
| } | |||
| std::shared_ptr<Callback> mpScannerCallback; | |||
| std::shared_ptr<Scanner> mpScanner; | |||
| }; | |||
| std::shared_ptr<Callback> mpScannerCallback; | |||
| std::shared_ptr<Scanner> mpScanner; | |||
| util::Injected<IoContext> mIo; | |||
| }; | |||
| // Factory function | |||
| template <typename NodeState, typename GatewayFactory, typename IoContext> | |||
| std::unique_ptr<PeerGateways<NodeState, GatewayFactory, IoContext>> makePeerGateways( | |||
| const std::chrono::seconds rescanPeriod, | |||
| NodeState state, | |||
| GatewayFactory factory, | |||
| util::Injected<IoContext> io) | |||
| { | |||
| using namespace std; | |||
| using Gateways = PeerGateways<NodeState, GatewayFactory, IoContext>; | |||
| return unique_ptr<Gateways>{ | |||
| new Gateways{rescanPeriod, move(state), move(factory), move(io)}}; | |||
| } | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,72 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/PeerGateways.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| template <typename NodeState, typename GatewayFactory, typename IoContext> | |||
| class Service | |||
| { | |||
| public: | |||
| using ServicePeerGateways = PeerGateways<NodeState, GatewayFactory, IoContext>; | |||
| Service(NodeState state, GatewayFactory factory, util::Injected<IoContext> io) | |||
| : mGateways( | |||
| std::chrono::seconds(5), std::move(state), std::move(factory), std::move(io)) | |||
| { | |||
| } | |||
| void enable(const bool bEnable) | |||
| { | |||
| mGateways.enable(bEnable); | |||
| } | |||
| // Asynchronously operate on the current set of peer gateways. The | |||
| // handler will be invoked in the service's io context. | |||
| template <typename Handler> | |||
| void withGatewaysAsync(Handler handler) | |||
| { | |||
| mGateways.withGatewaysAsync(std::move(handler)); | |||
| } | |||
| void updateNodeState(const NodeState& state) | |||
| { | |||
| mGateways.updateNodeState(state); | |||
| } | |||
| // Repair the gateway with the given address if possible. Its | |||
| // sockets may have been closed, for example, and the gateway needs | |||
| // to be regenerated. | |||
| void repairGateway(const asio::ip::address& gatewayAddr) | |||
| { | |||
| mGateways.repairGateway(gatewayAddr); | |||
| } | |||
| private: | |||
| ServicePeerGateways mGateways; | |||
| }; | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,139 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioService.hpp> | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/SafeAsyncHandler.hpp> | |||
| #include <array> | |||
| #include <cassert> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| template <std::size_t MaxPacketSize> | |||
| struct Socket | |||
| { | |||
| Socket(platforms::asio::AsioService& io) | |||
| : mpImpl(std::make_shared<Impl>(io)) | |||
| { | |||
| } | |||
| Socket(const Socket&) = delete; | |||
| Socket& operator=(const Socket&) = delete; | |||
| Socket(Socket&& rhs) | |||
| : mpImpl(std::move(rhs.mpImpl)) | |||
| { | |||
| } | |||
| std::size_t send( | |||
| const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) | |||
| { | |||
| assert(numBytes < MaxPacketSize); | |||
| return mpImpl->mSocket.send_to(asio::buffer(pData, numBytes), to); | |||
| } | |||
| template <typename Handler> | |||
| void receive(Handler handler) | |||
| { | |||
| mpImpl->mHandler = std::move(handler); | |||
| mpImpl->mSocket.async_receive_from( | |||
| asio::buffer(mpImpl->mReceiveBuffer, MaxPacketSize), mpImpl->mSenderEndpoint, | |||
| util::makeAsyncSafe(mpImpl)); | |||
| } | |||
| asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return mpImpl->mSocket.local_endpoint(); | |||
| } | |||
| struct Impl | |||
| { | |||
| Impl(platforms::asio::AsioService& io) | |||
| : mSocket(io.mService, asio::ip::udp::v4()) | |||
| { | |||
| } | |||
| ~Impl() | |||
| { | |||
| // Ignore error codes in shutdown and close as the socket may | |||
| // have already been forcibly closed | |||
| asio::error_code ec; | |||
| mSocket.shutdown(asio::ip::udp::socket::shutdown_both, ec); | |||
| mSocket.close(ec); | |||
| } | |||
| void operator()(const asio::error_code& error, const std::size_t numBytes) | |||
| { | |||
| if (!error && numBytes > 0 && numBytes <= MaxPacketSize) | |||
| { | |||
| const auto bufBegin = begin(mReceiveBuffer); | |||
| mHandler(mSenderEndpoint, bufBegin, bufBegin + static_cast<ptrdiff_t>(numBytes)); | |||
| } | |||
| } | |||
| asio::ip::udp::socket mSocket; | |||
| asio::ip::udp::endpoint mSenderEndpoint; | |||
| using Buffer = std::array<uint8_t, MaxPacketSize>; | |||
| Buffer mReceiveBuffer; | |||
| using ByteIt = typename Buffer::const_iterator; | |||
| std::function<void(const asio::ip::udp::endpoint&, ByteIt, ByteIt)> mHandler; | |||
| }; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| // Configure an asio socket for receiving multicast messages | |||
| template <std::size_t MaxPacketSize> | |||
| void configureMulticastSocket(Socket<MaxPacketSize>& socket, | |||
| const asio::ip::address_v4& addr, | |||
| const asio::ip::udp::endpoint& multicastEndpoint) | |||
| { | |||
| socket.mpImpl->mSocket.set_option(asio::ip::udp::socket::reuse_address(true)); | |||
| // ??? | |||
| socket.mpImpl->mSocket.set_option(asio::socket_base::broadcast(!addr.is_loopback())); | |||
| // ??? | |||
| socket.mpImpl->mSocket.set_option( | |||
| asio::ip::multicast::enable_loopback(addr.is_loopback())); | |||
| socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr)); | |||
| // Is from_string("0.0.0.0") best approach? | |||
| socket.mpImpl->mSocket.bind( | |||
| {asio::ip::address::from_string("0.0.0.0"), multicastEndpoint.port()}); | |||
| socket.mpImpl->mSocket.set_option( | |||
| asio::ip::multicast::join_group(multicastEndpoint.address().to_v4(), addr)); | |||
| } | |||
| // Configure an asio socket for receiving unicast messages | |||
| template <std::size_t MaxPacketSize> | |||
| void configureUnicastSocket( | |||
| Socket<MaxPacketSize>& socket, const asio::ip::address_v4& addr) | |||
| { | |||
| // ??? really necessary? | |||
| socket.mpImpl->mSocket.set_option( | |||
| asio::ip::multicast::enable_loopback(addr.is_loopback())); | |||
| socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr)); | |||
| socket.mpImpl->mSocket.bind(asio::ip::udp::endpoint{addr, 0}); | |||
| } | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,330 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/IpV4Interface.hpp> | |||
| #include <ableton/discovery/MessageTypes.hpp> | |||
| #include <ableton/discovery/v1/Messages.hpp> | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/Injected.hpp> | |||
| #include <ableton/util/SafeAsyncHandler.hpp> | |||
| #include <algorithm> | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| // An exception thrown when sending a udp message fails. Stores the | |||
| // interface through which the sending failed. | |||
| struct UdpSendException : std::runtime_error | |||
| { | |||
| UdpSendException(const std::runtime_error& e, asio::ip::address ifAddr) | |||
| : std::runtime_error(e.what()) | |||
| , interfaceAddr(std::move(ifAddr)) | |||
| { | |||
| } | |||
| asio::ip::address interfaceAddr; | |||
| }; | |||
| // Throws UdpSendException | |||
| template <typename Interface, typename NodeId, typename Payload> | |||
| void sendUdpMessage(Interface& iface, | |||
| NodeId from, | |||
| const uint8_t ttl, | |||
| const v1::MessageType messageType, | |||
| const Payload& payload, | |||
| const asio::ip::udp::endpoint& to) | |||
| { | |||
| using namespace std; | |||
| v1::MessageBuffer buffer; | |||
| const auto messageBegin = begin(buffer); | |||
| const auto messageEnd = | |||
| v1::detail::encodeMessage(move(from), ttl, messageType, payload, messageBegin); | |||
| const auto numBytes = static_cast<size_t>(distance(messageBegin, messageEnd)); | |||
| try | |||
| { | |||
| iface.send(buffer.data(), numBytes, to); | |||
| } | |||
| catch (const std::runtime_error& err) | |||
| { | |||
| throw UdpSendException{err, iface.endpoint().address()}; | |||
| } | |||
| } | |||
| // UdpMessenger uses a "shared_ptr pImpl" pattern to make it movable | |||
| // and to support safe async handler callbacks when receiving messages | |||
| // on the given interface. | |||
| template <typename Interface, typename NodeStateT, typename IoContext> | |||
| class UdpMessenger | |||
| { | |||
| public: | |||
| using NodeState = NodeStateT; | |||
| using NodeId = typename NodeState::IdType; | |||
| using Timer = typename util::Injected<IoContext>::type::Timer; | |||
| using TimerError = typename Timer::ErrorCode; | |||
| using TimePoint = typename Timer::TimePoint; | |||
| UdpMessenger(util::Injected<Interface> iface, | |||
| NodeState state, | |||
| util::Injected<IoContext> io, | |||
| const uint8_t ttl, | |||
| const uint8_t ttlRatio) | |||
| : mpImpl(std::make_shared<Impl>( | |||
| std::move(iface), std::move(state), std::move(io), ttl, ttlRatio)) | |||
| { | |||
| // We need to always listen for incoming traffic in order to | |||
| // respond to peer state broadcasts | |||
| mpImpl->listen(MulticastTag{}); | |||
| mpImpl->listen(UnicastTag{}); | |||
| mpImpl->broadcastState(); | |||
| } | |||
| UdpMessenger(const UdpMessenger&) = delete; | |||
| UdpMessenger& operator=(const UdpMessenger&) = delete; | |||
| UdpMessenger(UdpMessenger&& rhs) | |||
| : mpImpl(std::move(rhs.mpImpl)) | |||
| { | |||
| } | |||
| ~UdpMessenger() | |||
| { | |||
| if (mpImpl != nullptr) | |||
| { | |||
| try | |||
| { | |||
| mpImpl->sendByeBye(); | |||
| } | |||
| catch (const UdpSendException& err) | |||
| { | |||
| debug(mpImpl->mIo->log()) << "Failed to send bye bye message: " << err.what(); | |||
| } | |||
| } | |||
| } | |||
| void updateState(NodeState state) | |||
| { | |||
| mpImpl->updateState(std::move(state)); | |||
| } | |||
| // Broadcast the current state of the system to all peers. May throw | |||
| // std::runtime_error if assembling a broadcast message fails or if | |||
| // there is an error at the transport layer. Throws on failure. | |||
| void broadcastState() | |||
| { | |||
| mpImpl->broadcastState(); | |||
| } | |||
| // Asynchronous receive function for incoming messages from peers. Will | |||
| // return immediately and the handler will be invoked when a message | |||
| // is received. Handler must have operator() overloads for PeerState and | |||
| // ByeBye messages. | |||
| template <typename Handler> | |||
| void receive(Handler handler) | |||
| { | |||
| mpImpl->setReceiveHandler(std::move(handler)); | |||
| } | |||
| private: | |||
| struct Impl : std::enable_shared_from_this<Impl> | |||
| { | |||
| Impl(util::Injected<Interface> iface, | |||
| NodeState state, | |||
| util::Injected<IoContext> io, | |||
| const uint8_t ttl, | |||
| const uint8_t ttlRatio) | |||
| : mIo(std::move(io)) | |||
| , mInterface(std::move(iface)) | |||
| , mState(std::move(state)) | |||
| , mTimer(mIo->makeTimer()) | |||
| , mLastBroadcastTime{} | |||
| , mTtl(ttl) | |||
| , mTtlRatio(ttlRatio) | |||
| , mPeerStateHandler([](PeerState<NodeState>) {}) | |||
| , mByeByeHandler([](ByeBye<NodeId>) {}) | |||
| { | |||
| } | |||
| template <typename Handler> | |||
| void setReceiveHandler(Handler handler) | |||
| { | |||
| mPeerStateHandler = [handler]( | |||
| PeerState<NodeState> state) { handler(std::move(state)); }; | |||
| mByeByeHandler = [handler](ByeBye<NodeId> byeBye) { handler(std::move(byeBye)); }; | |||
| } | |||
| void sendByeBye() | |||
| { | |||
| sendUdpMessage( | |||
| *mInterface, mState.ident(), 0, v1::kByeBye, makePayload(), multicastEndpoint()); | |||
| } | |||
| void updateState(NodeState state) | |||
| { | |||
| mState = std::move(state); | |||
| } | |||
| void broadcastState() | |||
| { | |||
| using namespace std::chrono; | |||
| const auto minBroadcastPeriod = milliseconds{50}; | |||
| const auto nominalBroadcastPeriod = milliseconds(mTtl * 1000 / mTtlRatio); | |||
| const auto timeSinceLastBroadcast = | |||
| duration_cast<milliseconds>(mTimer.now() - mLastBroadcastTime); | |||
| // The rate is limited to maxBroadcastRate to prevent flooding the network. | |||
| const auto delay = minBroadcastPeriod - timeSinceLastBroadcast; | |||
| // Schedule the next broadcast before we actually send the | |||
| // message so that if sending throws an exception we are still | |||
| // scheduled to try again. We want to keep trying at our | |||
| // interval as long as this instance is alive. | |||
| mTimer.expires_from_now(delay > milliseconds{0} ? delay : nominalBroadcastPeriod); | |||
| mTimer.async_wait([this](const TimerError e) { | |||
| if (!e) | |||
| { | |||
| broadcastState(); | |||
| } | |||
| }); | |||
| // If we're not delaying, broadcast now | |||
| if (delay < milliseconds{1}) | |||
| { | |||
| debug(mIo->log()) << "Broadcasting state"; | |||
| sendPeerState(v1::kAlive, multicastEndpoint()); | |||
| } | |||
| } | |||
| void sendPeerState( | |||
| const v1::MessageType messageType, const asio::ip::udp::endpoint& to) | |||
| { | |||
| sendUdpMessage( | |||
| *mInterface, mState.ident(), mTtl, messageType, toPayload(mState), to); | |||
| mLastBroadcastTime = mTimer.now(); | |||
| } | |||
| void sendResponse(const asio::ip::udp::endpoint& to) | |||
| { | |||
| sendPeerState(v1::kResponse, to); | |||
| } | |||
| template <typename Tag> | |||
| void listen(Tag tag) | |||
| { | |||
| mInterface->receive(util::makeAsyncSafe(this->shared_from_this()), tag); | |||
| } | |||
| template <typename Tag, typename It> | |||
| void operator()(Tag tag, | |||
| const asio::ip::udp::endpoint& from, | |||
| const It messageBegin, | |||
| const It messageEnd) | |||
| { | |||
| auto result = v1::parseMessageHeader<NodeId>(messageBegin, messageEnd); | |||
| const auto& header = result.first; | |||
| // Ignore messages from self and other groups | |||
| if (header.ident != mState.ident() && header.groupId == 0) | |||
| { | |||
| debug(mIo->log()) << "Received message type " | |||
| << static_cast<int>(header.messageType) << " from peer " | |||
| << header.ident; | |||
| switch (header.messageType) | |||
| { | |||
| case v1::kAlive: | |||
| sendResponse(from); | |||
| receivePeerState(std::move(result.first), result.second, messageEnd); | |||
| break; | |||
| case v1::kResponse: | |||
| receivePeerState(std::move(result.first), result.second, messageEnd); | |||
| break; | |||
| case v1::kByeBye: | |||
| receiveByeBye(std::move(result.first.ident)); | |||
| break; | |||
| default: | |||
| info(mIo->log()) << "Unknown message received of type: " << header.messageType; | |||
| } | |||
| } | |||
| listen(tag); | |||
| } | |||
| template <typename It> | |||
| void receivePeerState( | |||
| v1::MessageHeader<NodeId> header, It payloadBegin, It payloadEnd) | |||
| { | |||
| try | |||
| { | |||
| auto state = NodeState::fromPayload( | |||
| std::move(header.ident), std::move(payloadBegin), std::move(payloadEnd)); | |||
| // Handlers must only be called once | |||
| auto handler = std::move(mPeerStateHandler); | |||
| mPeerStateHandler = [](PeerState<NodeState>) {}; | |||
| handler(PeerState<NodeState>{std::move(state), header.ttl}); | |||
| } | |||
| catch (const std::runtime_error& err) | |||
| { | |||
| info(mIo->log()) << "Ignoring peer state message: " << err.what(); | |||
| } | |||
| } | |||
| void receiveByeBye(NodeId nodeId) | |||
| { | |||
| // Handlers must only be called once | |||
| auto byeByeHandler = std::move(mByeByeHandler); | |||
| mByeByeHandler = [](ByeBye<NodeId>) {}; | |||
| byeByeHandler(ByeBye<NodeId>{std::move(nodeId)}); | |||
| } | |||
| util::Injected<IoContext> mIo; | |||
| util::Injected<Interface> mInterface; | |||
| NodeState mState; | |||
| Timer mTimer; | |||
| TimePoint mLastBroadcastTime; | |||
| uint8_t mTtl; | |||
| uint8_t mTtlRatio; | |||
| std::function<void(PeerState<NodeState>)> mPeerStateHandler; | |||
| std::function<void(ByeBye<NodeId>)> mByeByeHandler; | |||
| }; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| // Factory function | |||
| template <typename Interface, typename NodeState, typename IoContext> | |||
| UdpMessenger<Interface, NodeState, IoContext> makeUdpMessenger( | |||
| util::Injected<Interface> iface, | |||
| NodeState state, | |||
| util::Injected<IoContext> io, | |||
| const uint8_t ttl, | |||
| const uint8_t ttlRatio) | |||
| { | |||
| return UdpMessenger<Interface, NodeState, IoContext>{ | |||
| std::move(iface), std::move(state), std::move(io), ttl, ttlRatio}; | |||
| } | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,75 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/util/Log.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| namespace test | |||
| { | |||
| class Interface | |||
| { | |||
| public: | |||
| void send(const uint8_t* const bytes, | |||
| const size_t numBytes, | |||
| const asio::ip::udp::endpoint& endpoint) | |||
| { | |||
| sentMessages.push_back( | |||
| std::make_pair(std::vector<uint8_t>{bytes, bytes + numBytes}, endpoint)); | |||
| } | |||
| template <typename Callback, typename Tag> | |||
| void receive(Callback callback, Tag tag) | |||
| { | |||
| mCallback = [callback, tag]( | |||
| const asio::ip::udp::endpoint& from, const std::vector<uint8_t>& buffer) { | |||
| callback(tag, from, begin(buffer), end(buffer)); | |||
| }; | |||
| } | |||
| template <typename It> | |||
| void incomingMessage( | |||
| const asio::ip::udp::endpoint& from, It messageBegin, It messageEnd) | |||
| { | |||
| std::vector<uint8_t> buffer{messageBegin, messageEnd}; | |||
| mCallback(from, buffer); | |||
| } | |||
| asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return asio::ip::udp::endpoint({}, 0); | |||
| } | |||
| using SentMessage = std::pair<std::vector<uint8_t>, asio::ip::udp::endpoint>; | |||
| std::vector<SentMessage> sentMessages; | |||
| private: | |||
| using ReceiveCallback = | |||
| std::function<void(const asio::ip::udp::endpoint&, const std::vector<uint8_t>&)>; | |||
| ReceiveCallback mCallback; | |||
| }; | |||
| } // namespace test | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,98 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/NetworkByteStreamSerializable.hpp> | |||
| #include <cstdint> | |||
| #include <utility> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| namespace test | |||
| { | |||
| // Test payload entries | |||
| // A fixed-size entry type | |||
| struct Foo | |||
| { | |||
| enum | |||
| { | |||
| key = '_foo' | |||
| }; | |||
| std::int32_t fooVal; | |||
| friend std::uint32_t sizeInByteStream(const Foo& foo) | |||
| { | |||
| // Namespace qualification is needed to avoid ambiguous function definitions | |||
| return discovery::sizeInByteStream(foo.fooVal); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const Foo& foo, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(foo.fooVal, std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<Foo, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = Deserialize<decltype(fooVal)>::fromNetworkByteStream( | |||
| std::move(begin), std::move(end)); | |||
| return std::make_pair(Foo{std::move(result.first)}, std::move(result.second)); | |||
| } | |||
| }; | |||
| // A variable-size entry type | |||
| struct Bar | |||
| { | |||
| enum | |||
| { | |||
| key = '_bar' | |||
| }; | |||
| std::vector<std::uint64_t> barVals; | |||
| friend std::uint32_t sizeInByteStream(const Bar& bar) | |||
| { | |||
| return discovery::sizeInByteStream(bar.barVals); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const Bar& bar, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(bar.barVals, out); | |||
| } | |||
| template <typename It> | |||
| static std::pair<Bar, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = Deserialize<decltype(barVals)>::fromNetworkByteStream( | |||
| std::move(begin), std::move(end)); | |||
| return std::make_pair(Bar{std::move(result.first)}, std::move(result.second)); | |||
| } | |||
| }; | |||
| } // namespace test | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,83 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/Log.hpp> | |||
| #include <ableton/util/test/IoService.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| namespace test | |||
| { | |||
| class Socket | |||
| { | |||
| public: | |||
| Socket(util::test::IoService&) | |||
| { | |||
| } | |||
| friend void configureUnicastSocket(Socket&, const asio::ip::address_v4&) | |||
| { | |||
| } | |||
| std::size_t send( | |||
| const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) | |||
| { | |||
| sentMessages.push_back( | |||
| std::make_pair(std::vector<uint8_t>{pData, pData + numBytes}, to)); | |||
| return numBytes; | |||
| } | |||
| template <typename Handler> | |||
| void receive(Handler handler) | |||
| { | |||
| mCallback = [handler](const asio::ip::udp::endpoint& from, | |||
| const std::vector<uint8_t>& buffer) { handler(from, begin(buffer), end(buffer)); }; | |||
| } | |||
| template <typename It> | |||
| void incomingMessage( | |||
| const asio::ip::udp::endpoint& from, It messageBegin, It messageEnd) | |||
| { | |||
| std::vector<uint8_t> buffer{messageBegin, messageEnd}; | |||
| mCallback(from, buffer); | |||
| } | |||
| asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return asio::ip::udp::endpoint({}, 0); | |||
| } | |||
| using SentMessage = std::pair<std::vector<uint8_t>, asio::ip::udp::endpoint>; | |||
| std::vector<SentMessage> sentMessages; | |||
| private: | |||
| using ReceiveCallback = | |||
| std::function<void(const asio::ip::udp::endpoint&, const std::vector<uint8_t>&)>; | |||
| ReceiveCallback mCallback; | |||
| }; | |||
| } // namespace test | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,168 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/Payload.hpp> | |||
| #include <array> | |||
| namespace ableton | |||
| { | |||
| namespace discovery | |||
| { | |||
| namespace v1 | |||
| { | |||
| // The maximum size of a message, in bytes | |||
| const std::size_t kMaxMessageSize = 512; | |||
| // Utility typedef for an array of bytes of maximum message size | |||
| using MessageBuffer = std::array<uint8_t, v1::kMaxMessageSize>; | |||
| using MessageType = uint8_t; | |||
| using SessionGroupId = uint16_t; | |||
| const MessageType kInvalid = 0; | |||
| const MessageType kAlive = 1; | |||
| const MessageType kResponse = 2; | |||
| const MessageType kByeBye = 3; | |||
| template <typename NodeId> | |||
| struct MessageHeader | |||
| { | |||
| MessageType messageType; | |||
| uint8_t ttl; | |||
| SessionGroupId groupId; | |||
| NodeId ident; | |||
| friend std::uint32_t sizeInByteStream(const MessageHeader& header) | |||
| { | |||
| return discovery::sizeInByteStream(header.messageType) | |||
| + discovery::sizeInByteStream(header.ttl) | |||
| + discovery::sizeInByteStream(header.groupId) | |||
| + discovery::sizeInByteStream(header.ident); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const MessageHeader& header, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(header.ident, | |||
| discovery::toNetworkByteStream(header.groupId, | |||
| discovery::toNetworkByteStream(header.ttl, | |||
| discovery::toNetworkByteStream(header.messageType, std::move(out))))); | |||
| } | |||
| template <typename It> | |||
| static std::pair<MessageHeader, It> fromNetworkByteStream(It begin, const It end) | |||
| { | |||
| using namespace std; | |||
| MessageHeader header; | |||
| tie(header.messageType, begin) = | |||
| Deserialize<decltype(header.messageType)>::fromNetworkByteStream(begin, end); | |||
| tie(header.ttl, begin) = | |||
| Deserialize<decltype(header.ttl)>::fromNetworkByteStream(begin, end); | |||
| tie(header.groupId, begin) = | |||
| Deserialize<decltype(header.groupId)>::fromNetworkByteStream(begin, end); | |||
| tie(header.ident, begin) = | |||
| Deserialize<decltype(header.ident)>::fromNetworkByteStream(begin, end); | |||
| return make_pair(move(header), move(begin)); | |||
| } | |||
| }; | |||
| namespace detail | |||
| { | |||
| // Types that are only used in the sending/parsing of messages, not | |||
| // publicly exposed. | |||
| using ProtocolHeader = std::array<char, 8>; | |||
| const ProtocolHeader kProtocolHeader = {{'_', 'a', 's', 'd', 'p', '_', 'v', 1}}; | |||
| // Must have at least kMaxMessageSize bytes available in the output stream | |||
| template <typename NodeId, typename Payload, typename It> | |||
| It encodeMessage(NodeId from, | |||
| const uint8_t ttl, | |||
| const MessageType messageType, | |||
| const Payload& payload, | |||
| It out) | |||
| { | |||
| using namespace std; | |||
| const MessageHeader<NodeId> header = {messageType, ttl, 0, std::move(from)}; | |||
| const auto messageSize = | |||
| kProtocolHeader.size() + sizeInByteStream(header) + sizeInByteStream(payload); | |||
| if (messageSize < kMaxMessageSize) | |||
| { | |||
| return toNetworkByteStream( | |||
| payload, toNetworkByteStream( | |||
| header, copy(begin(kProtocolHeader), end(kProtocolHeader), move(out)))); | |||
| } | |||
| else | |||
| { | |||
| throw range_error("Exceeded maximum message size"); | |||
| } | |||
| } | |||
| } // namespace detail | |||
| template <typename NodeId, typename Payload, typename It> | |||
| It aliveMessage(NodeId from, const uint8_t ttl, const Payload& payload, It out) | |||
| { | |||
| return detail::encodeMessage(std::move(from), ttl, kAlive, payload, std::move(out)); | |||
| } | |||
| template <typename NodeId, typename Payload, typename It> | |||
| It responseMessage(NodeId from, const uint8_t ttl, const Payload& payload, It out) | |||
| { | |||
| return detail::encodeMessage(std::move(from), ttl, kResponse, payload, std::move(out)); | |||
| } | |||
| template <typename NodeId, typename It> | |||
| It byeByeMessage(NodeId from, It out) | |||
| { | |||
| return detail::encodeMessage( | |||
| std::move(from), 0, kByeBye, makePayload(), std::move(out)); | |||
| } | |||
| template <typename NodeId, typename It> | |||
| std::pair<MessageHeader<NodeId>, It> parseMessageHeader(It bytesBegin, const It bytesEnd) | |||
| { | |||
| using namespace std; | |||
| using ItDiff = typename iterator_traits<It>::difference_type; | |||
| MessageHeader<NodeId> header = {}; | |||
| const auto protocolHeaderSize = discovery::sizeInByteStream(detail::kProtocolHeader); | |||
| const auto minMessageSize = | |||
| static_cast<ItDiff>(protocolHeaderSize + sizeInByteStream(header)); | |||
| // If there are enough bytes in the stream to make a header and if | |||
| // the first bytes in the stream are the protocol header, then | |||
| // proceed to parse the stream. | |||
| if (distance(bytesBegin, bytesEnd) >= minMessageSize | |||
| && equal(begin(detail::kProtocolHeader), end(detail::kProtocolHeader), bytesBegin)) | |||
| { | |||
| tie(header, bytesBegin) = MessageHeader<NodeId>::fromNetworkByteStream( | |||
| bytesBegin + protocolHeaderSize, bytesEnd); | |||
| } | |||
| return make_pair(move(header), move(bytesBegin)); | |||
| } | |||
| } // namespace v1 | |||
| } // namespace discovery | |||
| } // namespace ableton | |||
| @@ -0,0 +1,103 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/NetworkByteStreamSerializable.hpp> | |||
| #include <cmath> | |||
| #include <cstdint> | |||
| #include <tuple> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| struct Beats : std::tuple<std::int64_t> | |||
| { | |||
| Beats() = default; | |||
| explicit Beats(const double beats) | |||
| : std::tuple<std::int64_t>(llround(beats * 1e6)) | |||
| { | |||
| } | |||
| explicit Beats(const std::int64_t microBeats) | |||
| : std::tuple<std::int64_t>(microBeats) | |||
| { | |||
| } | |||
| double floating() const | |||
| { | |||
| return microBeats() / 1e6; | |||
| } | |||
| std::int64_t microBeats() const | |||
| { | |||
| return std::get<0>(*this); | |||
| } | |||
| Beats operator-() const | |||
| { | |||
| return Beats{-microBeats()}; | |||
| } | |||
| friend Beats abs(const Beats b) | |||
| { | |||
| return Beats{std::abs(b.microBeats())}; | |||
| } | |||
| friend Beats operator+(const Beats lhs, const Beats rhs) | |||
| { | |||
| return Beats{lhs.microBeats() + rhs.microBeats()}; | |||
| } | |||
| friend Beats operator-(const Beats lhs, const Beats rhs) | |||
| { | |||
| return Beats{lhs.microBeats() - rhs.microBeats()}; | |||
| } | |||
| friend Beats operator%(const Beats lhs, const Beats rhs) | |||
| { | |||
| return rhs == Beats{0.} ? Beats{0.} : Beats{lhs.microBeats() % rhs.microBeats()}; | |||
| } | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const Beats beats) | |||
| { | |||
| return discovery::sizeInByteStream(beats.microBeats()); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const Beats beats, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(beats.microBeats(), std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<Beats, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = discovery::Deserialize<std::int64_t>::fromNetworkByteStream( | |||
| std::move(begin), std::move(end)); | |||
| return std::make_pair(Beats{result.first}, std::move(result.second)); | |||
| } | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,115 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/GhostXForm.hpp> | |||
| #include <ableton/link/Timeline.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| // Clamp the tempo of the given timeline to the valid Link range | |||
| inline Timeline clampTempo(const Timeline timeline) | |||
| { | |||
| const double kMinBpm = 20.0; | |||
| const double kMaxBpm = 999.0; | |||
| return {Tempo{(std::min)((std::max)(timeline.tempo.bpm(), kMinBpm), kMaxBpm)}, | |||
| timeline.beatOrigin, timeline.timeOrigin}; | |||
| } | |||
| // Given an existing client timeline, a session timeline, and the | |||
| // global host transform of the session, return a new version of the client | |||
| // timeline. The resulting new client timeline is continuous with the | |||
| // previous timeline so that curClient.toBeats(atTime) == | |||
| // result.toBeats(atTime). | |||
| inline Timeline updateClientTimelineFromSession(const Timeline curClient, | |||
| const Timeline session, | |||
| const std::chrono::microseconds atTime, | |||
| const GhostXForm xform) | |||
| { | |||
| // An intermediate timeline representing the continuation of the | |||
| // existing client timeline with the tempo from the session timeline. | |||
| const auto tempTl = Timeline{session.tempo, curClient.toBeats(atTime), atTime}; | |||
| // The host time corresponding to beat 0 on the session | |||
| // timeline. Beat 0 on the session timeline is important because it | |||
| // serves as the origin of the quantization grid for all participants. | |||
| const auto hostBeatZero = xform.ghostToHost(session.fromBeats(Beats{INT64_C(0)})); | |||
| // The new client timeline becomes the result of sliding the | |||
| // intermediate timeline back so that it's anchor corresponds to | |||
| // beat zero on the session timeline. The result preserves the | |||
| // magnitude of beats on the client timeline while encoding the | |||
| // quantization reference point in the time and beatOrigins. | |||
| return {tempTl.tempo, tempTl.toBeats(hostBeatZero), hostBeatZero}; | |||
| } | |||
| inline Timeline updateSessionTimelineFromClient(const Timeline curSession, | |||
| const Timeline client, | |||
| const std::chrono::microseconds atTime, | |||
| const GhostXForm xform) | |||
| { | |||
| // The client timeline was constructed so that it's timeOrigin | |||
| // corresponds to beat 0 on the session timeline. | |||
| const auto ghostBeat0 = xform.hostToGhost(client.timeOrigin); | |||
| const auto zero = Beats{INT64_C(0)}; | |||
| // If beat 0 was not shifted and there is not a new tempo, an update | |||
| // of the session timeline is not required. Don't create an | |||
| // equivalent timeline with different anchor points if not needed as | |||
| // this will trigger other unnecessary changes. | |||
| if (curSession.toBeats(ghostBeat0) == zero && client.tempo == curSession.tempo) | |||
| { | |||
| return curSession; | |||
| } | |||
| else | |||
| { | |||
| // An intermediate timeline representing the new tempo, the | |||
| // effective time, and a possibly adjusted origin. | |||
| const auto tempTl = Timeline{client.tempo, zero, ghostBeat0}; | |||
| // The final session timeline must have the beat corresponding to | |||
| // atTime on the old session timeline as its beatOrigin because this is | |||
| // used for prioritization of timelines among peers - we can't let a | |||
| // modification applied by the client artificially increase or | |||
| // reduce the timeline's priority in the session. The new beat | |||
| // origin should be as close as possible to lining up with atTime, | |||
| // but we must also make sure that it's > curSession.beatOrigin | |||
| // because otherwise it will get ignored. | |||
| const auto newBeatOrigin = (std::max)(curSession.toBeats(xform.hostToGhost(atTime)), | |||
| curSession.beatOrigin + Beats{INT64_C(1)}); | |||
| return {client.tempo, newBeatOrigin, tempTl.fromBeats(newBeatOrigin)}; | |||
| } | |||
| } | |||
| // Shift timeline so result.toBeats(t) == client.toBeats(t) + | |||
| // shift. This takes into account the fact that the timeOrigin | |||
| // corresponds to beat 0 on the session timeline. Using this function | |||
| // and then setting the session timeline with the result will change | |||
| // the phase of the session by the given shift amount. | |||
| inline Timeline shiftClientTimeline(Timeline client, const Beats shift) | |||
| { | |||
| const auto timeDelta = client.fromBeats(shift) - client.fromBeats(Beats{INT64_C(0)}); | |||
| client.timeOrigin = client.timeOrigin - timeDelta; | |||
| return client; | |||
| } | |||
| } // link | |||
| } // ableton | |||
| @@ -0,0 +1,446 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/Service.hpp> | |||
| #include <ableton/link/ClientSessionTimelines.hpp> | |||
| #include <ableton/link/Gateway.hpp> | |||
| #include <ableton/link/GhostXForm.hpp> | |||
| #include <ableton/link/NodeState.hpp> | |||
| #include <ableton/link/Peers.hpp> | |||
| #include <ableton/link/Sessions.hpp> | |||
| #include <mutex> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| namespace detail | |||
| { | |||
| template <typename Clock> | |||
| GhostXForm initXForm(const Clock& clock) | |||
| { | |||
| // Make the current time map to a ghost time of 0 with ghost time | |||
| // increasing at the same rate as clock time | |||
| return {1.0, -clock.micros()}; | |||
| } | |||
| // The timespan in which local modifications to the timeline will be | |||
| // preferred over any modifications coming from the network. | |||
| const auto kLocalModGracePeriod = std::chrono::seconds(1); | |||
| } // namespace detail | |||
| // function types corresponding to the Controller callback type params | |||
| using PeerCountCallback = std::function<void(std::size_t)>; | |||
| using TempoCallback = std::function<void(ableton::link::Tempo)>; | |||
| // The main Link controller | |||
| template <typename PeerCountCallback, | |||
| typename TempoCallback, | |||
| typename Clock, | |||
| typename IoContext> | |||
| class Controller | |||
| { | |||
| public: | |||
| using Ticks = typename Clock::Ticks; | |||
| Controller(Tempo tempo, | |||
| PeerCountCallback peerCallback, | |||
| TempoCallback tempoCallback, | |||
| Clock clock, | |||
| util::Injected<IoContext> io) | |||
| : mTempoCallback(std::move(tempoCallback)) | |||
| , mClock(std::move(clock)) | |||
| , mNodeId(NodeId::random()) | |||
| , mSessionId(mNodeId) | |||
| , mGhostXForm(detail::initXForm(mClock)) | |||
| , mSessionTimeline(clampTempo({tempo, Beats{0.}, std::chrono::microseconds{0}})) | |||
| , mClientTimeline({mSessionTimeline.tempo, Beats{0.}, | |||
| mGhostXForm.ghostToHost(std::chrono::microseconds{0})}) | |||
| , mRtClientTimeline(mClientTimeline) | |||
| , mRtClientTimelineTimestamp(0) | |||
| , mSessionPeerCounter(*this, std::move(peerCallback)) | |||
| , mEnabled(false) | |||
| , mIo(std::move(io)) | |||
| , mRealtimeIo(util::injectRef(*mIo)) | |||
| , mPeers(util::injectRef(*mIo), | |||
| std::ref(mSessionPeerCounter), | |||
| SessionTimelineCallback{*this}) | |||
| , mSessions({mSessionId, mSessionTimeline, {mGhostXForm, mClock.micros()}}, | |||
| util::injectRef(mPeers), | |||
| MeasurePeer{*this}, | |||
| JoinSessionCallback{*this}, | |||
| util::injectRef(*mIo), | |||
| mClock) | |||
| , mDiscovery( | |||
| std::make_pair(NodeState{mNodeId, mSessionId, mSessionTimeline}, mGhostXForm), | |||
| GatewayFactory{*this}, | |||
| util::injectVal(mIo->clone(UdpSendExceptionHandler{*this}))) | |||
| { | |||
| } | |||
| Controller(const Controller&) = delete; | |||
| Controller(Controller&&) = delete; | |||
| Controller& operator=(const Controller&) = delete; | |||
| Controller& operator=(Controller&&) = delete; | |||
| void enable(const bool bEnable) | |||
| { | |||
| const bool bWasEnabled = mEnabled.exchange(bEnable); | |||
| if (bWasEnabled != bEnable) | |||
| { | |||
| mRealtimeIo.async([this, bEnable] { | |||
| if (bEnable) | |||
| { | |||
| // Always reset when first enabling to avoid hijacking | |||
| // tempo in existing sessions | |||
| resetState(); | |||
| } | |||
| mDiscovery.enable(bEnable); | |||
| }); | |||
| } | |||
| } | |||
| bool isEnabled() const | |||
| { | |||
| return mEnabled; | |||
| } | |||
| std::size_t numPeers() const | |||
| { | |||
| return mSessionPeerCounter.mSessionPeerCount; | |||
| } | |||
| // Get the current Link timeline. Thread-safe but may block, so | |||
| // it cannot be used from audio thread. | |||
| Timeline timeline() const | |||
| { | |||
| std::lock_guard<std::mutex> lock(mClientTimelineGuard); | |||
| return mClientTimeline; | |||
| } | |||
| // Set the timeline to be used, starting at the given | |||
| // time. Thread-safe but may block, so it cannot be used from audio thread. | |||
| void setTimeline(Timeline newTimeline, const std::chrono::microseconds atTime) | |||
| { | |||
| newTimeline = clampTempo(newTimeline); | |||
| { | |||
| std::lock_guard<std::mutex> lock(mClientTimelineGuard); | |||
| mClientTimeline = newTimeline; | |||
| } | |||
| mIo->async([this, newTimeline, atTime] { | |||
| handleTimelineFromClient(updateSessionTimelineFromClient( | |||
| mSessionTimeline, newTimeline, atTime, mGhostXForm)); | |||
| }); | |||
| } | |||
| // Non-blocking timeline access for a realtime context. NOT | |||
| // thread-safe. Must not be called from multiple threads | |||
| // concurrently and must not be called concurrently with setTimelineRtSafe. | |||
| Timeline timelineRtSafe() const | |||
| { | |||
| // Respect the session timing guard but don't block on it. If we | |||
| // can't access it because it's being modified we fall back to our | |||
| // cached version of the timeline. | |||
| const auto now = mClock.micros(); | |||
| if (now - mRtClientTimelineTimestamp > detail::kLocalModGracePeriod | |||
| && mSessionTimingGuard.try_lock()) | |||
| { | |||
| const auto clientTimeline = updateClientTimelineFromSession( | |||
| mRtClientTimeline, mSessionTimeline, now, mGhostXForm); | |||
| mSessionTimingGuard.unlock(); | |||
| if (clientTimeline != mRtClientTimeline) | |||
| { | |||
| mRtClientTimeline = clientTimeline; | |||
| } | |||
| } | |||
| return mRtClientTimeline; | |||
| } | |||
| // should only be called from the audio thread | |||
| void setTimelineRtSafe(Timeline newTimeline, const std::chrono::microseconds atTime) | |||
| { | |||
| newTimeline = clampTempo(newTimeline); | |||
| // Cache the new timeline for serving back to the client | |||
| mRtClientTimeline = newTimeline; | |||
| mRtClientTimelineTimestamp = | |||
| isEnabled() ? mClock.micros() : std::chrono::microseconds(0); | |||
| // Update the session timeline from the new client timeline | |||
| mRealtimeIo.async([this, newTimeline, atTime] { | |||
| // Synchronize with the non-rt version of the client timeline | |||
| { | |||
| std::lock_guard<std::mutex> lock(mClientTimelineGuard); | |||
| mClientTimeline = newTimeline; | |||
| } | |||
| handleTimelineFromClient(updateSessionTimelineFromClient( | |||
| mSessionTimeline, newTimeline, atTime, mGhostXForm)); | |||
| }); | |||
| } | |||
| private: | |||
| void updateSessionTiming(const Timeline newTimeline, const GhostXForm newXForm) | |||
| { | |||
| const auto oldTimeline = mSessionTimeline; | |||
| const auto oldXForm = mGhostXForm; | |||
| if (oldTimeline != newTimeline || oldXForm != newXForm) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mSessionTimingGuard); | |||
| mSessionTimeline = newTimeline; | |||
| mGhostXForm = newXForm; | |||
| } | |||
| // Update the client timeline based on the new session timing data | |||
| { | |||
| std::lock_guard<std::mutex> lock(mClientTimelineGuard); | |||
| mClientTimeline = updateClientTimelineFromSession( | |||
| mClientTimeline, mSessionTimeline, mClock.micros(), mGhostXForm); | |||
| } | |||
| // Push the change to the discovery service | |||
| mDiscovery.updateNodeState( | |||
| std::make_pair(NodeState{mNodeId, mSessionId, newTimeline}, newXForm)); | |||
| if (oldTimeline.tempo != newTimeline.tempo) | |||
| { | |||
| mTempoCallback(newTimeline.tempo); | |||
| } | |||
| } | |||
| } | |||
| void handleTimelineFromClient(Timeline tl) | |||
| { | |||
| mSessions.resetTimeline(tl); | |||
| mPeers.setSessionTimeline(mSessionId, tl); | |||
| updateSessionTiming(std::move(tl), mGhostXForm); | |||
| } | |||
| void handleTimelineFromSession(SessionId id, Timeline timeline) | |||
| { | |||
| debug(mIo->log()) << "Received timeline with tempo: " << timeline.tempo.bpm() | |||
| << " for session: " << id; | |||
| updateSessionTiming( | |||
| mSessions.sawSessionTimeline(std::move(id), std::move(timeline)), mGhostXForm); | |||
| } | |||
| void joinSession(const Session& session) | |||
| { | |||
| const bool sessionIdChanged = mSessionId != session.sessionId; | |||
| mSessionId = session.sessionId; | |||
| updateSessionTiming(session.timeline, session.measurement.xform); | |||
| if (sessionIdChanged) | |||
| { | |||
| debug(mIo->log()) << "Joining session " << session.sessionId << " with tempo " | |||
| << session.timeline.tempo.bpm(); | |||
| mSessionPeerCounter(); | |||
| } | |||
| } | |||
| void resetState() | |||
| { | |||
| mNodeId = NodeId::random(); | |||
| mSessionId = mNodeId; | |||
| const auto xform = detail::initXForm(mClock); | |||
| const auto hostTime = -xform.intercept; | |||
| // When creating the new timeline, make it continuous by finding | |||
| // the beat on the old session timeline corresponding to the | |||
| // current host time and mapping it to the new ghost time | |||
| // representation of the current host time. | |||
| const auto newTl = Timeline{mSessionTimeline.tempo, | |||
| mSessionTimeline.toBeats(mGhostXForm.hostToGhost(hostTime)), | |||
| xform.hostToGhost(hostTime)}; | |||
| updateSessionTiming(newTl, xform); | |||
| mSessions.resetSession({mNodeId, newTl, {xform, hostTime}}); | |||
| mPeers.resetPeers(); | |||
| } | |||
| struct SessionTimelineCallback | |||
| { | |||
| void operator()(SessionId id, Timeline timeline) | |||
| { | |||
| mController.handleTimelineFromSession(std::move(id), std::move(timeline)); | |||
| } | |||
| Controller& mController; | |||
| }; | |||
| struct SessionPeerCounter | |||
| { | |||
| SessionPeerCounter(Controller& controller, PeerCountCallback callback) | |||
| : mController(controller) | |||
| , mCallback(std::move(callback)) | |||
| , mSessionPeerCount(0) | |||
| { | |||
| } | |||
| void operator()() | |||
| { | |||
| const auto count = | |||
| mController.mPeers.uniqueSessionPeerCount(mController.mSessionId); | |||
| const auto oldCount = mSessionPeerCount.exchange(count); | |||
| if (oldCount != count) | |||
| { | |||
| if (count == 0) | |||
| { | |||
| // When the count goes down to zero, completely reset the | |||
| // state, effectively founding a new session | |||
| mController.resetState(); | |||
| } | |||
| mCallback(count); | |||
| } | |||
| } | |||
| Controller& mController; | |||
| PeerCountCallback mCallback; | |||
| std::atomic<std::size_t> mSessionPeerCount; | |||
| }; | |||
| struct MeasurePeer | |||
| { | |||
| template <typename Peer, typename Handler> | |||
| void operator()(Peer peer, Handler handler) | |||
| { | |||
| using It = typename Discovery::ServicePeerGateways::GatewayMap::iterator; | |||
| using ValueType = typename Discovery::ServicePeerGateways::GatewayMap::value_type; | |||
| mController.mDiscovery.withGatewaysAsync([peer, handler](It begin, const It end) { | |||
| const auto addr = peer.second; | |||
| const auto it = std::find_if( | |||
| begin, end, [&addr](const ValueType& vt) { return vt.first == addr; }); | |||
| if (it != end) | |||
| { | |||
| it->second->measurePeer(std::move(peer.first), std::move(handler)); | |||
| } | |||
| else | |||
| { | |||
| // invoke the handler with an empty result if we couldn't | |||
| // find the peer's gateway | |||
| handler(GhostXForm{}); | |||
| } | |||
| }); | |||
| } | |||
| Controller& mController; | |||
| }; | |||
| struct JoinSessionCallback | |||
| { | |||
| void operator()(Session session) | |||
| { | |||
| mController.joinSession(std::move(session)); | |||
| } | |||
| Controller& mController; | |||
| }; | |||
| using IoType = typename util::Injected<IoContext>::type; | |||
| using ControllerPeers = | |||
| Peers<IoType&, std::reference_wrapper<SessionPeerCounter>, SessionTimelineCallback>; | |||
| using ControllerGateway = | |||
| Gateway<typename ControllerPeers::GatewayObserver, Clock, IoType&>; | |||
| using GatewayPtr = std::shared_ptr<ControllerGateway>; | |||
| struct GatewayFactory | |||
| { | |||
| GatewayPtr operator()(std::pair<NodeState, GhostXForm> state, | |||
| util::Injected<IoType&> io, | |||
| const asio::ip::address& addr) | |||
| { | |||
| if (addr.is_v4()) | |||
| { | |||
| return GatewayPtr{new ControllerGateway{std::move(io), addr.to_v4(), | |||
| util::injectVal(makeGatewayObserver(mController.mPeers, addr)), | |||
| std::move(state.first), std::move(state.second), mController.mClock}}; | |||
| } | |||
| else | |||
| { | |||
| throw std::runtime_error("Could not create peer gateway on non-ipV4 address"); | |||
| } | |||
| } | |||
| Controller& mController; | |||
| }; | |||
| struct UdpSendExceptionHandler | |||
| { | |||
| using Exception = discovery::UdpSendException; | |||
| void operator()(const Exception& exception) | |||
| { | |||
| mController.mDiscovery.repairGateway(exception.interfaceAddr); | |||
| } | |||
| Controller& mController; | |||
| }; | |||
| TempoCallback mTempoCallback; | |||
| Clock mClock; | |||
| NodeId mNodeId; | |||
| SessionId mSessionId; | |||
| // Mutex that controls access to mGhostXForm and mSessionTimeline | |||
| mutable std::mutex mSessionTimingGuard; | |||
| GhostXForm mGhostXForm; | |||
| Timeline mSessionTimeline; | |||
| mutable std::mutex mClientTimelineGuard; | |||
| Timeline mClientTimeline; | |||
| mutable Timeline mRtClientTimeline; | |||
| std::chrono::microseconds mRtClientTimelineTimestamp; | |||
| SessionPeerCounter mSessionPeerCounter; | |||
| std::atomic<bool> mEnabled; | |||
| util::Injected<IoContext> mIo; | |||
| // A realtime facade over the provided IoContext. This should only | |||
| // be used by realtime code, non-realtime code should use mIo. | |||
| typename IoType::template RealTimeContext<IoType&> mRealtimeIo; | |||
| ControllerPeers mPeers; | |||
| using ControllerSessions = Sessions<ControllerPeers&, | |||
| MeasurePeer, | |||
| JoinSessionCallback, | |||
| typename util::Injected<IoContext>::type&, | |||
| Clock>; | |||
| ControllerSessions mSessions; | |||
| using Discovery = | |||
| discovery::Service<std::pair<NodeState, GhostXForm>, GatewayFactory, IoContext>; | |||
| Discovery mDiscovery; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,94 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/PeerGateway.hpp> | |||
| #include <ableton/link/MeasurementService.hpp> | |||
| #include <ableton/link/PeerState.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| template <typename PeerObserver, typename Clock, typename IoContext> | |||
| class Gateway | |||
| { | |||
| public: | |||
| Gateway(util::Injected<IoContext> io, | |||
| asio::ip::address_v4 addr, | |||
| util::Injected<PeerObserver> observer, | |||
| NodeState nodeState, | |||
| GhostXForm ghostXForm, | |||
| Clock clock) | |||
| // TODO: Measurement should have an IoContext injected | |||
| : mIo(std::move(io)), | |||
| mMeasurement(addr, | |||
| nodeState.sessionId, | |||
| std::move(ghostXForm), | |||
| std::move(clock), | |||
| util::injectVal(channel(mIo->log(), "gateway@" + addr.to_string()))), | |||
| mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo), | |||
| std::move(addr), | |||
| std::move(observer), | |||
| PeerState{std::move(nodeState), mMeasurement.endpoint()})) | |||
| { | |||
| } | |||
| Gateway(const Gateway& rhs) = delete; | |||
| Gateway& operator=(const Gateway& rhs) = delete; | |||
| Gateway(Gateway&& rhs) | |||
| : mIo(std::move(rhs.mIo)) | |||
| , mMeasurement(std::move(rhs.mMeasurement)) | |||
| , mPeerGateway(std::move(rhs.mPeerGateway)) | |||
| { | |||
| } | |||
| Gateway& operator=(Gateway&& rhs) | |||
| { | |||
| mIo = std::move(rhs.mIo); | |||
| mMeasurement = std::move(rhs.mMeasurement); | |||
| mPeerGateway = std::move(rhs.mPeerGateway); | |||
| return *this; | |||
| } | |||
| void updateNodeState(std::pair<NodeState, GhostXForm> state) | |||
| { | |||
| mMeasurement.updateNodeState(state.first.sessionId, state.second); | |||
| mPeerGateway.updateState(PeerState{std::move(state.first), mMeasurement.endpoint()}); | |||
| } | |||
| template <typename Handler> | |||
| void measurePeer(const PeerState& peer, Handler handler) | |||
| { | |||
| mMeasurement.measurePeer(peer, std::move(handler)); | |||
| } | |||
| private: | |||
| util::Injected<IoContext> mIo; | |||
| MeasurementService<Clock, typename util::Injected<IoContext>::type::Log> mMeasurement; | |||
| discovery:: | |||
| IpV4Gateway<PeerObserver, PeerState, typename util::Injected<IoContext>::type&> | |||
| mPeerGateway; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,59 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <chrono> | |||
| #include <cmath> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| using std::chrono::microseconds; | |||
| struct GhostXForm | |||
| { | |||
| microseconds hostToGhost(const microseconds hostTime) const | |||
| { | |||
| return microseconds{llround(slope * hostTime.count())} + intercept; | |||
| } | |||
| microseconds ghostToHost(const microseconds ghostTime) const | |||
| { | |||
| return microseconds{llround((ghostTime - intercept).count() / slope)}; | |||
| } | |||
| friend bool operator==(const GhostXForm lhs, const GhostXForm rhs) | |||
| { | |||
| return lhs.slope == rhs.slope && lhs.intercept == rhs.intercept; | |||
| } | |||
| friend bool operator!=(const GhostXForm lhs, const GhostXForm rhs) | |||
| { | |||
| return !(lhs == rhs); | |||
| } | |||
| double slope; | |||
| microseconds intercept; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,82 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/LinearRegression.hpp> | |||
| #include <chrono> | |||
| #include <vector> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| template <class T> | |||
| class HostTimeFilter | |||
| { | |||
| static const std::size_t kNumPoints = 512; | |||
| using Points = std::vector<std::pair<double, double>>; | |||
| using PointIt = typename Points::iterator; | |||
| public: | |||
| HostTimeFilter() | |||
| : mIndex(0) | |||
| { | |||
| mPoints.reserve(kNumPoints); | |||
| } | |||
| ~HostTimeFilter() = default; | |||
| void reset() | |||
| { | |||
| mIndex = 0; | |||
| mPoints.clear(); | |||
| } | |||
| std::chrono::microseconds sampleTimeToHostTime(const double sampleTime) | |||
| { | |||
| const auto micros = static_cast<double>(mHostTimeSampler.micros().count()); | |||
| const auto point = std::make_pair(sampleTime, micros); | |||
| if (mPoints.size() < kNumPoints) | |||
| { | |||
| mPoints.push_back(point); | |||
| } | |||
| else | |||
| { | |||
| mPoints[mIndex] = point; | |||
| } | |||
| mIndex = (mIndex + 1) % kNumPoints; | |||
| const auto result = linearRegression(mPoints.begin(), mPoints.end()); | |||
| const auto hostTime = (result.first * sampleTime) + result.second; | |||
| return std::chrono::microseconds(llround(hostTime)); | |||
| } | |||
| private: | |||
| std::size_t mIndex; | |||
| Points mPoints; | |||
| T mHostTimeSampler; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,160 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <array> | |||
| #include <cfloat> | |||
| #include <cmath> | |||
| #include <limits> | |||
| #if LINK_PLATFORM_WINDOWS | |||
| // Windows.h (or more specifically, minwindef.h) define the max(a, b) macro | |||
| // which conflicts with the symbol provided by std::numeric_limits. | |||
| #ifdef max | |||
| #undef max | |||
| #endif | |||
| #endif | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| template <std::size_t n> | |||
| struct Kalman | |||
| { | |||
| Kalman() | |||
| : mValue(0) | |||
| , mGain(0) | |||
| , mVVariance(1) | |||
| , mWVariance(1) | |||
| , mCoVariance(1) | |||
| , mVarianceLength(n) | |||
| , mCounter(mVarianceLength) | |||
| { | |||
| } | |||
| double getValue() | |||
| { | |||
| return mValue; | |||
| } | |||
| double calculateVVariance() | |||
| { | |||
| auto vVar = 0.; | |||
| auto meanOfDiffs = 0.; | |||
| for (size_t k = 0; k < (mVarianceLength); k++) | |||
| { | |||
| meanOfDiffs += (mMeasuredValues[k] - mFilterValues[k]); | |||
| } | |||
| meanOfDiffs /= (mVarianceLength); | |||
| for (size_t i = 0; i < (mVarianceLength); i++) | |||
| { | |||
| vVar += (pow(mMeasuredValues[i] - mFilterValues[i] - meanOfDiffs, 2.0)); | |||
| } | |||
| vVar /= (mVarianceLength - 1); | |||
| return vVar; | |||
| } | |||
| double calculateWVariance() | |||
| { | |||
| auto wVar = 0.; | |||
| auto meanOfDiffs = 0.; | |||
| for (size_t k = 0; k < (mVarianceLength); k++) | |||
| { | |||
| meanOfDiffs += (mFilterValues[(mCounter - k - 1) % mVarianceLength] | |||
| - mFilterValues[(mCounter - k - 2) % mVarianceLength]); | |||
| } | |||
| meanOfDiffs /= (mVarianceLength); | |||
| for (size_t i = 0; i < (mVarianceLength); i++) | |||
| { | |||
| wVar += (pow(mFilterValues[(mCounter - i - 1) % mVarianceLength] | |||
| - mFilterValues[(mCounter - i - 2) % mVarianceLength] - meanOfDiffs, | |||
| 2.0)); | |||
| } | |||
| wVar /= (mVarianceLength - 1); | |||
| return wVar; | |||
| } | |||
| void iterate(const double value) | |||
| { | |||
| const std::size_t currentIndex = mCounter % mVarianceLength; | |||
| mMeasuredValues[currentIndex] = value; | |||
| if (mCounter < (mVarianceLength + mVarianceLength)) | |||
| { | |||
| if (mCounter == mVarianceLength) | |||
| { | |||
| mValue = value; | |||
| } | |||
| else | |||
| { | |||
| mValue = (mValue + value) / 2; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // prediction equations | |||
| const double prevFilterValue = mFilterValues[(mCounter - 1) % mVarianceLength]; | |||
| mFilterValues[currentIndex] = prevFilterValue; | |||
| mWVariance = calculateWVariance(); | |||
| const double coVarianceEstimation = mCoVariance + mWVariance; | |||
| // update equations | |||
| mVVariance = calculateVVariance(); | |||
| if ((coVarianceEstimation + mVVariance) != 0) | |||
| { | |||
| mGain = coVarianceEstimation / (coVarianceEstimation + mVVariance); | |||
| } | |||
| else | |||
| { | |||
| mGain = std::numeric_limits<double>::max(); | |||
| } | |||
| mValue = prevFilterValue + mGain * (value - prevFilterValue); | |||
| mCoVariance = (1 - mGain) * coVarianceEstimation; | |||
| } | |||
| mFilterValues[currentIndex] = mValue; | |||
| ++mCounter; | |||
| } | |||
| double mValue; | |||
| double mGain; | |||
| double mVVariance; | |||
| double mWVariance; | |||
| double mCoVariance; | |||
| size_t mVarianceLength; | |||
| size_t mCounter; | |||
| std::array<double, n> mFilterValues; | |||
| std::array<double, n> mMeasuredValues; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,64 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <cfloat> | |||
| #include <cmath> | |||
| #include <numeric> | |||
| #include <utility> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| template <typename It> | |||
| std::pair<double, double> linearRegression(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| using Point = pair<double, double>; | |||
| const double numPoints = static_cast<double>(distance(begin, end)); | |||
| const double meanX = accumulate(begin, end, 0.0, [](double a, Point b) { | |||
| return a + b.first; | |||
| }) / numPoints; | |||
| const double productXX = accumulate(begin, end, 0.0, | |||
| [&meanX](double a, Point b) { return a + pow(b.first - meanX, 2.0); }); | |||
| const double meanY = accumulate(begin, end, 0.0, [](double a, Point b) { | |||
| return a + b.second; | |||
| }) / numPoints; | |||
| const double productXY = | |||
| inner_product(begin, end, begin, 0.0, [](double a, double b) { return a + b; }, | |||
| [&meanX, &meanY]( | |||
| Point a, Point b) { return ((a.first - meanX) * (b.second - meanY)); }); | |||
| const double slope = productXX == 0.0 ? 0.0 : productXY / productXX; | |||
| const double intercept = meanY - (slope * meanX); | |||
| return make_pair(slope, intercept); | |||
| } | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,296 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/Payload.hpp> | |||
| #include <ableton/discovery/Socket.hpp> | |||
| #include <ableton/link/PayloadEntries.hpp> | |||
| #include <ableton/link/PeerState.hpp> | |||
| #include <ableton/link/SessionId.hpp> | |||
| #include <ableton/link/v1/Messages.hpp> | |||
| #include <ableton/platforms/asio/AsioService.hpp> | |||
| #include <ableton/util/Injected.hpp> | |||
| #include <chrono> | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| template <typename IoService, typename Clock, typename Socket, typename Log> | |||
| struct Measurement | |||
| { | |||
| using Point = std::pair<double, double>; | |||
| using Callback = std::function<void(std::vector<Point>)>; | |||
| using Micros = std::chrono::microseconds; | |||
| using Timer = typename IoService::Timer; | |||
| static const std::size_t kNumberDataPoints = 100; | |||
| static const std::size_t kNumberMeasurements = 5; | |||
| Measurement() = default; | |||
| Measurement(const PeerState& state, | |||
| Callback callback, | |||
| asio::ip::address_v4 address, | |||
| Clock clock, | |||
| util::Injected<Log> log) | |||
| : mpIo(new IoService{}) | |||
| , mpImpl(std::make_shared<Impl>(*mpIo, | |||
| std::move(state), | |||
| std::move(callback), | |||
| std::move(address), | |||
| std::move(clock), | |||
| std::move(log))) | |||
| { | |||
| mpImpl->listen(); | |||
| } | |||
| Measurement(Measurement&& rhs) | |||
| : mpIo(std::move(rhs.mpIo)) | |||
| , mpImpl(std::move(rhs.mpImpl)) | |||
| { | |||
| } | |||
| ~Measurement() | |||
| { | |||
| postImplDestruction(); | |||
| } | |||
| Measurement& operator=(Measurement&& rhs) | |||
| { | |||
| postImplDestruction(); | |||
| mpIo = std::move(rhs.mpIo); | |||
| mpImpl = std::move(rhs.mpImpl); | |||
| return *this; | |||
| } | |||
| void postImplDestruction() | |||
| { | |||
| // Post destruction of the impl object into the io thread if valid | |||
| if (mpIo) | |||
| { | |||
| mpIo->post(ImplDeleter{*this}); | |||
| } | |||
| } | |||
| struct Impl : std::enable_shared_from_this<Impl> | |||
| { | |||
| Impl(IoService& io, | |||
| const PeerState& state, | |||
| Callback callback, | |||
| asio::ip::address_v4 address, | |||
| Clock clock, | |||
| util::Injected<Log> log) | |||
| : mpSocket(std::make_shared<Socket>(io)) | |||
| , mSessionId(state.nodeState.sessionId) | |||
| , mEndpoint(state.endpoint) | |||
| , mCallback(std::move(callback)) | |||
| , mClock(std::move(clock)) | |||
| , mTimer(util::injectVal(io.makeTimer())) | |||
| , mMeasurementsStarted(0) | |||
| , mLog(std::move(log)) | |||
| , mSuccess(false) | |||
| { | |||
| configureUnicastSocket(*mpSocket, address); | |||
| const auto ht = HostTime{mClock.micros()}; | |||
| sendPing(mEndpoint, discovery::makePayload(ht)); | |||
| resetTimer(); | |||
| } | |||
| void resetTimer() | |||
| { | |||
| mTimer->cancel(); | |||
| mTimer->expires_from_now(std::chrono::milliseconds(50)); | |||
| mTimer->async_wait([this](const typename Timer::ErrorCode e) { | |||
| if (!e) | |||
| { | |||
| if (mMeasurementsStarted < kNumberMeasurements) | |||
| { | |||
| const auto ht = HostTime{mClock.micros()}; | |||
| sendPing(mEndpoint, discovery::makePayload(ht)); | |||
| ++mMeasurementsStarted; | |||
| resetTimer(); | |||
| } | |||
| else | |||
| { | |||
| fail(); | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| void listen() | |||
| { | |||
| mpSocket->receive(util::makeAsyncSafe(this->shared_from_this())); | |||
| } | |||
| // Operator to handle incoming messages on the interface | |||
| template <typename It> | |||
| void operator()( | |||
| const asio::ip::udp::endpoint& from, const It messageBegin, const It messageEnd) | |||
| { | |||
| using namespace std; | |||
| const auto result = v1::parseMessageHeader(messageBegin, messageEnd); | |||
| const auto& header = result.first; | |||
| const auto payloadBegin = result.second; | |||
| if (header.messageType == v1::kPong) | |||
| { | |||
| debug(*mLog) << "Received Pong message from " << from; | |||
| // parse for all entries | |||
| SessionId sessionId{}; | |||
| std::chrono::microseconds ghostTime{0}; | |||
| std::chrono::microseconds prevGHostTime{0}; | |||
| std::chrono::microseconds prevHostTime{0}; | |||
| try | |||
| { | |||
| discovery::parsePayload<SessionMembership, GHostTime, PrevGHostTime, HostTime>( | |||
| payloadBegin, messageEnd, | |||
| [&sessionId](const SessionMembership& sms) { sessionId = sms.sessionId; }, | |||
| [&ghostTime](GHostTime gt) { ghostTime = std::move(gt.time); }, | |||
| [&prevGHostTime](PrevGHostTime gt) { prevGHostTime = std::move(gt.time); }, | |||
| [&prevHostTime](HostTime ht) { prevHostTime = std::move(ht.time); }); | |||
| } | |||
| catch (const std::runtime_error& err) | |||
| { | |||
| warning(*mLog) << "Failed parsing payload, caught exception: " << err.what(); | |||
| listen(); | |||
| return; | |||
| } | |||
| if (mSessionId == sessionId) | |||
| { | |||
| const auto hostTime = mClock.micros(); | |||
| const auto payload = | |||
| discovery::makePayload(HostTime{hostTime}, PrevGHostTime{ghostTime}); | |||
| sendPing(from, payload); | |||
| listen(); | |||
| if (prevGHostTime != Micros{0}) | |||
| { | |||
| mData.push_back( | |||
| std::make_pair(static_cast<double>((hostTime + prevHostTime).count()) * 0.5, | |||
| static_cast<double>(ghostTime.count()))); | |||
| mData.push_back(std::make_pair(static_cast<double>(prevHostTime.count()), | |||
| static_cast<double>((ghostTime + prevGHostTime).count()) * 0.5)); | |||
| } | |||
| if (mData.size() > kNumberDataPoints) | |||
| { | |||
| finish(); | |||
| } | |||
| else | |||
| { | |||
| resetTimer(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| fail(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| debug(*mLog) << "Received invalid message from " << from; | |||
| listen(); | |||
| } | |||
| } | |||
| template <typename Payload> | |||
| void sendPing(asio::ip::udp::endpoint to, const Payload& payload) | |||
| { | |||
| v1::MessageBuffer buffer; | |||
| const auto msgBegin = std::begin(buffer); | |||
| const auto msgEnd = v1::pingMessage(payload, msgBegin); | |||
| const auto numBytes = static_cast<size_t>(std::distance(msgBegin, msgEnd)); | |||
| try | |||
| { | |||
| mpSocket->send(buffer.data(), numBytes, to); | |||
| } | |||
| catch (const std::runtime_error& err) | |||
| { | |||
| info(*mLog) << "Failed to send Ping to " << to.address().to_string() << ": " | |||
| << err.what(); | |||
| } | |||
| } | |||
| void finish() | |||
| { | |||
| mTimer->cancel(); | |||
| mCallback(std::move(mData)); | |||
| mData = {}; | |||
| mSuccess = true; | |||
| debug(*mLog) << "Measuring " << mEndpoint << " done."; | |||
| } | |||
| void fail() | |||
| { | |||
| mCallback(std::vector<Point>{}); | |||
| mData = {}; | |||
| debug(*mLog) << "Measuring " << mEndpoint << " failed."; | |||
| } | |||
| std::shared_ptr<Socket> mpSocket; | |||
| SessionId mSessionId; | |||
| asio::ip::udp::endpoint mEndpoint; | |||
| std::vector<std::pair<double, double>> mData; | |||
| Callback mCallback; | |||
| Clock mClock; | |||
| util::Injected<typename IoService::Timer> mTimer; | |||
| std::size_t mMeasurementsStarted; | |||
| util::Injected<Log> mLog; | |||
| bool mSuccess; | |||
| }; | |||
| struct ImplDeleter | |||
| { | |||
| ImplDeleter(Measurement& measurement) | |||
| : mpImpl(std::move(measurement.mpImpl)) | |||
| { | |||
| } | |||
| void operator()() | |||
| { | |||
| // Notify callback that the measurement has failed if it did | |||
| // not succeed before destruction | |||
| if (!mpImpl->mSuccess) | |||
| { | |||
| mpImpl->fail(); | |||
| } | |||
| mpImpl.reset(); | |||
| } | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| std::unique_ptr<IoService> mpIo; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,70 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/NetworkByteStreamSerializable.hpp> | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| struct MeasurementEndpointV4 | |||
| { | |||
| enum | |||
| { | |||
| key = 'mep4' | |||
| }; | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const MeasurementEndpointV4 mep) | |||
| { | |||
| return discovery::sizeInByteStream( | |||
| static_cast<std::uint32_t>(mep.ep.address().to_v4().to_ulong())) | |||
| + discovery::sizeInByteStream(mep.ep.port()); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const MeasurementEndpointV4 mep, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(mep.ep.port(), | |||
| discovery::toNetworkByteStream( | |||
| static_cast<std::uint32_t>(mep.ep.address().to_v4().to_ulong()), std::move(out))); | |||
| } | |||
| template <typename It> | |||
| static std::pair<MeasurementEndpointV4, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto addrRes = | |||
| discovery::Deserialize<std::uint32_t>::fromNetworkByteStream(move(begin), end); | |||
| auto portRes = discovery::Deserialize<std::uint16_t>::fromNetworkByteStream( | |||
| move(addrRes.second), end); | |||
| return make_pair(MeasurementEndpointV4{{asio::ip::address_v4{move(addrRes.first)}, | |||
| move(portRes.first)}}, | |||
| move(portRes.second)); | |||
| } | |||
| asio::ip::udp::endpoint ep; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,186 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/Socket.hpp> | |||
| #include <ableton/link/GhostXForm.hpp> | |||
| #include <ableton/link/Kalman.hpp> | |||
| #include <ableton/link/LinearRegression.hpp> | |||
| #include <ableton/link/Measurement.hpp> | |||
| #include <ableton/link/PeerState.hpp> | |||
| #include <ableton/link/PingResponder.hpp> | |||
| #include <ableton/link/SessionId.hpp> | |||
| #include <ableton/link/v1/Messages.hpp> | |||
| #include <ableton/platforms/asio/AsioService.hpp> | |||
| #include <ableton/util/Log.hpp> | |||
| #include <map> | |||
| #include <memory> | |||
| #include <thread> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| template <typename Clock, typename Log> | |||
| class MeasurementService | |||
| { | |||
| public: | |||
| using Point = std::pair<double, double>; | |||
| using MeasurementInstance = Measurement<platforms::asio::AsioService, | |||
| Clock, | |||
| discovery::Socket<v1::kMaxMessageSize>, | |||
| Log>; | |||
| using MeasurementServicePingResponder = PingResponder<platforms::asio::AsioService&, | |||
| Clock, | |||
| discovery::Socket<v1::kMaxMessageSize>, | |||
| Log>; | |||
| static const std::size_t kNumberThreads = 1; | |||
| MeasurementService(asio::ip::address_v4 address, | |||
| SessionId sessionId, | |||
| GhostXForm ghostXForm, | |||
| Clock clock, | |||
| util::Injected<Log> log) | |||
| : mClock(std::move(clock)) | |||
| , mLog(std::move(log)) | |||
| , mPingResponder(std::move(address), | |||
| std::move(sessionId), | |||
| std::move(ghostXForm), | |||
| util::injectRef(mIo), | |||
| mClock, | |||
| mLog) | |||
| { | |||
| } | |||
| MeasurementService(const MeasurementService&) = delete; | |||
| MeasurementService(MeasurementService&&) = delete; | |||
| ~MeasurementService() | |||
| { | |||
| // Clear the measurement map in the io service so that whatever | |||
| // cleanup code executes in response to the destruction of the | |||
| // measurement objects still have access to the io service | |||
| mIo.post([this] { mMeasurementMap.clear(); }); | |||
| } | |||
| void updateNodeState(const SessionId& sessionId, const GhostXForm& xform) | |||
| { | |||
| mPingResponder.updateNodeState(sessionId, xform); | |||
| } | |||
| asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return mPingResponder.endpoint(); | |||
| } | |||
| // Measure the peer and invoke the handler with a GhostXForm | |||
| template <typename Handler> | |||
| void measurePeer(const PeerState& state, const Handler handler) | |||
| { | |||
| using namespace std; | |||
| mIo.post([this, state, handler] { | |||
| const auto nodeId = state.nodeState.nodeId; | |||
| auto addr = mPingResponder.endpoint().address().to_v4(); | |||
| auto callback = CompletionCallback<Handler>{*this, nodeId, handler}; | |||
| try | |||
| { | |||
| mMeasurementMap[nodeId] = | |||
| MeasurementInstance{state, move(callback), move(addr), mClock, mLog}; | |||
| } | |||
| catch (const runtime_error& err) | |||
| { | |||
| info(*mLog) << "Failed to measure. Reason: " << err.what(); | |||
| handler(GhostXForm{}); | |||
| } | |||
| }); | |||
| } | |||
| static GhostXForm filter( | |||
| std::vector<Point>::const_iterator begin, std::vector<Point>::const_iterator end) | |||
| { | |||
| using namespace std; | |||
| using std::chrono::microseconds; | |||
| Kalman<5> kalman; | |||
| for (auto it = begin; it != end; ++it) | |||
| { | |||
| kalman.iterate(it->second - it->first); | |||
| } | |||
| return GhostXForm{1, microseconds(llround(kalman.getValue()))}; | |||
| } | |||
| private: | |||
| template <typename Handler> | |||
| struct CompletionCallback | |||
| { | |||
| void operator()(const std::vector<Point> data) | |||
| { | |||
| using namespace std; | |||
| using std::chrono::microseconds; | |||
| // Post this to the measurement service's io service so that we | |||
| // don't delete the measurement object in its stack. Capture all | |||
| // needed data separately from this, since this object may be | |||
| // gone by the time the block gets executed. | |||
| auto nodeId = mNodeId; | |||
| auto handler = mHandler; | |||
| auto& measurementMap = mService.mMeasurementMap; | |||
| mService.mIo.post([nodeId, handler, &measurementMap, data] { | |||
| const auto it = measurementMap.find(nodeId); | |||
| if (it != measurementMap.end()) | |||
| { | |||
| if (data.empty()) | |||
| { | |||
| handler(GhostXForm{}); | |||
| } | |||
| else | |||
| { | |||
| handler(MeasurementService::filter(begin(data), end(data))); | |||
| } | |||
| measurementMap.erase(it); | |||
| } | |||
| }); | |||
| } | |||
| MeasurementService& mService; | |||
| NodeId mNodeId; | |||
| Handler mHandler; | |||
| }; | |||
| // Make sure the measurement map outlives the io service so that the rest of | |||
| // the members are guaranteed to be valid when any final handlers | |||
| // are begin run. | |||
| using MeasurementMap = std::map<NodeId, MeasurementInstance>; | |||
| MeasurementMap mMeasurementMap; | |||
| Clock mClock; | |||
| util::Injected<Log> mLog; | |||
| platforms::asio::AsioService mIo; | |||
| MeasurementServicePingResponder mPingResponder; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,82 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/NetworkByteStreamSerializable.hpp> | |||
| #include <algorithm> | |||
| #include <array> | |||
| #include <cstdint> | |||
| #include <random> | |||
| #include <string> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| using NodeIdArray = std::array<std::uint8_t, 8>; | |||
| struct NodeId : NodeIdArray | |||
| { | |||
| NodeId() = default; | |||
| NodeId(NodeIdArray rhs) | |||
| : NodeIdArray(std::move(rhs)) | |||
| { | |||
| } | |||
| static NodeId random() | |||
| { | |||
| using namespace std; | |||
| random_device rd; | |||
| mt19937 gen(rd()); | |||
| // uint8_t not standardized for this type - use unsigned | |||
| uniform_int_distribution<unsigned> dist(33, 126); // printable ascii chars | |||
| NodeId nodeId; | |||
| generate( | |||
| nodeId.begin(), nodeId.end(), [&] { return static_cast<uint8_t>(dist(gen)); }); | |||
| return nodeId; | |||
| } | |||
| friend std::ostream& operator<<(std::ostream& stream, const NodeId& id) | |||
| { | |||
| return stream << std::string{id.cbegin(), id.cend()}; | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const NodeId& nodeId, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(nodeId, std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<NodeId, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto result = | |||
| discovery::Deserialize<NodeIdArray>::fromNetworkByteStream(move(begin), move(end)); | |||
| return make_pair(NodeId(move(result.first)), move(result.second)); | |||
| } | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,69 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/Payload.hpp> | |||
| #include <ableton/link/NodeId.hpp> | |||
| #include <ableton/link/SessionId.hpp> | |||
| #include <ableton/link/Timeline.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| struct NodeState | |||
| { | |||
| using Payload = decltype(discovery::makePayload(Timeline{}, SessionMembership{})); | |||
| NodeId ident() const | |||
| { | |||
| return nodeId; | |||
| } | |||
| friend bool operator==(const NodeState& lhs, const NodeState& rhs) | |||
| { | |||
| return std::tie(lhs.nodeId, lhs.sessionId, lhs.timeline) | |||
| == std::tie(rhs.nodeId, rhs.sessionId, rhs.timeline); | |||
| } | |||
| friend Payload toPayload(const NodeState& state) | |||
| { | |||
| return discovery::makePayload(state.timeline, SessionMembership{state.sessionId}); | |||
| } | |||
| template <typename It> | |||
| static NodeState fromPayload(NodeId id, It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto state = NodeState{move(id), {}, {}}; | |||
| discovery::parsePayload<Timeline, SessionMembership>(move(begin), move(end), | |||
| [&state](Timeline tl) { state.timeline = move(tl); }, | |||
| [&state](SessionMembership sm) { state.sessionId = move(sm.sessionId); }); | |||
| return state; | |||
| } | |||
| NodeId nodeId; | |||
| SessionId sessionId; | |||
| Timeline timeline; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,146 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/NetworkByteStreamSerializable.hpp> | |||
| #include <cmath> | |||
| #include <cstdint> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| struct HostTime | |||
| { | |||
| enum | |||
| { | |||
| key = '__ht' | |||
| }; | |||
| HostTime() = default; | |||
| HostTime(const std::chrono::microseconds tm) | |||
| : time(tm) | |||
| { | |||
| } | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const HostTime& sht) | |||
| { | |||
| return discovery::sizeInByteStream(std::move(sht.time)); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const HostTime& sht, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(std::move(sht.time), std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<HostTime, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto result = discovery::Deserialize<chrono::microseconds>::fromNetworkByteStream( | |||
| move(begin), move(end)); | |||
| return make_pair(HostTime{move(result.first)}, move(result.second)); | |||
| } | |||
| std::chrono::microseconds time; | |||
| }; | |||
| struct GHostTime : HostTime | |||
| { | |||
| enum | |||
| { | |||
| key = '__gt' | |||
| }; | |||
| GHostTime() = default; | |||
| GHostTime(const std::chrono::microseconds tm) | |||
| : time(tm) | |||
| { | |||
| } | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const GHostTime& dgt) | |||
| { | |||
| return discovery::sizeInByteStream(std::move(dgt.time)); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const GHostTime& dgt, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(std::move(dgt.time), std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<GHostTime, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto result = discovery::Deserialize<chrono::microseconds>::fromNetworkByteStream( | |||
| move(begin), move(end)); | |||
| return make_pair(GHostTime{move(result.first)}, move(result.second)); | |||
| } | |||
| std::chrono::microseconds time; | |||
| }; | |||
| struct PrevGHostTime | |||
| { | |||
| enum | |||
| { | |||
| key = '_pgt' | |||
| }; | |||
| PrevGHostTime() = default; | |||
| PrevGHostTime(const std::chrono::microseconds tm) | |||
| : time(tm) | |||
| { | |||
| } | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const PrevGHostTime& dgt) | |||
| { | |||
| return discovery::sizeInByteStream(std::move(dgt.time)); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const PrevGHostTime& pdgt, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(std::move(pdgt.time), std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<PrevGHostTime, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto result = discovery::Deserialize<chrono::microseconds>::fromNetworkByteStream( | |||
| move(begin), move(end)); | |||
| return make_pair(PrevGHostTime{move(result.first)}, move(result.second)); | |||
| } | |||
| std::chrono::microseconds time; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,83 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/Payload.hpp> | |||
| #include <ableton/link/MeasurementEndpointV4.hpp> | |||
| #include <ableton/link/NodeState.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| // A state type for peers. PeerState stores the normal NodeState plus | |||
| // additional information (the remote endpoint at which to find its | |||
| // ping/pong measurement server). | |||
| struct PeerState | |||
| { | |||
| using IdType = NodeId; | |||
| IdType ident() const | |||
| { | |||
| return nodeState.ident(); | |||
| } | |||
| SessionId sessionId() const | |||
| { | |||
| return nodeState.sessionId; | |||
| } | |||
| Timeline timeline() const | |||
| { | |||
| return nodeState.timeline; | |||
| } | |||
| friend bool operator==(const PeerState& lhs, const PeerState& rhs) | |||
| { | |||
| return lhs.nodeState == rhs.nodeState && lhs.endpoint == rhs.endpoint; | |||
| } | |||
| friend auto toPayload(const PeerState& state) | |||
| -> decltype(std::declval<NodeState::Payload>() | |||
| + discovery::makePayload(MeasurementEndpointV4{{}})) | |||
| { | |||
| return toPayload(state.nodeState) | |||
| + discovery::makePayload(MeasurementEndpointV4{state.endpoint}); | |||
| } | |||
| template <typename It> | |||
| static PeerState fromPayload(NodeId id, It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto peerState = PeerState{NodeState::fromPayload(move(id), begin, end), {}}; | |||
| discovery::parsePayload<MeasurementEndpointV4>(move(begin), move(end), | |||
| [&peerState](MeasurementEndpointV4 me4) { peerState.endpoint = move(me4.ep); }); | |||
| return peerState; | |||
| } | |||
| NodeState nodeState; | |||
| asio::ip::udp::endpoint endpoint; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,355 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/PeerState.hpp> | |||
| #include <ableton/util/Injected.hpp> | |||
| #include <cassert> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| // SessionMembershipCallback is invoked when any change to session | |||
| // membership occurs (when any peer joins or leaves a session) | |||
| // | |||
| // SessionTimelineCallback is invoked with a session id and a timeline | |||
| // whenever a new combination of these values is seen | |||
| template <typename IoContext, | |||
| typename SessionMembershipCallback, | |||
| typename SessionTimelineCallback> | |||
| class Peers | |||
| { | |||
| // non-movable private implementation type | |||
| struct Impl; | |||
| public: | |||
| using Peer = std::pair<PeerState, asio::ip::address>; | |||
| Peers(util::Injected<IoContext> io, | |||
| SessionMembershipCallback membership, | |||
| SessionTimelineCallback timeline) | |||
| : mpImpl( | |||
| std::make_shared<Impl>(std::move(io), std::move(membership), std::move(timeline))) | |||
| { | |||
| } | |||
| // The set of peers for a given session, ordered by (peerId, addr). | |||
| // The result will possibly contain multiple entries for the same | |||
| // peer if it is visible through multiple gateways. | |||
| std::vector<Peer> sessionPeers(const SessionId& sid) const | |||
| { | |||
| using namespace std; | |||
| vector<Peer> result; | |||
| auto& peerVec = mpImpl->mPeers; | |||
| copy_if(begin(peerVec), end(peerVec), back_inserter(result), SessionMemberPred{sid}); | |||
| return result; | |||
| } | |||
| // Number of individual for a given session. | |||
| std::size_t uniqueSessionPeerCount(const SessionId& sid) const | |||
| { | |||
| using namespace std; | |||
| auto peerVec = sessionPeers(sid); | |||
| auto last = unique(begin(peerVec), end(peerVec), | |||
| [](const Peer& a, const Peer& b) { return a.first.ident() == b.first.ident(); }); | |||
| return static_cast<size_t>(distance(begin(peerVec), last)); | |||
| } | |||
| void setSessionTimeline(const SessionId& sid, const Timeline& tl) | |||
| { | |||
| // Set the cached timeline for all peers to a new client-specified | |||
| // timeline. When we make a timeline change, we do so | |||
| // optimistically and clients assume that all peers in a session | |||
| // have adopted the newly specified timeline. We must represent | |||
| // this in our cache or else we risk failing to notify about a | |||
| // higher-priority peer timeline that was already seen. | |||
| for (auto& peer : mpImpl->mPeers) | |||
| { | |||
| if (peer.first.sessionId() == sid) | |||
| { | |||
| peer.first.nodeState.timeline = tl; | |||
| } | |||
| } | |||
| } | |||
| // Purge all cached peers that are members of the given session | |||
| void forgetSession(const SessionId& sid) | |||
| { | |||
| using namespace std; | |||
| auto& peerVec = mpImpl->mPeers; | |||
| peerVec.erase( | |||
| remove_if(begin(peerVec), end(peerVec), SessionMemberPred{sid}), end(peerVec)); | |||
| } | |||
| void resetPeers() | |||
| { | |||
| mpImpl->mPeers.clear(); | |||
| } | |||
| // Observer type that monitors peer discovery on a particular | |||
| // gateway and relays the information to a Peers instance. | |||
| // Models the PeerObserver concept from the discovery module. | |||
| struct GatewayObserver | |||
| { | |||
| using GatewayObserverNodeState = PeerState; | |||
| using GatewayObserverNodeId = NodeId; | |||
| GatewayObserver(std::shared_ptr<Impl> pImpl, asio::ip::address addr) | |||
| : mpImpl(std::move(pImpl)) | |||
| , mAddr(std::move(addr)) | |||
| { | |||
| } | |||
| GatewayObserver(const GatewayObserver&) = delete; | |||
| GatewayObserver(GatewayObserver&& rhs) | |||
| : mpImpl(std::move(rhs.mpImpl)) | |||
| , mAddr(std::move(rhs.mAddr)) | |||
| { | |||
| } | |||
| ~GatewayObserver() | |||
| { | |||
| // Check to handle the moved from case | |||
| if (mpImpl) | |||
| { | |||
| auto& io = *mpImpl->mIo; | |||
| io.async(Deleter{*this}); | |||
| } | |||
| } | |||
| // model the PeerObserver concept from discovery | |||
| friend void sawPeer(GatewayObserver& observer, const PeerState& state) | |||
| { | |||
| auto pImpl = observer.mpImpl; | |||
| auto addr = observer.mAddr; | |||
| assert(pImpl); | |||
| pImpl->mIo->async([pImpl, addr, state] { | |||
| pImpl->sawPeerOnGateway(std::move(state), std::move(addr)); | |||
| }); | |||
| } | |||
| friend void peerLeft(GatewayObserver& observer, const NodeId& id) | |||
| { | |||
| auto pImpl = observer.mpImpl; | |||
| auto addr = observer.mAddr; | |||
| pImpl->mIo->async( | |||
| [pImpl, addr, id] { pImpl->peerLeftGateway(std::move(id), std::move(addr)); }); | |||
| } | |||
| friend void peerTimedOut(GatewayObserver& observer, const NodeId& id) | |||
| { | |||
| auto pImpl = observer.mpImpl; | |||
| auto addr = observer.mAddr; | |||
| pImpl->mIo->async( | |||
| [pImpl, addr, id] { pImpl->peerLeftGateway(std::move(id), std::move(addr)); }); | |||
| } | |||
| struct Deleter | |||
| { | |||
| Deleter(GatewayObserver& observer) | |||
| : mpImpl(std::move(observer.mpImpl)) | |||
| , mAddr(std::move(observer.mAddr)) | |||
| { | |||
| } | |||
| void operator()() | |||
| { | |||
| mpImpl->gatewayClosed(mAddr); | |||
| } | |||
| std::shared_ptr<Impl> mpImpl; | |||
| asio::ip::address mAddr; | |||
| }; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| asio::ip::address mAddr; | |||
| }; | |||
| // Factory function for the gateway observer | |||
| friend GatewayObserver makeGatewayObserver(Peers& peers, asio::ip::address addr) | |||
| { | |||
| return GatewayObserver{peers.mpImpl, std::move(addr)}; | |||
| } | |||
| private: | |||
| struct Impl | |||
| { | |||
| Impl(util::Injected<IoContext> io, | |||
| SessionMembershipCallback membership, | |||
| SessionTimelineCallback timeline) | |||
| : mIo(std::move(io)) | |||
| , mSessionMembershipCallback(std::move(membership)) | |||
| , mSessionTimelineCallback(std::move(timeline)) | |||
| { | |||
| } | |||
| void sawPeerOnGateway(PeerState peerState, asio::ip::address gatewayAddr) | |||
| { | |||
| using namespace std; | |||
| const auto peerSession = peerState.sessionId(); | |||
| const auto peerTimeline = peerState.timeline(); | |||
| bool isNewSessionTimeline = false; | |||
| bool didSessionMembershipChange = false; | |||
| { | |||
| isNewSessionTimeline = !sessionTimelineExists(peerSession, peerTimeline); | |||
| auto peer = make_pair(move(peerState), move(gatewayAddr)); | |||
| const auto idRange = equal_range(begin(mPeers), end(mPeers), peer, PeerIdComp{}); | |||
| if (idRange.first == idRange.second) | |||
| { | |||
| // This peer is not currently known on any gateway | |||
| didSessionMembershipChange = true; | |||
| mPeers.insert(move(idRange.first), move(peer)); | |||
| } | |||
| else | |||
| { | |||
| // We've seen this peer before... does it have a new session? | |||
| didSessionMembershipChange = | |||
| all_of(idRange.first, idRange.second, [&peerSession](const Peer& test) { | |||
| return test.first.sessionId() != peerSession; | |||
| }); | |||
| // was it on this gateway? | |||
| const auto addrRange = | |||
| equal_range(idRange.first, idRange.second, peer, AddrComp{}); | |||
| if (addrRange.first == addrRange.second) | |||
| { | |||
| // First time on this gateway, add it | |||
| mPeers.insert(move(addrRange.first), move(peer)); | |||
| } | |||
| else | |||
| { | |||
| // We have an entry for this peer on this gateway, update it | |||
| *addrRange.first = move(peer); | |||
| } | |||
| } | |||
| } // end lock | |||
| // Invoke callbacks outside the critical section | |||
| if (isNewSessionTimeline) | |||
| { | |||
| mSessionTimelineCallback(peerSession, peerTimeline); | |||
| } | |||
| if (didSessionMembershipChange) | |||
| { | |||
| mSessionMembershipCallback(); | |||
| } | |||
| } | |||
| void peerLeftGateway(const NodeId& nodeId, const asio::ip::address& gatewayAddr) | |||
| { | |||
| using namespace std; | |||
| bool didSessionMembershipChange = false; | |||
| { | |||
| auto it = find_if(begin(mPeers), end(mPeers), [&](const Peer& peer) { | |||
| return peer.first.ident() == nodeId && peer.second == gatewayAddr; | |||
| }); | |||
| if (it != end(mPeers)) | |||
| { | |||
| mPeers.erase(move(it)); | |||
| didSessionMembershipChange = true; | |||
| } | |||
| } // end lock | |||
| if (didSessionMembershipChange) | |||
| { | |||
| mSessionMembershipCallback(); | |||
| } | |||
| } | |||
| void gatewayClosed(const asio::ip::address& gatewayAddr) | |||
| { | |||
| using namespace std; | |||
| { | |||
| mPeers.erase( | |||
| remove_if(begin(mPeers), end(mPeers), | |||
| [&gatewayAddr](const Peer& peer) { return peer.second == gatewayAddr; }), | |||
| end(mPeers)); | |||
| } // end lock | |||
| mSessionMembershipCallback(); | |||
| } | |||
| bool sessionTimelineExists(const SessionId& session, const Timeline& tl) | |||
| { | |||
| using namespace std; | |||
| return find_if(begin(mPeers), end(mPeers), [&](const Peer& peer) { | |||
| return peer.first.sessionId() == session && peer.first.timeline() == tl; | |||
| }) != end(mPeers); | |||
| } | |||
| struct PeerIdComp | |||
| { | |||
| bool operator()(const Peer& lhs, const Peer& rhs) const | |||
| { | |||
| return lhs.first.ident() < rhs.first.ident(); | |||
| } | |||
| }; | |||
| struct AddrComp | |||
| { | |||
| bool operator()(const Peer& lhs, const Peer& rhs) const | |||
| { | |||
| return lhs.second < rhs.second; | |||
| } | |||
| }; | |||
| util::Injected<IoContext> mIo; | |||
| SessionMembershipCallback mSessionMembershipCallback; | |||
| SessionTimelineCallback mSessionTimelineCallback; | |||
| std::vector<Peer> mPeers; // sorted by peerId, unique by (peerId, addr) | |||
| }; | |||
| struct SessionMemberPred | |||
| { | |||
| bool operator()(const Peer& peer) const | |||
| { | |||
| return peer.first.sessionId() == sid; | |||
| } | |||
| const SessionId& sid; | |||
| }; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| template <typename Io, | |||
| typename SessionMembershipCallback, | |||
| typename SessionTimelineCallback> | |||
| Peers<Io, SessionMembershipCallback, SessionTimelineCallback> makePeers( | |||
| util::Injected<Io> io, | |||
| SessionMembershipCallback membershipCallback, | |||
| SessionTimelineCallback timelineCallback) | |||
| { | |||
| return {std::move(io), std::move(membershipCallback), std::move(timelineCallback)}; | |||
| } | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,100 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/Beats.hpp> | |||
| #include <ableton/link/Timeline.hpp> | |||
| #include <chrono> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| // Returns a value in the range [0,quantum) corresponding to beats % | |||
| // quantum except that negative beat values are handled correctly. | |||
| // If the given quantum is zero, returns zero. | |||
| inline Beats phase(const Beats beats, const Beats quantum) | |||
| { | |||
| if (quantum == Beats{INT64_C(0)}) | |||
| { | |||
| return Beats{INT64_C(0)}; | |||
| } | |||
| else | |||
| { | |||
| // Handle negative beat values by doing the computation relative to an | |||
| // origin that is on the nearest quantum boundary less than -(abs(x)) | |||
| const auto quantumMicros = quantum.microBeats(); | |||
| const auto quantumBins = (llabs(beats.microBeats()) + quantumMicros) / quantumMicros; | |||
| const std::int64_t quantumBeats{quantumBins * quantumMicros}; | |||
| return (beats + Beats{quantumBeats}) % quantum; | |||
| } | |||
| } | |||
| // Return the least value greater than x that matches the phase of | |||
| // target with respect to the given quantum. If the given quantum | |||
| // quantum is 0, x is returned. | |||
| inline Beats nextPhaseMatch(const Beats x, const Beats target, const Beats quantum) | |||
| { | |||
| const auto desiredPhase = phase(target, quantum); | |||
| const auto xPhase = phase(x, quantum); | |||
| const auto phaseDiff = (desiredPhase - xPhase + quantum) % quantum; | |||
| return x + phaseDiff; | |||
| } | |||
| // Return the closest value to x that matches the phase of the target | |||
| // with respect to the given quantum. The result deviates from x by at | |||
| // most quantum/2, but may be less than x. | |||
| inline Beats closestPhaseMatch(const Beats x, const Beats target, const Beats quantum) | |||
| { | |||
| return nextPhaseMatch(x - Beats{0.5 * quantum.floating()}, target, quantum); | |||
| } | |||
| // Interprets the given timeline as encoding a quantum boundary at its | |||
| // origin. Given such a timeline, returns a phase-encoded beat value | |||
| // relative to the given quantum that corresponds to the given | |||
| // time. The phase of the resulting beat value can be calculated with | |||
| // phase(beats, quantum). The result will deviate by up to +- | |||
| // (quantum/2) beats compared to the result of tl.toBeats(time). | |||
| inline Beats toPhaseEncodedBeats( | |||
| const Timeline& tl, const std::chrono::microseconds time, const Beats quantum) | |||
| { | |||
| const auto beat = tl.toBeats(time); | |||
| return closestPhaseMatch(beat, beat - tl.beatOrigin, quantum); | |||
| } | |||
| // The inverse of toPhaseEncodedBeats. Given a phase encoded beat | |||
| // value from the given timeline and quantum, find the time value that | |||
| // it maps to. | |||
| inline std::chrono::microseconds fromPhaseEncodedBeats( | |||
| const Timeline& tl, const Beats beat, const Beats quantum) | |||
| { | |||
| const auto fromOrigin = beat - tl.beatOrigin; | |||
| const auto originOffset = fromOrigin - phase(fromOrigin, quantum); | |||
| // invert the phase calculation so that it always rounds up in the | |||
| // middle instead of down like closestPhaseMatch. Otherwise we'll | |||
| // end up rounding down twice when a value is at phase quantum/2. | |||
| const auto inversePhaseOffset = closestPhaseMatch( | |||
| quantum - phase(fromOrigin, quantum), quantum - phase(beat, quantum), quantum); | |||
| return tl.fromBeats(tl.beatOrigin + originOffset + quantum - inversePhaseOffset); | |||
| } | |||
| } // link | |||
| } // ableton | |||
| @@ -0,0 +1,185 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/GhostXForm.hpp> | |||
| #include <ableton/link/PayloadEntries.hpp> | |||
| #include <ableton/link/SessionId.hpp> | |||
| #include <ableton/link/v1/Messages.hpp> | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/Injected.hpp> | |||
| #include <ableton/util/SafeAsyncHandler.hpp> | |||
| #include <chrono> | |||
| #include <memory> | |||
| #include <thread> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| template <typename Io, typename Clock, typename Socket, typename Log> | |||
| class PingResponder | |||
| { | |||
| public: | |||
| PingResponder(asio::ip::address_v4 address, | |||
| SessionId sessionId, | |||
| GhostXForm ghostXForm, | |||
| util::Injected<Io> io, | |||
| Clock clock, | |||
| util::Injected<Log> log) | |||
| : mIo(std::move(io)) | |||
| , mpImpl(std::make_shared<Impl>(*mIo, | |||
| std::move(address), | |||
| std::move(sessionId), | |||
| std::move(ghostXForm), | |||
| std::move(clock), | |||
| std::move(log))) | |||
| { | |||
| mpImpl->listen(); | |||
| } | |||
| PingResponder(const PingResponder&) = delete; | |||
| PingResponder(PingResponder&&) = delete; | |||
| ~PingResponder() | |||
| { | |||
| // post the release of the impl object into the io service so that | |||
| // it happens in the same thread as its handlers | |||
| auto pImpl = mpImpl; | |||
| mIo->post([pImpl]() mutable { pImpl.reset(); }); | |||
| } | |||
| void updateNodeState(const SessionId& sessionId, const GhostXForm& xform) | |||
| { | |||
| auto pImpl = mpImpl; | |||
| mIo->post([pImpl, sessionId, xform] { | |||
| pImpl->mSessionId = std::move(sessionId); | |||
| pImpl->mGhostXForm = std::move(xform); | |||
| }); | |||
| } | |||
| asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return mpImpl->mSocket.endpoint(); | |||
| } | |||
| asio::ip::address address() const | |||
| { | |||
| return endpoint().address(); | |||
| } | |||
| Socket socket() const | |||
| { | |||
| return mpImpl->mSocket; | |||
| } | |||
| private: | |||
| struct Impl : std::enable_shared_from_this<Impl> | |||
| { | |||
| Impl(typename util::Injected<Io>::type& io, | |||
| asio::ip::address_v4 address, | |||
| SessionId sessionId, | |||
| GhostXForm ghostXForm, | |||
| Clock clock, | |||
| util::Injected<Log> log) | |||
| : mSessionId(std::move(sessionId)) | |||
| , mGhostXForm(std::move(ghostXForm)) | |||
| , mClock(std::move(clock)) | |||
| , mLog(std::move(log)) | |||
| , mSocket(io) | |||
| { | |||
| configureUnicastSocket(mSocket, address); | |||
| } | |||
| void listen() | |||
| { | |||
| mSocket.receive(util::makeAsyncSafe(this->shared_from_this())); | |||
| } | |||
| // Operator to handle incoming messages on the interface | |||
| template <typename It> | |||
| void operator()(const asio::ip::udp::endpoint& from, const It begin, const It end) | |||
| { | |||
| using namespace discovery; | |||
| // Decode Ping Message | |||
| const auto result = link::v1::parseMessageHeader(begin, end); | |||
| const auto& header = result.first; | |||
| const auto payloadBegin = result.second; | |||
| // Check Payload size | |||
| const auto payloadSize = static_cast<std::size_t>(std::distance(payloadBegin, end)); | |||
| const auto maxPayloadSize = | |||
| sizeInByteStream(makePayload(HostTime{}, PrevGHostTime{})); | |||
| if (header.messageType == v1::kPing && payloadSize <= maxPayloadSize) | |||
| { | |||
| debug(*mLog) << "Received ping message from " << from; | |||
| try | |||
| { | |||
| reply(std::move(payloadBegin), std::move(end), from); | |||
| } | |||
| catch (const std::runtime_error& err) | |||
| { | |||
| info(*mLog) << "Failed to send pong to " << from << ". Reason: " << err.what(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| info(*mLog) << "Received invalid Message from " << from << "."; | |||
| } | |||
| listen(); | |||
| } | |||
| template <typename It> | |||
| void reply(It begin, It end, const asio::ip::udp::endpoint& to) | |||
| { | |||
| using namespace discovery; | |||
| // Encode Pong Message | |||
| const auto id = SessionMembership{mSessionId}; | |||
| const auto currentGt = GHostTime{mGhostXForm.hostToGhost(mClock.micros())}; | |||
| const auto pongPayload = makePayload(id, currentGt); | |||
| v1::MessageBuffer pongBuffer; | |||
| const auto pongMsgBegin = std::begin(pongBuffer); | |||
| auto pongMsgEnd = v1::pongMessage(pongPayload, pongMsgBegin); | |||
| // Append ping payload to pong message. | |||
| pongMsgEnd = std::copy(begin, end, pongMsgEnd); | |||
| const auto numBytes = | |||
| static_cast<std::size_t>(std::distance(pongMsgBegin, pongMsgEnd)); | |||
| mSocket.send(pongBuffer.data(), numBytes, to); | |||
| } | |||
| SessionId mSessionId; | |||
| GhostXForm mGhostXForm; | |||
| Clock mClock; | |||
| util::Injected<Log> mLog; | |||
| Socket mSocket; | |||
| }; | |||
| util::Injected<Io> mIo; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,65 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/NodeId.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| // SessionIds occupy the same value space as NodeIds and are | |||
| // identified by their founding node. | |||
| using SessionId = NodeId; | |||
| // A payload entry indicating membership in a particular session | |||
| struct SessionMembership | |||
| { | |||
| enum | |||
| { | |||
| key = 'sess' | |||
| }; | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const SessionMembership& sm) | |||
| { | |||
| return discovery::sizeInByteStream(sm.sessionId); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const SessionMembership& sm, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(sm.sessionId, std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<SessionMembership, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| auto idRes = SessionId::fromNetworkByteStream(move(begin), move(end)); | |||
| return make_pair(SessionMembership{move(idRes.first)}, move(idRes.second)); | |||
| } | |||
| SessionId sessionId; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,303 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/GhostXForm.hpp> | |||
| #include <ableton/link/SessionId.hpp> | |||
| #include <ableton/link/Timeline.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| struct SessionMeasurement | |||
| { | |||
| GhostXForm xform; | |||
| std::chrono::microseconds timestamp; | |||
| }; | |||
| struct Session | |||
| { | |||
| SessionId sessionId; | |||
| Timeline timeline; | |||
| SessionMeasurement measurement; | |||
| }; | |||
| template <typename Peers, | |||
| typename MeasurePeer, | |||
| typename JoinSessionCallback, | |||
| typename IoContext, | |||
| typename Clock> | |||
| class Sessions | |||
| { | |||
| public: | |||
| using Timer = typename util::Injected<IoContext>::type::Timer; | |||
| Sessions(Session init, | |||
| util::Injected<Peers> peers, | |||
| MeasurePeer measure, | |||
| JoinSessionCallback join, | |||
| util::Injected<IoContext> io, | |||
| Clock clock) | |||
| : mPeers(std::move(peers)) | |||
| , mMeasure(std::move(measure)) | |||
| , mCallback(std::move(join)) | |||
| , mCurrent(std::move(init)) | |||
| , mIo(std::move(io)) | |||
| , mTimer(mIo->makeTimer()) | |||
| , mClock(std::move(clock)) | |||
| { | |||
| } | |||
| void resetSession(Session session) | |||
| { | |||
| mCurrent = std::move(session); | |||
| mOtherSessions.clear(); | |||
| } | |||
| void resetTimeline(Timeline timeline) | |||
| { | |||
| mCurrent.timeline = std::move(timeline); | |||
| } | |||
| // Consider the observed session/timeline pair and return a possibly | |||
| // new timeline that should be used going forward. | |||
| Timeline sawSessionTimeline(SessionId sid, Timeline timeline) | |||
| { | |||
| using namespace std; | |||
| if (sid == mCurrent.sessionId) | |||
| { | |||
| // matches our current session, update the timeline if necessary | |||
| updateTimeline(mCurrent, move(timeline)); | |||
| } | |||
| else | |||
| { | |||
| auto session = Session{move(sid), move(timeline), {}}; | |||
| const auto range = | |||
| equal_range(begin(mOtherSessions), end(mOtherSessions), session, SessionIdComp{}); | |||
| if (range.first == range.second) | |||
| { | |||
| // brand new session, insert it into our list of known | |||
| // sessions and launch a measurement | |||
| launchSessionMeasurement(session); | |||
| mOtherSessions.insert(range.first, move(session)); | |||
| } | |||
| else | |||
| { | |||
| // we've seen this session before, update its timeline if necessary | |||
| updateTimeline(*range.first, move(timeline)); | |||
| } | |||
| } | |||
| return mCurrent.timeline; | |||
| } | |||
| private: | |||
| void launchSessionMeasurement(Session& session) | |||
| { | |||
| using namespace std; | |||
| auto peers = mPeers->sessionPeers(session.sessionId); | |||
| if (!peers.empty()) | |||
| { | |||
| // first criteria: always prefer the founding peer | |||
| const auto it = find_if(begin(peers), end(peers), | |||
| [&session](const Peer& peer) { return session.sessionId == peer.first.ident(); }); | |||
| // TODO: second criteria should be degree. We don't have that | |||
| // represented yet so just use the first peer for now | |||
| auto peer = it == end(peers) ? peers.front() : *it; | |||
| // mark that a session is in progress by clearing out the | |||
| // session's timestamp | |||
| session.measurement.timestamp = {}; | |||
| mMeasure(move(peer), MeasurementResultsHandler{*this, session.sessionId}); | |||
| } | |||
| } | |||
| void handleSuccessfulMeasurement(const SessionId& id, GhostXForm xform) | |||
| { | |||
| using namespace std; | |||
| debug(mIo->log()) << "Session " << id << " measurement completed with result " | |||
| << "(" << xform.slope << ", " << xform.intercept.count() << ")"; | |||
| auto measurement = SessionMeasurement{move(xform), mClock.micros()}; | |||
| if (mCurrent.sessionId == id) | |||
| { | |||
| mCurrent.measurement = move(measurement); | |||
| mCallback(mCurrent); | |||
| } | |||
| else | |||
| { | |||
| const auto range = equal_range( | |||
| begin(mOtherSessions), end(mOtherSessions), Session{id, {}, {}}, SessionIdComp{}); | |||
| if (range.first != range.second) | |||
| { | |||
| const auto SESSION_EPS = chrono::microseconds{500000}; | |||
| // should we join this session? | |||
| const auto hostTime = mClock.micros(); | |||
| const auto curGhost = mCurrent.measurement.xform.hostToGhost(hostTime); | |||
| const auto newGhost = measurement.xform.hostToGhost(hostTime); | |||
| // update the measurement for the session entry | |||
| range.first->measurement = move(measurement); | |||
| // If session times too close - fall back to session id order | |||
| const auto ghostDiff = newGhost - curGhost; | |||
| if (ghostDiff > SESSION_EPS || (std::abs(ghostDiff.count()) < SESSION_EPS.count() | |||
| && id < mCurrent.sessionId)) | |||
| { | |||
| // The new session wins, switch over to it | |||
| auto current = mCurrent; | |||
| mCurrent = move(*range.first); | |||
| mOtherSessions.erase(range.first); | |||
| // Put the old current session back into our list of known | |||
| // sessions so that we won't re-measure it | |||
| const auto it = upper_bound( | |||
| begin(mOtherSessions), end(mOtherSessions), current, SessionIdComp{}); | |||
| mOtherSessions.insert(it, move(current)); | |||
| // And notify that we have a new session and make sure that | |||
| // we remeasure it periodically. | |||
| mCallback(mCurrent); | |||
| scheduleRemeasurement(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void scheduleRemeasurement() | |||
| { | |||
| // set a timer to re-measure the active session after a period | |||
| mTimer.expires_from_now(std::chrono::microseconds{30000000}); | |||
| mTimer.async_wait([this](const typename Timer::ErrorCode e) { | |||
| if (!e) | |||
| { | |||
| launchSessionMeasurement(mCurrent); | |||
| scheduleRemeasurement(); | |||
| } | |||
| }); | |||
| } | |||
| void handleFailedMeasurement(const SessionId& id) | |||
| { | |||
| using namespace std; | |||
| debug(mIo->log()) << "Session " << id << " measurement failed."; | |||
| // if we failed to measure for our current session, schedule a | |||
| // retry in the future. Otherwise, remove the session from our set | |||
| // of known sessions (if it is seen again it will be measured as | |||
| // if new). | |||
| if (mCurrent.sessionId == id) | |||
| { | |||
| scheduleRemeasurement(); | |||
| } | |||
| else | |||
| { | |||
| const auto range = equal_range( | |||
| begin(mOtherSessions), end(mOtherSessions), Session{id, {}, {}}, SessionIdComp{}); | |||
| if (range.first != range.second) | |||
| { | |||
| mOtherSessions.erase(range.first); | |||
| mPeers->forgetSession(id); | |||
| } | |||
| } | |||
| } | |||
| void updateTimeline(Session& session, Timeline timeline) | |||
| { | |||
| // We use beat origin magnitude to prioritize sessions. | |||
| if (timeline.beatOrigin > session.timeline.beatOrigin) | |||
| { | |||
| debug(mIo->log()) << "Adopting peer timeline (" << timeline.tempo.bpm() << ", " | |||
| << timeline.beatOrigin.floating() << ", " | |||
| << timeline.timeOrigin.count() << ")"; | |||
| session.timeline = std::move(timeline); | |||
| } | |||
| else | |||
| { | |||
| debug(mIo->log()) << "Rejecting peer timeline with beat origin: " | |||
| << timeline.beatOrigin.floating() | |||
| << ". Current timeline beat origin: " | |||
| << session.timeline.beatOrigin.floating(); | |||
| } | |||
| } | |||
| struct MeasurementResultsHandler | |||
| { | |||
| void operator()(GhostXForm xform) const | |||
| { | |||
| Sessions& sessions = mSessions; | |||
| const SessionId& sessionId = mSessionId; | |||
| if (xform == GhostXForm{}) | |||
| { | |||
| mSessions.mIo->async([&sessions, sessionId] { | |||
| sessions.handleFailedMeasurement(std::move(sessionId)); | |||
| }); | |||
| } | |||
| else | |||
| { | |||
| mSessions.mIo->async([&sessions, sessionId, xform] { | |||
| sessions.handleSuccessfulMeasurement(std::move(sessionId), std::move(xform)); | |||
| }); | |||
| } | |||
| } | |||
| Sessions& mSessions; | |||
| SessionId mSessionId; | |||
| }; | |||
| struct SessionIdComp | |||
| { | |||
| bool operator()(const Session& lhs, const Session& rhs) const | |||
| { | |||
| return lhs.sessionId < rhs.sessionId; | |||
| } | |||
| }; | |||
| using Peer = typename util::Injected<Peers>::type::Peer; | |||
| util::Injected<Peers> mPeers; | |||
| MeasurePeer mMeasure; | |||
| JoinSessionCallback mCallback; | |||
| Session mCurrent; | |||
| util::Injected<IoContext> mIo; | |||
| Timer mTimer; | |||
| Clock mClock; | |||
| std::vector<Session> mOtherSessions; // sorted/unique by session id | |||
| }; | |||
| template <typename Peers, | |||
| typename MeasurePeer, | |||
| typename JoinSessionCallback, | |||
| typename IoContext, | |||
| typename Clock> | |||
| Sessions<Peers, MeasurePeer, JoinSessionCallback, IoContext, Clock> makeSessions( | |||
| Session init, | |||
| util::Injected<Peers> peers, | |||
| MeasurePeer measure, | |||
| JoinSessionCallback join, | |||
| util::Injected<IoContext> io, | |||
| Clock clock) | |||
| { | |||
| using namespace std; | |||
| return {move(init), move(peers), move(measure), move(join), move(io), move(clock)}; | |||
| } | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,90 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/Beats.hpp> | |||
| #include <chrono> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| struct Tempo : std::tuple<double> | |||
| { | |||
| Tempo() = default; | |||
| // Beats per minute | |||
| explicit Tempo(const double bpm) | |||
| : std::tuple<double>(bpm) | |||
| { | |||
| } | |||
| Tempo(const std::chrono::microseconds microsPerBeat) | |||
| : std::tuple<double>(60. * 1e6 / microsPerBeat.count()) | |||
| { | |||
| } | |||
| double bpm() const | |||
| { | |||
| return std::get<0>(*this); | |||
| } | |||
| std::chrono::microseconds microsPerBeat() const | |||
| { | |||
| return std::chrono::microseconds{llround(60. * 1e6 / bpm())}; | |||
| } | |||
| // Given the tempo, convert a time to a beat value | |||
| Beats microsToBeats(const std::chrono::microseconds micros) const | |||
| { | |||
| return Beats{micros.count() / static_cast<double>(microsPerBeat().count())}; | |||
| } | |||
| // Given the tempo, convert a beat to a time value | |||
| std::chrono::microseconds beatsToMicros(const Beats beats) const | |||
| { | |||
| return std::chrono::microseconds{llround(beats.floating() * microsPerBeat().count())}; | |||
| } | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const Tempo tempo) | |||
| { | |||
| return discovery::sizeInByteStream(tempo.microsPerBeat()); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const Tempo tempo, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(tempo.microsPerBeat(), std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<Tempo, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| auto result = | |||
| discovery::Deserialize<std::chrono::microseconds>::fromNetworkByteStream( | |||
| std::move(begin), std::move(end)); | |||
| return std::make_pair(Tempo{std::move(result.first)}, std::move(result.second)); | |||
| } | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,99 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/NetworkByteStreamSerializable.hpp> | |||
| #include <ableton/link/Beats.hpp> | |||
| #include <ableton/link/Tempo.hpp> | |||
| #include <cmath> | |||
| #include <cstdint> | |||
| #include <tuple> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| // A tuple of (tempo, beats, time), with integral units | |||
| // based on microseconds. This type establishes a bijection between | |||
| // beats and wall time, given a valid tempo. It also serves as a | |||
| // payload entry. | |||
| struct Timeline | |||
| { | |||
| enum | |||
| { | |||
| key = 'tmln' | |||
| }; | |||
| Beats toBeats(const std::chrono::microseconds time) const | |||
| { | |||
| return beatOrigin + tempo.microsToBeats(time - timeOrigin); | |||
| } | |||
| std::chrono::microseconds fromBeats(const Beats beats) const | |||
| { | |||
| return timeOrigin + tempo.beatsToMicros(beats - beatOrigin); | |||
| } | |||
| friend bool operator==(const Timeline& lhs, const Timeline& rhs) | |||
| { | |||
| return std::tie(lhs.tempo, lhs.beatOrigin, lhs.timeOrigin) | |||
| == std::tie(rhs.tempo, rhs.beatOrigin, rhs.timeOrigin); | |||
| } | |||
| friend bool operator!=(const Timeline& lhs, const Timeline& rhs) | |||
| { | |||
| return !(lhs == rhs); | |||
| } | |||
| // Model the NetworkByteStreamSerializable concept | |||
| friend std::uint32_t sizeInByteStream(const Timeline& tl) | |||
| { | |||
| return discovery::sizeInByteStream(std::tie(tl.tempo, tl.beatOrigin, tl.timeOrigin)); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const Timeline& tl, It out) | |||
| { | |||
| return discovery::toNetworkByteStream( | |||
| std::tie(tl.tempo, tl.beatOrigin, tl.timeOrigin), std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<Timeline, It> fromNetworkByteStream(It begin, It end) | |||
| { | |||
| using namespace std; | |||
| using namespace discovery; | |||
| Timeline timeline; | |||
| auto result = | |||
| Deserialize<tuple<Tempo, Beats, chrono::microseconds>>::fromNetworkByteStream( | |||
| move(begin), move(end)); | |||
| tie(timeline.tempo, timeline.beatOrigin, timeline.timeOrigin) = move(result.first); | |||
| return make_pair(move(timeline), move(result.second)); | |||
| } | |||
| Tempo tempo; | |||
| Beats beatOrigin; | |||
| std::chrono::microseconds timeOrigin; | |||
| }; | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,138 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/Payload.hpp> | |||
| #include <array> | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| namespace v1 | |||
| { | |||
| // The maximum size of a message, in bytes | |||
| const std::size_t kMaxMessageSize = 512; | |||
| // Utility typedef for an array of bytes of maximum message size | |||
| using MessageBuffer = std::array<uint8_t, v1::kMaxMessageSize>; | |||
| using MessageType = uint8_t; | |||
| const MessageType kPing = 1; | |||
| const MessageType kPong = 2; | |||
| struct MessageHeader | |||
| { | |||
| MessageType messageType; | |||
| friend std::uint32_t sizeInByteStream(const MessageHeader& header) | |||
| { | |||
| return discovery::sizeInByteStream(header.messageType); | |||
| } | |||
| template <typename It> | |||
| friend It toNetworkByteStream(const MessageHeader& header, It out) | |||
| { | |||
| return discovery::toNetworkByteStream(header.messageType, std::move(out)); | |||
| } | |||
| template <typename It> | |||
| static std::pair<MessageHeader, It> fromNetworkByteStream(It begin, const It end) | |||
| { | |||
| using namespace discovery; | |||
| MessageHeader header; | |||
| std::tie(header.messageType, begin) = | |||
| Deserialize<decltype(header.messageType)>::fromNetworkByteStream(begin, end); | |||
| return std::make_pair(std::move(header), std::move(begin)); | |||
| } | |||
| }; | |||
| namespace detail | |||
| { | |||
| // Types that are only used in the sending/parsing of messages, not | |||
| // publicly exposed. | |||
| using ProtocolHeader = std::array<char, 8>; | |||
| const ProtocolHeader kProtocolHeader = {{'_', 'l', 'i', 'n', 'k', '_', 'v', 1}}; | |||
| // Must have at least kMaxMessageSize bytes available in the output stream | |||
| template <typename Payload, typename It> | |||
| It encodeMessage(const MessageType messageType, const Payload& payload, It out) | |||
| { | |||
| using namespace std; | |||
| const MessageHeader header = {messageType}; | |||
| const auto messageSize = | |||
| kProtocolHeader.size() + sizeInByteStream(header) + sizeInByteStream(payload); | |||
| if (messageSize < kMaxMessageSize) | |||
| { | |||
| return toNetworkByteStream( | |||
| payload, toNetworkByteStream( | |||
| header, copy(begin(kProtocolHeader), end(kProtocolHeader), move(out)))); | |||
| } | |||
| else | |||
| { | |||
| throw range_error("Exceeded maximum message size"); | |||
| } | |||
| } | |||
| } // namespace detail | |||
| template <typename Payload, typename It> | |||
| It pingMessage(const Payload& payload, It out) | |||
| { | |||
| return detail::encodeMessage(kPing, payload, std::move(out)); | |||
| } | |||
| template <typename Payload, typename It> | |||
| It pongMessage(const Payload& payload, It out) | |||
| { | |||
| return detail::encodeMessage(kPong, payload, std::move(out)); | |||
| } | |||
| template <typename It> | |||
| std::pair<MessageHeader, It> parseMessageHeader(It bytesBegin, const It bytesEnd) | |||
| { | |||
| using ItDiff = typename std::iterator_traits<It>::difference_type; | |||
| MessageHeader header = {}; | |||
| const auto protocolHeaderSize = discovery::sizeInByteStream(detail::kProtocolHeader); | |||
| const auto minMessageSize = | |||
| static_cast<ItDiff>(protocolHeaderSize + sizeInByteStream(header)); | |||
| // If there are enough bytes in the stream to make a header and if | |||
| // the first bytes in the stream are the protocol header, then | |||
| // proceed to parse the stream. | |||
| if (std::distance(bytesBegin, bytesEnd) >= minMessageSize | |||
| && std::equal( | |||
| begin(detail::kProtocolHeader), end(detail::kProtocolHeader), bytesBegin)) | |||
| { | |||
| std::tie(header, bytesBegin) = | |||
| MessageHeader::fromNetworkByteStream(bytesBegin + protocolHeaderSize, bytesEnd); | |||
| } | |||
| return std::make_pair(std::move(header), std::move(bytesBegin)); | |||
| } | |||
| } // namespace v1 | |||
| } // namespace link | |||
| } // namespace ableton | |||
| @@ -0,0 +1,66 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/link/Controller.hpp> | |||
| #include <ableton/util/Log.hpp> | |||
| #if LINK_PLATFORM_WINDOWS | |||
| #include <ableton/platforms/asio/Context.hpp> | |||
| #include <ableton/platforms/windows/Clock.hpp> | |||
| #include <ableton/platforms/windows/ScanIpIfAddrs.hpp> | |||
| #elif LINK_PLATFORM_MACOSX | |||
| #include <ableton/platforms/asio/Context.hpp> | |||
| #include <ableton/platforms/darwin/Clock.hpp> | |||
| #include <ableton/platforms/posix/ScanIpIfAddrs.hpp> | |||
| #elif LINK_PLATFORM_LINUX | |||
| #include <ableton/platforms/asio/Context.hpp> | |||
| #include <ableton/platforms/posix/ScanIpIfAddrs.hpp> | |||
| #include <ableton/platforms/stl/Clock.hpp> | |||
| #endif | |||
| namespace ableton | |||
| { | |||
| namespace link | |||
| { | |||
| namespace platform | |||
| { | |||
| #if LINK_PLATFORM_WINDOWS | |||
| using Clock = platforms::windows::Clock; | |||
| using IoContext = | |||
| platforms::asio::Context<platforms::windows::ScanIpIfAddrs, util::NullLog>; | |||
| #elif LINK_PLATFORM_MACOSX | |||
| using Clock = platforms::darwin::Clock; | |||
| using IoContext = | |||
| platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>; | |||
| #elif LINK_PLATFORM_LINUX | |||
| using Clock = platforms::stl::Clock; | |||
| using IoContext = | |||
| platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>; | |||
| #endif | |||
| using Controller = Controller<PeerCountCallback, TempoCallback, Clock, IoContext>; | |||
| } // platform | |||
| } // link | |||
| } // ableton | |||
| @@ -0,0 +1,105 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioTimer.hpp> | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <thread> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace asio | |||
| { | |||
| class AsioService | |||
| { | |||
| public: | |||
| using Timer = AsioTimer; | |||
| AsioService() | |||
| : AsioService(DefaultHandler{}) | |||
| { | |||
| } | |||
| template <typename ExceptionHandler> | |||
| explicit AsioService(ExceptionHandler exceptHandler) | |||
| : mpWork(new ::asio::io_service::work(mService)) | |||
| { | |||
| mThread = | |||
| std::thread{[](::asio::io_service& service, ExceptionHandler handler) { | |||
| for (;;) | |||
| { | |||
| try | |||
| { | |||
| service.run(); | |||
| break; | |||
| } | |||
| catch (const typename ExceptionHandler::Exception& exception) | |||
| { | |||
| handler(exception); | |||
| } | |||
| } | |||
| }, | |||
| std::ref(mService), std::move(exceptHandler)}; | |||
| } | |||
| ~AsioService() | |||
| { | |||
| mpWork.reset(); | |||
| mThread.join(); | |||
| } | |||
| AsioTimer makeTimer() | |||
| { | |||
| return {mService}; | |||
| } | |||
| template <typename Handler> | |||
| void post(Handler handler) | |||
| { | |||
| mService.post(std::move(handler)); | |||
| } | |||
| ::asio::io_service mService; | |||
| private: | |||
| // Default handler is hidden and defines a hidden exception type | |||
| // that will never be thrown by other code, so it effectively does | |||
| // not catch. | |||
| struct DefaultHandler | |||
| { | |||
| struct Exception | |||
| { | |||
| }; | |||
| void operator()(const Exception&) | |||
| { | |||
| } | |||
| }; | |||
| std::unique_ptr<::asio::io_service::work> mpWork; | |||
| std::thread mThread; | |||
| }; | |||
| } // namespace asio | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,132 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/SafeAsyncHandler.hpp> | |||
| #include <functional> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace asio | |||
| { | |||
| // This implementation is based on the boost::asio::system_timer concept. | |||
| // Since boost::system_timer doesn't support move semantics, we create a wrapper | |||
| // with a unique_ptr to get a movable type. It also handles an inconvenient | |||
| // aspect of asio timers, which is that you must explicitly guard against the | |||
| // handler firing after cancellation. We handle this by use of the SafeAsyncHandler | |||
| // utility. AsioTimer therefore guarantees that a handler will not be called after | |||
| // the destruction of the timer, or after the timer has been canceled. | |||
| class AsioTimer | |||
| { | |||
| public: | |||
| using ErrorCode = ::asio::error_code; | |||
| using TimePoint = std::chrono::system_clock::time_point; | |||
| AsioTimer(::asio::io_service& io) | |||
| : mpTimer(new ::asio::system_timer(io)) | |||
| , mpAsyncHandler(std::make_shared<AsyncHandler>()) | |||
| { | |||
| } | |||
| ~AsioTimer() | |||
| { | |||
| // The timer may not be valid anymore if this instance was moved from | |||
| if (mpTimer != nullptr) | |||
| { | |||
| // Ignore errors during cancellation | |||
| cancel(); | |||
| } | |||
| } | |||
| AsioTimer(const AsioTimer&) = delete; | |||
| AsioTimer& operator=(const AsioTimer&) = delete; | |||
| // Enable move construction but not move assignment. Move assignment | |||
| // would get weird - would have to handle outstanding handlers | |||
| AsioTimer(AsioTimer&& rhs) | |||
| : mpTimer(std::move(rhs.mpTimer)) | |||
| , mpAsyncHandler(std::move(rhs.mpAsyncHandler)) | |||
| { | |||
| } | |||
| void expires_at(std::chrono::system_clock::time_point tp) | |||
| { | |||
| mpTimer->expires_at(std::move(tp)); | |||
| } | |||
| template <typename T> | |||
| void expires_from_now(T duration) | |||
| { | |||
| mpTimer->expires_from_now(std::move(duration)); | |||
| } | |||
| ErrorCode cancel() | |||
| { | |||
| ErrorCode ec; | |||
| mpTimer->cancel(ec); | |||
| mpAsyncHandler->mpHandler = nullptr; | |||
| return ec; | |||
| } | |||
| template <typename Handler> | |||
| void async_wait(Handler handler) | |||
| { | |||
| *mpAsyncHandler = std::move(handler); | |||
| mpTimer->async_wait(util::makeAsyncSafe(mpAsyncHandler)); | |||
| } | |||
| TimePoint now() const | |||
| { | |||
| return std::chrono::system_clock::now(); | |||
| } | |||
| private: | |||
| struct AsyncHandler | |||
| { | |||
| template <typename Handler> | |||
| AsyncHandler& operator=(Handler handler) | |||
| { | |||
| mpHandler = [handler](ErrorCode ec) { handler(std::move(ec)); }; | |||
| return *this; | |||
| } | |||
| void operator()(ErrorCode ec) | |||
| { | |||
| if (mpHandler) | |||
| { | |||
| mpHandler(std::move(ec)); | |||
| } | |||
| } | |||
| std::function<void(const ErrorCode)> mpHandler; | |||
| }; | |||
| std::unique_ptr<::asio::system_timer> mpTimer; | |||
| std::shared_ptr<AsyncHandler> mpAsyncHandler; | |||
| }; | |||
| } // namespace asio | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,76 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| /*! | |||
| * \brief Wrapper file for AsioStandalone library | |||
| * | |||
| * This file includes all necessary headers from the AsioStandalone library which are used | |||
| * by Link. | |||
| */ | |||
| #pragma push_macro("ASIO_STANDALONE") | |||
| #define ASIO_STANDALONE 1 | |||
| #pragma push_macro("ASIO_NO_TYPEID") | |||
| #define ASIO_NO_TYPEID 1 | |||
| #if LINK_PLATFORM_WINDOWS | |||
| #pragma push_macro("INCL_EXTRA_HTON_FUNCTIONS") | |||
| #define INCL_EXTRA_HTON_FUNCTIONS 1 | |||
| #endif | |||
| #if defined(__clang__) | |||
| #pragma clang diagnostic push | |||
| #if __has_warning("-Wcomma") | |||
| #pragma clang diagnostic ignored "-Wcomma" | |||
| #endif | |||
| #endif | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(push) | |||
| // C4191: 'operator/operation': unsafe conversion from 'type of expression' to | |||
| // 'type required' | |||
| #pragma warning(disable : 4191) | |||
| // C4548: expression before comma has no effect; expected expression with side-effect | |||
| #pragma warning(disable : 4548) | |||
| // C4619: #pragma warning : there is no warning number 'number' | |||
| #pragma warning(disable : 4619) | |||
| // C4675: 'function' : resolved overload was found by argument-dependent lookup | |||
| #pragma warning(disable : 4675) | |||
| #endif | |||
| #include <asio.hpp> | |||
| #include <asio/system_timer.hpp> | |||
| #if LINK_PLATFORM_WINDOWS | |||
| #pragma pop_macro("INCL_EXTRA_HTON_FUNCTIONS") | |||
| #endif | |||
| #pragma pop_macro("ASIO_STANDALONE") | |||
| #pragma pop_macro("ASIO_NO_TYPEID") | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(pop) | |||
| #endif | |||
| #if defined(__clang__) | |||
| #pragma clang diagnostic pop | |||
| #endif | |||
| @@ -0,0 +1,181 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/discovery/IpV4Interface.hpp> | |||
| #include <ableton/platforms/asio/AsioTimer.hpp> | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/platforms/asio/PooledHandlerContext.hpp> | |||
| #include <ableton/platforms/asio/Socket.hpp> | |||
| #include <thread> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace asio | |||
| { | |||
| template <typename ScanIpIfAddrs, typename LogT> | |||
| class Context | |||
| { | |||
| public: | |||
| using Timer = AsioTimer; | |||
| using Log = LogT; | |||
| template <std::size_t BufferSize> | |||
| using Socket = asio::Socket<BufferSize>; | |||
| template <typename IoContext> | |||
| using RealTimeContext = PooledHandlerContext<IoContext>; | |||
| Context() | |||
| : Context(DefaultHandler{}) | |||
| { | |||
| } | |||
| template <typename ExceptionHandler> | |||
| explicit Context(ExceptionHandler exceptHandler) | |||
| : mpService(new ::asio::io_service()) | |||
| , mpWork(new ::asio::io_service::work(*mpService)) | |||
| { | |||
| mThread = | |||
| std::thread{[](::asio::io_service& service, ExceptionHandler handler) { | |||
| for (;;) | |||
| { | |||
| try | |||
| { | |||
| service.run(); | |||
| break; | |||
| } | |||
| catch (const typename ExceptionHandler::Exception& exception) | |||
| { | |||
| handler(exception); | |||
| } | |||
| } | |||
| }, | |||
| std::ref(*mpService), std::move(exceptHandler)}; | |||
| } | |||
| Context(const Context&) = delete; | |||
| Context(Context&& rhs) | |||
| : mpService(std::move(rhs.mpService)) | |||
| , mpWork(std::move(rhs.mpWork)) | |||
| , mThread(std::move(rhs.mThread)) | |||
| , mLog(std::move(rhs.mLog)) | |||
| , mScanIpIfAddrs(std::move(rhs.mScanIpIfAddrs)) | |||
| { | |||
| } | |||
| ~Context() | |||
| { | |||
| if (mpService) | |||
| { | |||
| mpWork.reset(); | |||
| mThread.join(); | |||
| } | |||
| } | |||
| template <std::size_t BufferSize> | |||
| Socket<BufferSize> openUnicastSocket(const ::asio::ip::address_v4& addr) | |||
| { | |||
| auto socket = Socket<BufferSize>{*mpService}; | |||
| socket.mpImpl->mSocket.set_option( | |||
| ::asio::ip::multicast::enable_loopback(addr.is_loopback())); | |||
| socket.mpImpl->mSocket.set_option(::asio::ip::multicast::outbound_interface(addr)); | |||
| socket.mpImpl->mSocket.bind(::asio::ip::udp::endpoint{addr, 0}); | |||
| return socket; | |||
| } | |||
| template <std::size_t BufferSize> | |||
| Socket<BufferSize> openMulticastSocket(const ::asio::ip::address_v4& addr) | |||
| { | |||
| auto socket = Socket<BufferSize>{*mpService}; | |||
| socket.mpImpl->mSocket.set_option(::asio::ip::udp::socket::reuse_address(true)); | |||
| socket.mpImpl->mSocket.set_option( | |||
| ::asio::socket_base::broadcast(!addr.is_loopback())); | |||
| socket.mpImpl->mSocket.set_option( | |||
| ::asio::ip::multicast::enable_loopback(addr.is_loopback())); | |||
| socket.mpImpl->mSocket.set_option(::asio::ip::multicast::outbound_interface(addr)); | |||
| socket.mpImpl->mSocket.bind({::asio::ip::address::from_string("0.0.0.0"), | |||
| discovery::multicastEndpoint().port()}); | |||
| socket.mpImpl->mSocket.set_option(::asio::ip::multicast::join_group( | |||
| discovery::multicastEndpoint().address().to_v4(), addr)); | |||
| return socket; | |||
| } | |||
| std::vector<::asio::ip::address> scanNetworkInterfaces() | |||
| { | |||
| return mScanIpIfAddrs(); | |||
| } | |||
| Timer makeTimer() const | |||
| { | |||
| return {*mpService}; | |||
| } | |||
| Log& log() | |||
| { | |||
| return mLog; | |||
| } | |||
| template <typename Handler> | |||
| void async(Handler handler) | |||
| { | |||
| mpService->post(std::move(handler)); | |||
| } | |||
| Context clone() const | |||
| { | |||
| return {}; | |||
| } | |||
| template <typename ExceptionHandler> | |||
| Context clone(ExceptionHandler handler) const | |||
| { | |||
| return Context{std::move(handler)}; | |||
| } | |||
| private: | |||
| // Default handler is hidden and defines a hidden exception type | |||
| // that will never be thrown by other code, so it effectively does | |||
| // not catch. | |||
| struct DefaultHandler | |||
| { | |||
| struct Exception | |||
| { | |||
| }; | |||
| void operator()(const Exception&) | |||
| { | |||
| } | |||
| }; | |||
| std::unique_ptr<::asio::io_service> mpService; | |||
| std::unique_ptr<::asio::io_service::work> mpWork; | |||
| std::thread mThread; | |||
| Log mLog; | |||
| ScanIpIfAddrs mScanIpIfAddrs; | |||
| }; | |||
| } // namespace asio | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,115 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/Injected.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace asio | |||
| { | |||
| template <typename IoContext, | |||
| std::size_t MaxNumHandlers = 32, | |||
| std::size_t MaxHandlerSize = 128> | |||
| struct PooledHandlerContext | |||
| { | |||
| PooledHandlerContext(util::Injected<IoContext> io) | |||
| : mIo(std::move(io)) | |||
| { | |||
| // initialize the handler free store | |||
| mFreeStack.reserve(MaxNumHandlers); | |||
| for (std::size_t i = 0; i < MaxNumHandlers; ++i) | |||
| { | |||
| mFreeStack.push_back(reinterpret_cast<void*>(mRaw + i)); | |||
| } | |||
| } | |||
| template <typename Handler> | |||
| void async(Handler handler) | |||
| { | |||
| try | |||
| { | |||
| mIo->async(HandlerWrapper<Handler>{*this, std::move(handler)}); | |||
| } | |||
| catch (std::bad_alloc) | |||
| { | |||
| warning(mIo->log()) << "Handler dropped due to low memory pool"; | |||
| } | |||
| } | |||
| template <typename Handler> | |||
| struct HandlerWrapper | |||
| { | |||
| HandlerWrapper(PooledHandlerContext& context, Handler handler) | |||
| : mContext(context) | |||
| , mHandler(std::move(handler)) | |||
| { | |||
| } | |||
| void operator()() | |||
| { | |||
| mHandler(); | |||
| } | |||
| // Use pooled allocation so that posting handlers will not cause | |||
| // system allocation | |||
| friend void* asio_handler_allocate( | |||
| const std::size_t size, HandlerWrapper* const pHandler) | |||
| { | |||
| if (size > MaxHandlerSize || pHandler->mContext.mFreeStack.empty()) | |||
| { | |||
| // Going over the max handler size is a programming error, as | |||
| // this is not a dynamically variable value. | |||
| assert(size <= MaxHandlerSize); | |||
| throw std::bad_alloc(); | |||
| } | |||
| else | |||
| { | |||
| const auto p = pHandler->mContext.mFreeStack.back(); | |||
| pHandler->mContext.mFreeStack.pop_back(); | |||
| return p; | |||
| } | |||
| } | |||
| friend void asio_handler_deallocate( | |||
| void* const p, std::size_t, HandlerWrapper* const pHandler) | |||
| { | |||
| pHandler->mContext.mFreeStack.push_back(p); | |||
| } | |||
| PooledHandlerContext& mContext; | |||
| Handler mHandler; | |||
| }; | |||
| using MemChunk = typename std::aligned_storage<MaxHandlerSize, | |||
| std::alignment_of<max_align_t>::value>::type; | |||
| MemChunk mRaw[MaxNumHandlers]; | |||
| std::vector<void*> mFreeStack; | |||
| util::Injected<IoContext> mIo; | |||
| }; | |||
| } // namespace asio | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,110 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/util/SafeAsyncHandler.hpp> | |||
| #include <array> | |||
| #include <cassert> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace asio | |||
| { | |||
| template <std::size_t MaxPacketSize> | |||
| struct Socket | |||
| { | |||
| Socket(::asio::io_service& io) | |||
| : mpImpl(std::make_shared<Impl>(io)) | |||
| { | |||
| } | |||
| Socket(const Socket&) = delete; | |||
| Socket& operator=(const Socket&) = delete; | |||
| Socket(Socket&& rhs) | |||
| : mpImpl(std::move(rhs.mpImpl)) | |||
| { | |||
| } | |||
| std::size_t send(const uint8_t* const pData, | |||
| const size_t numBytes, | |||
| const ::asio::ip::udp::endpoint& to) | |||
| { | |||
| assert(numBytes < MaxPacketSize); | |||
| return mpImpl->mSocket.send_to(::asio::buffer(pData, numBytes), to); | |||
| } | |||
| template <typename Handler> | |||
| void receive(Handler handler) | |||
| { | |||
| mpImpl->mHandler = std::move(handler); | |||
| mpImpl->mSocket.async_receive_from( | |||
| ::asio::buffer(mpImpl->mReceiveBuffer, MaxPacketSize), mpImpl->mSenderEndpoint, | |||
| util::makeAsyncSafe(mpImpl)); | |||
| } | |||
| ::asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return mpImpl->mSocket.local_endpoint(); | |||
| } | |||
| struct Impl | |||
| { | |||
| Impl(::asio::io_service& io) | |||
| : mSocket(io, ::asio::ip::udp::v4()) | |||
| { | |||
| } | |||
| ~Impl() | |||
| { | |||
| // Ignore error codes in shutdown and close as the socket may | |||
| // have already been forcibly closed | |||
| ::asio::error_code ec; | |||
| mSocket.shutdown(::asio::ip::udp::socket::shutdown_both, ec); | |||
| mSocket.close(ec); | |||
| } | |||
| void operator()(const ::asio::error_code& error, const std::size_t numBytes) | |||
| { | |||
| if (!error && numBytes > 0 && numBytes <= MaxPacketSize) | |||
| { | |||
| const auto bufBegin = begin(mReceiveBuffer); | |||
| mHandler(mSenderEndpoint, bufBegin, bufBegin + static_cast<ptrdiff_t>(numBytes)); | |||
| } | |||
| } | |||
| ::asio::ip::udp::socket mSocket; | |||
| ::asio::ip::udp::endpoint mSenderEndpoint; | |||
| using Buffer = std::array<uint8_t, MaxPacketSize>; | |||
| Buffer mReceiveBuffer; | |||
| using ByteIt = typename Buffer::const_iterator; | |||
| std::function<void(const ::asio::ip::udp::endpoint&, ByteIt, ByteIt)> mHandler; | |||
| }; | |||
| std::shared_ptr<Impl> mpImpl; | |||
| }; | |||
| } // namespace asio | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,43 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <algorithm> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace asio | |||
| { | |||
| // Utility for making v4 or v6 ip addresses from raw bytes in network byte-order | |||
| template <typename AsioAddrType> | |||
| AsioAddrType makeAddress(const char* pAddr) | |||
| { | |||
| using namespace std; | |||
| typename AsioAddrType::bytes_type bytes; | |||
| copy(pAddr, pAddr + bytes.size(), begin(bytes)); | |||
| return AsioAddrType{bytes}; | |||
| } | |||
| } // asio | |||
| } // platforms | |||
| } // ableton | |||
| @@ -0,0 +1,70 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <chrono> | |||
| #include <mach/mach_time.h> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace darwin | |||
| { | |||
| struct Clock | |||
| { | |||
| using Ticks = std::uint64_t; | |||
| using Micros = std::chrono::microseconds; | |||
| Clock() | |||
| { | |||
| mach_timebase_info_data_t timeInfo; | |||
| mach_timebase_info(&timeInfo); | |||
| // numer / denom gives nanoseconds, we want microseconds | |||
| mTicksToMicros = timeInfo.numer / (timeInfo.denom * 1000.); | |||
| } | |||
| Micros ticksToMicros(const Ticks ticks) const | |||
| { | |||
| return Micros{llround(mTicksToMicros * ticks)}; | |||
| } | |||
| Ticks microsToTicks(const Micros micros) const | |||
| { | |||
| return static_cast<Ticks>(micros.count() / mTicksToMicros); | |||
| } | |||
| Ticks ticks() const | |||
| { | |||
| return mach_absolute_time(); | |||
| } | |||
| std::chrono::microseconds micros() const | |||
| { | |||
| return ticksToMicros(ticks()); | |||
| } | |||
| double mTicksToMicros; | |||
| }; | |||
| } // namespace darwin | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,30 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| // ntohll and htonll are not defined in 10.7 SDK, so we provide a compatibility macro here | |||
| #ifndef ntohll | |||
| #define ntohll(x) __DARWIN_OSSwapInt64(x) | |||
| #endif | |||
| #ifndef htonll | |||
| #define htonll(x) __DARWIN_OSSwapInt64(x) | |||
| #endif | |||
| @@ -0,0 +1,30 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <byteswap.h> | |||
| #ifndef ntohll | |||
| #define ntohll(x) bswap_64(x) | |||
| #endif | |||
| #ifndef htonll | |||
| #define htonll(x) bswap_64(x) | |||
| #endif | |||
| @@ -0,0 +1,110 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/platforms/asio/Util.hpp> | |||
| #include <arpa/inet.h> | |||
| #include <ifaddrs.h> | |||
| #include <net/if.h> | |||
| #include <vector> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace posix | |||
| { | |||
| namespace detail | |||
| { | |||
| // RAII type to make [get,free]ifaddrs function pairs exception safe | |||
| class GetIfAddrs | |||
| { | |||
| public: | |||
| GetIfAddrs() | |||
| { | |||
| if (getifaddrs(&interfaces)) // returns 0 on success | |||
| { | |||
| interfaces = NULL; | |||
| } | |||
| } | |||
| ~GetIfAddrs() | |||
| { | |||
| if (interfaces) | |||
| freeifaddrs(interfaces); | |||
| } | |||
| // RAII must not copy | |||
| GetIfAddrs(GetIfAddrs&) = delete; | |||
| GetIfAddrs& operator=(GetIfAddrs&) = delete; | |||
| template <typename Function> | |||
| void withIfAddrs(Function f) | |||
| { | |||
| if (interfaces) | |||
| f(*interfaces); | |||
| } | |||
| private: | |||
| struct ifaddrs* interfaces = NULL; | |||
| }; | |||
| } // detail | |||
| // Posix implementation of ip interface address scanner | |||
| struct ScanIpIfAddrs | |||
| { | |||
| // Scan active network interfaces and return corresponding addresses | |||
| // for all ip-based interfaces. | |||
| std::vector<::asio::ip::address> operator()() | |||
| { | |||
| std::vector<::asio::ip::address> addrs; | |||
| detail::GetIfAddrs getIfAddrs; | |||
| getIfAddrs.withIfAddrs([&addrs](const struct ifaddrs& interfaces) { | |||
| const struct ifaddrs* interface; | |||
| for (interface = &interfaces; interface; interface = interface->ifa_next) | |||
| { | |||
| auto addr = reinterpret_cast<const struct sockaddr_in*>(interface->ifa_addr); | |||
| if (addr && interface->ifa_flags & IFF_UP) | |||
| { | |||
| if (addr->sin_family == AF_INET) | |||
| { | |||
| auto bytes = reinterpret_cast<const char*>(&addr->sin_addr); | |||
| addrs.emplace_back(asio::makeAddress<::asio::ip::address_v4>(bytes)); | |||
| } | |||
| else if (addr->sin_family == AF_INET6) | |||
| { | |||
| auto addr6 = reinterpret_cast<const struct sockaddr_in6*>(addr); | |||
| auto bytes = reinterpret_cast<const char*>(&addr6->sin6_addr); | |||
| addrs.emplace_back(asio::makeAddress<::asio::ip::address_v6>(bytes)); | |||
| } | |||
| } | |||
| } | |||
| }); | |||
| return addrs; | |||
| } | |||
| }; | |||
| } // namespace posix | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,51 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <chrono> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace stl | |||
| { | |||
| struct Clock | |||
| { | |||
| using Ticks = std::uint64_t; | |||
| Clock() | |||
| { | |||
| mStartTime = std::chrono::high_resolution_clock::now(); | |||
| } | |||
| std::chrono::microseconds micros() const | |||
| { | |||
| using namespace std::chrono; | |||
| return duration_cast<microseconds>(high_resolution_clock::now() - mStartTime); | |||
| } | |||
| std::chrono::high_resolution_clock::time_point mStartTime; | |||
| }; | |||
| } // namespace stl | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,71 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <chrono> | |||
| #include <cstdint> | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace windows | |||
| { | |||
| struct Clock | |||
| { | |||
| using Ticks = std::int64_t; | |||
| using Micros = std::chrono::microseconds; | |||
| Clock() | |||
| { | |||
| LARGE_INTEGER frequency; | |||
| QueryPerformanceFrequency(&frequency); | |||
| mTicksToMicros = 1.0e6 / frequency.QuadPart; | |||
| } | |||
| Micros ticksToMicros(const Ticks ticks) const | |||
| { | |||
| return Micros{llround(mTicksToMicros * ticks)}; | |||
| } | |||
| Ticks microsToTicks(const Micros micros) const | |||
| { | |||
| return static_cast<Ticks>(micros.count() / mTicksToMicros); | |||
| } | |||
| Ticks ticks() const | |||
| { | |||
| LARGE_INTEGER count; | |||
| QueryPerformanceCounter(&count); | |||
| return count.QuadPart; | |||
| } | |||
| std::chrono::microseconds micros() const | |||
| { | |||
| return ticksToMicros(ticks()); | |||
| } | |||
| double mTicksToMicros; | |||
| }; | |||
| } // namespace windows | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,140 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/platforms/asio/Util.hpp> | |||
| #include <iphlpapi.h> | |||
| #include <stdio.h> | |||
| #include <vector> | |||
| #include <winsock2.h> | |||
| #include <ws2tcpip.h> | |||
| #pragma comment(lib, "iphlpapi.lib") | |||
| #pragma comment(lib, "ws2_32.lib") | |||
| namespace ableton | |||
| { | |||
| namespace platforms | |||
| { | |||
| namespace windows | |||
| { | |||
| namespace detail | |||
| { | |||
| // RAII type to make [get,free]ifaddrs function pairs exception safe | |||
| class GetIfAddrs | |||
| { | |||
| public: | |||
| GetIfAddrs() | |||
| { | |||
| const int MAX_TRIES = 3; // MSFT recommendation | |||
| const int WORKING_BUFFER_SIZE = 15000; // MSFT recommendation | |||
| DWORD adapter_addrs_buffer_size = WORKING_BUFFER_SIZE; | |||
| for (int i = 0; i < MAX_TRIES; i++) | |||
| { | |||
| adapter_addrs = (IP_ADAPTER_ADDRESSES*)malloc(adapter_addrs_buffer_size); | |||
| assert(adapter_addrs); | |||
| DWORD error = ::GetAdaptersAddresses(AF_UNSPEC, | |||
| GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | |||
| | GAA_FLAG_SKIP_FRIENDLY_NAME, | |||
| NULL, adapter_addrs, &adapter_addrs_buffer_size); | |||
| if (error == ERROR_SUCCESS) | |||
| { | |||
| break; | |||
| } | |||
| // if buffer too small, use new buffer size in next iteration | |||
| if (error == ERROR_BUFFER_OVERFLOW) | |||
| { | |||
| free(adapter_addrs); | |||
| adapter_addrs = NULL; | |||
| continue; | |||
| } | |||
| } | |||
| } | |||
| ~GetIfAddrs() | |||
| { | |||
| if (adapter_addrs) | |||
| free(adapter_addrs); | |||
| } | |||
| // RAII must not copy | |||
| GetIfAddrs(GetIfAddrs&) = delete; | |||
| GetIfAddrs& operator=(GetIfAddrs&) = delete; | |||
| template <typename Function> | |||
| void withIfAddrs(Function f) | |||
| { | |||
| if (adapter_addrs) | |||
| f(*adapter_addrs); | |||
| } | |||
| private: | |||
| IP_ADAPTER_ADDRESSES* adapter_addrs; | |||
| IP_ADAPTER_ADDRESSES* adapter; | |||
| }; | |||
| } // detail | |||
| struct ScanIpIfAddrs | |||
| { | |||
| // Scan active network interfaces and return corresponding addresses | |||
| // for all ip-based interfaces. | |||
| std::vector<::asio::ip::address> operator()() | |||
| { | |||
| std::vector<::asio::ip::address> addrs; | |||
| detail::GetIfAddrs getIfAddrs; | |||
| getIfAddrs.withIfAddrs([&addrs](const IP_ADAPTER_ADDRESSES& interfaces) { | |||
| const IP_ADAPTER_ADDRESSES* networkInterface; | |||
| for (networkInterface = &interfaces; networkInterface; | |||
| networkInterface = networkInterface->Next) | |||
| { | |||
| for (IP_ADAPTER_UNICAST_ADDRESS* address = networkInterface->FirstUnicastAddress; | |||
| NULL != address; address = address->Next) | |||
| { | |||
| auto family = address->Address.lpSockaddr->sa_family; | |||
| if (AF_INET == family) | |||
| { | |||
| // IPv4 | |||
| SOCKADDR_IN* addr4 = | |||
| reinterpret_cast<SOCKADDR_IN*>(address->Address.lpSockaddr); | |||
| auto bytes = reinterpret_cast<const char*>(&addr4->sin_addr); | |||
| addrs.emplace_back(asio::makeAddress<::asio::ip::address_v4>(bytes)); | |||
| } | |||
| else if (AF_INET6 == family) | |||
| { | |||
| SOCKADDR_IN6* addr6 = | |||
| reinterpret_cast<SOCKADDR_IN6*>(address->Address.lpSockaddr); | |||
| auto bytes = reinterpret_cast<const char*>(&addr6->sin6_addr); | |||
| addrs.emplace_back(asio::makeAddress<::asio::ip::address_v6>(bytes)); | |||
| } | |||
| } | |||
| } | |||
| }); | |||
| return addrs; | |||
| } | |||
| }; | |||
| } // namespace windows | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,43 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| /*! | |||
| * \brief Wrapper file for Catch library | |||
| * | |||
| * This file includes the Catch header for Link, and also disables some compiler warnings | |||
| * which are specific to that library. | |||
| */ | |||
| // Visual Studio | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(push) | |||
| // C4388: signed/unsigned mismatch | |||
| #pragma warning(disable : 4388) | |||
| // C4702: unreachable code | |||
| #pragma warning(disable : 4702) | |||
| #endif | |||
| #include <catch.hpp> | |||
| // Visual Studio | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(pop) | |||
| #endif | |||
| @@ -0,0 +1,110 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/test/serial_io/SchedulerTree.hpp> | |||
| #include <ableton/test/serial_io/Timer.hpp> | |||
| #include <ableton/util/Log.hpp> | |||
| #include <chrono> | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace test | |||
| { | |||
| namespace serial_io | |||
| { | |||
| class Context | |||
| { | |||
| public: | |||
| Context(const SchedulerTree::TimePoint& now, | |||
| const std::vector<::asio::ip::address>& ifAddrs, | |||
| std::shared_ptr<SchedulerTree> pScheduler) | |||
| : mNow(now) | |||
| , mIfAddrs(ifAddrs) | |||
| , mpScheduler(std::move(pScheduler)) | |||
| , mNextTimerId(0) | |||
| { | |||
| } | |||
| ~Context() | |||
| { | |||
| if (mpScheduler != nullptr) | |||
| { | |||
| // Finish any pending tasks before shutting down | |||
| mpScheduler->run(); | |||
| } | |||
| } | |||
| Context(const Context&) = delete; | |||
| Context& operator=(const Context&) = delete; | |||
| Context(Context&& rhs) | |||
| : mNow(rhs.mNow) | |||
| , mIfAddrs(rhs.mIfAddrs) | |||
| , mpScheduler(std::move(rhs.mpScheduler)) | |||
| , mLog(std::move(rhs.mLog)) | |||
| , mNextTimerId(rhs.mNextTimerId) | |||
| { | |||
| } | |||
| template <typename Handler> | |||
| void async(Handler handler) | |||
| { | |||
| mpScheduler->async(std::move(handler)); | |||
| } | |||
| Context clone() | |||
| { | |||
| return {mNow, mIfAddrs, mpScheduler->makeChild()}; | |||
| } | |||
| using Timer = serial_io::Timer; | |||
| Timer makeTimer() | |||
| { | |||
| return {mNextTimerId++, mNow, mpScheduler}; | |||
| } | |||
| using Log = util::NullLog; | |||
| Log& log() | |||
| { | |||
| return mLog; | |||
| } | |||
| std::vector<::asio::ip::address> scanNetworkInterfaces() | |||
| { | |||
| return mIfAddrs; | |||
| } | |||
| private: | |||
| const SchedulerTree::TimePoint& mNow; | |||
| const std::vector<::asio::ip::address>& mIfAddrs; | |||
| std::shared_ptr<SchedulerTree> mpScheduler; | |||
| Log mLog; | |||
| SchedulerTree::TimerId mNextTimerId; | |||
| }; | |||
| } // namespace serial_io | |||
| } // namespace test | |||
| } // namespace ableton | |||
| @@ -0,0 +1,92 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/asio/AsioWrapper.hpp> | |||
| #include <ableton/test/serial_io/Context.hpp> | |||
| #include <chrono> | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace test | |||
| { | |||
| namespace serial_io | |||
| { | |||
| class Fixture | |||
| { | |||
| public: | |||
| Fixture() | |||
| : mpScheduler(std::make_shared<SchedulerTree>()) | |||
| , mNow(std::chrono::milliseconds{123456789}) | |||
| { | |||
| } | |||
| ~Fixture() | |||
| { | |||
| flush(); | |||
| } | |||
| Fixture(const Fixture&) = delete; | |||
| Fixture& operator=(const Fixture&) = delete; | |||
| Fixture(Fixture&&) = delete; | |||
| Fixture& operator=(Fixture&&) = delete; | |||
| void setNetworkInterfaces(std::vector<::asio::ip::address> ifAddrs) | |||
| { | |||
| mIfAddrs = std::move(ifAddrs); | |||
| } | |||
| Context makeIoContext() | |||
| { | |||
| return {mNow, mIfAddrs, mpScheduler}; | |||
| } | |||
| void flush() | |||
| { | |||
| mpScheduler->run(); | |||
| } | |||
| template <typename T, typename Rep> | |||
| void advanceTime(std::chrono::duration<T, Rep> duration) | |||
| { | |||
| const auto target = mNow + duration; | |||
| mpScheduler->run(); | |||
| auto nextTimer = mpScheduler->nextTimerExpiration(); | |||
| while (nextTimer <= target) | |||
| { | |||
| mNow = nextTimer; | |||
| mpScheduler->triggerTimersUntil(mNow); | |||
| mpScheduler->run(); | |||
| nextTimer = mpScheduler->nextTimerExpiration(); | |||
| } | |||
| mNow = target; | |||
| } | |||
| private: | |||
| std::shared_ptr<SchedulerTree> mpScheduler; | |||
| SchedulerTree::TimePoint mNow; | |||
| std::vector<::asio::ip::address> mIfAddrs; | |||
| }; | |||
| } // namespace serial_io | |||
| } // namespace test | |||
| } // namespace ableton | |||
| @@ -0,0 +1,104 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <chrono> | |||
| #include <functional> | |||
| #include <list> | |||
| #include <map> | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace test | |||
| { | |||
| namespace serial_io | |||
| { | |||
| class SchedulerTree | |||
| { | |||
| public: | |||
| using TimePoint = std::chrono::system_clock::time_point; | |||
| using TimerId = std::size_t; | |||
| using TimerErrorCode = int; | |||
| void run(); | |||
| std::shared_ptr<SchedulerTree> makeChild(); | |||
| template <typename Handler> | |||
| void async(Handler handler) | |||
| { | |||
| mPendingHandlers.push_back(std::move(handler)); | |||
| } | |||
| template <typename Handler> | |||
| void setTimer(const TimerId timerId, const TimePoint expiration, Handler handler) | |||
| { | |||
| using namespace std; | |||
| mTimers[make_pair(move(expiration), timerId)] = move(handler); | |||
| } | |||
| void cancelTimer(const TimerId timerId); | |||
| // returns the time that the next timer in the subtree expires | |||
| TimePoint nextTimerExpiration(); | |||
| // triggers all timers in the subtree that expire at time t or before | |||
| void triggerTimersUntil(const TimePoint t); | |||
| private: | |||
| // returns true if some work was done, false if there was none to do | |||
| bool handlePending(); | |||
| // returns the time that the next timer from this node expires | |||
| TimePoint nextOwnTimerExpiration(); | |||
| // Traversal function over children that cleans up children that | |||
| // have been destroyed. | |||
| template <typename Fn> | |||
| void withChildren(Fn fn) | |||
| { | |||
| auto it = begin(mChildren); | |||
| while (it != end(mChildren)) | |||
| { | |||
| const auto childIt = it++; | |||
| auto pChild = childIt->lock(); | |||
| if (pChild) | |||
| { | |||
| fn(*pChild); | |||
| } | |||
| else | |||
| { | |||
| mChildren.erase(childIt); | |||
| } | |||
| } | |||
| } | |||
| using TimerHandler = std::function<void(TimerErrorCode)>; | |||
| using TimerMap = std::map<std::pair<TimePoint, TimerId>, TimerHandler>; | |||
| TimerMap mTimers; | |||
| std::list<std::function<void()>> mPendingHandlers; | |||
| std::list<std::weak_ptr<SchedulerTree>> mChildren; | |||
| }; | |||
| } // namespace serial_io | |||
| } // namespace test | |||
| } // namespace ableton | |||
| @@ -0,0 +1,83 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/platforms/AsioWrapper.hpp> | |||
| #include <functional> | |||
| namespace ableton | |||
| { | |||
| namespace test | |||
| { | |||
| namespace serial_io | |||
| { | |||
| struct Socket | |||
| { | |||
| using SendFn = std::function<std::size_t( | |||
| const uint8_t* const, const size_t, const asio::ip::udp::endpoint&)>; | |||
| struct ReceiveHandler | |||
| { | |||
| template <typename It> | |||
| void operator()(const asio::ip::udp::endpoint& from, const It begin, const It end) | |||
| { | |||
| std::vector<uint8_t> buffer{begin, end}; | |||
| mReceive(from, buffer); | |||
| } | |||
| std::function<const asio::ip::udp::endpoint&, const std::vector<uint8_t>&> mReceive; | |||
| }; | |||
| using ReceiveFn = std::function<void(ReceiveHandler)>; | |||
| Socket(SendFn sendFn, ReceiveFn receiveFn) | |||
| : mSendFn(std::move(sendFn)) | |||
| , mReceiveFn(std::move(receiveFn)) | |||
| { | |||
| } | |||
| std::size_t send( | |||
| const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) | |||
| { | |||
| return mSendFn(pData, numBytes, to); | |||
| } | |||
| template <typename Handler> | |||
| void receive(Handler handler) | |||
| { | |||
| mReceiveFn(ReceiveHandler{ | |||
| [handler](const asio::ip::udp::endpoint& from, const std::vector<uint8_t>& buffer) { | |||
| handler(from, begin(buffer), end(buffer)); | |||
| }}); | |||
| } | |||
| asio::ip::udp::endpoint endpoint() const | |||
| { | |||
| return asio::ip::udp::endpoint({}, 0); | |||
| } | |||
| SendFn mSendFn; | |||
| ReceiveFn mReceiveFn; | |||
| }; | |||
| } // namespace test | |||
| } // namespace platforms | |||
| } // namespace ableton | |||
| @@ -0,0 +1,110 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/test/serial_io/SchedulerTree.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace test | |||
| { | |||
| namespace serial_io | |||
| { | |||
| struct Timer | |||
| { | |||
| using ErrorCode = SchedulerTree::TimerErrorCode; | |||
| using TimePoint = SchedulerTree::TimePoint; | |||
| Timer(const SchedulerTree::TimerId timerId, | |||
| const TimePoint& now, | |||
| std::shared_ptr<SchedulerTree> pScheduler) | |||
| : mId(timerId) | |||
| , mNow(now) | |||
| , mpScheduler(std::move(pScheduler)) | |||
| { | |||
| } | |||
| ~Timer() | |||
| { | |||
| if (!mbMovedFrom) | |||
| { | |||
| cancel(); | |||
| } | |||
| } | |||
| Timer(const Timer&) = delete; | |||
| Timer(Timer&& rhs) | |||
| : mId(rhs.mId) | |||
| , mNow(rhs.mNow) | |||
| , mExpiration(std::move(rhs.mExpiration)) | |||
| , mpScheduler(std::move(rhs.mpScheduler)) | |||
| { | |||
| rhs.mbMovedFrom = true; | |||
| } | |||
| void expires_at(const TimePoint t) | |||
| { | |||
| if (t < mNow) | |||
| { | |||
| throw std::runtime_error("Setting timer in the past"); | |||
| } | |||
| else | |||
| { | |||
| cancel(); | |||
| mExpiration = t; | |||
| } | |||
| } | |||
| template <typename T, typename Rep> | |||
| void expires_from_now(std::chrono::duration<T, Rep> duration) | |||
| { | |||
| expires_at(mNow + duration); | |||
| } | |||
| void cancel() | |||
| { | |||
| auto pScheduler = mpScheduler.lock(); | |||
| pScheduler->cancelTimer(mId); | |||
| } | |||
| template <typename Handler> | |||
| void async_wait(Handler handler) | |||
| { | |||
| auto pScheduler = mpScheduler.lock(); | |||
| pScheduler->setTimer(mId, mExpiration, std::move(handler)); | |||
| } | |||
| TimePoint now() const | |||
| { | |||
| return mNow; | |||
| } | |||
| const SchedulerTree::TimerId mId; | |||
| const TimePoint& mNow; | |||
| TimePoint mExpiration; | |||
| std::weak_ptr<SchedulerTree> mpScheduler; | |||
| bool mbMovedFrom = false; | |||
| }; | |||
| } // namespace serial_io | |||
| } // namespace test | |||
| } // namespace ableton | |||
| @@ -0,0 +1,254 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace util | |||
| { | |||
| // Utility type for aiding in dependency injection. | |||
| // Base template and implementation for injected valued | |||
| template <typename T> | |||
| struct Injected | |||
| { | |||
| using type = T; | |||
| Injected() = default; | |||
| explicit Injected(T t) | |||
| : val(std::move(t)) | |||
| { | |||
| } | |||
| Injected(const Injected&) = default; | |||
| Injected& operator=(const Injected&) = default; | |||
| Injected(Injected&& rhs) | |||
| : val(std::move(rhs.val)) | |||
| { | |||
| } | |||
| Injected& operator=(Injected&& rhs) | |||
| { | |||
| val = std::move(rhs.val); | |||
| return *this; | |||
| } | |||
| T* operator->() | |||
| { | |||
| return &val; | |||
| } | |||
| const T* operator->() const | |||
| { | |||
| return &val; | |||
| } | |||
| T& operator*() | |||
| { | |||
| return val; | |||
| } | |||
| const T& operator*() const | |||
| { | |||
| return val; | |||
| } | |||
| T val; | |||
| }; | |||
| // Utility function for injecting values | |||
| template <typename T> | |||
| Injected<T> injectVal(T t) | |||
| { | |||
| return Injected<T>(std::move(t)); | |||
| } | |||
| // Specialization for injected references | |||
| template <typename T> | |||
| struct Injected<T&> | |||
| { | |||
| using type = T; | |||
| explicit Injected(T& t) | |||
| : ref(std::ref(t)) | |||
| { | |||
| } | |||
| Injected(const Injected&) = default; | |||
| Injected& operator=(const Injected&) = default; | |||
| Injected(Injected&& rhs) | |||
| : ref(std::move(rhs.ref)) | |||
| { | |||
| } | |||
| Injected& operator=(Injected&& rhs) | |||
| { | |||
| ref = std::move(rhs.ref); | |||
| return *this; | |||
| } | |||
| T* operator->() | |||
| { | |||
| return &ref.get(); | |||
| } | |||
| const T* operator->() const | |||
| { | |||
| return &ref.get(); | |||
| } | |||
| T& operator*() | |||
| { | |||
| return ref; | |||
| } | |||
| const T& operator*() const | |||
| { | |||
| return ref; | |||
| } | |||
| std::reference_wrapper<T> ref; | |||
| }; | |||
| // Utility function for injecting references | |||
| template <typename T> | |||
| Injected<T&> injectRef(T& t) | |||
| { | |||
| return Injected<T&>(t); | |||
| } | |||
| // Specialization for injected shared_ptr | |||
| template <typename T> | |||
| struct Injected<std::shared_ptr<T>> | |||
| { | |||
| using type = T; | |||
| explicit Injected(std::shared_ptr<T> pT) | |||
| : shared(std::move(pT)) | |||
| { | |||
| } | |||
| Injected(const Injected&) = default; | |||
| Injected& operator=(const Injected&) = default; | |||
| Injected(Injected&& rhs) | |||
| : shared(std::move(rhs.shared)) | |||
| { | |||
| } | |||
| Injected& operator=(Injected&& rhs) | |||
| { | |||
| shared = std::move(rhs.shared); | |||
| return *this; | |||
| } | |||
| T* operator->() | |||
| { | |||
| return shared.get(); | |||
| } | |||
| const T* operator->() const | |||
| { | |||
| return shared.get(); | |||
| } | |||
| T& operator*() | |||
| { | |||
| return *shared; | |||
| } | |||
| const T& operator*() const | |||
| { | |||
| return *shared; | |||
| } | |||
| std::shared_ptr<T> shared; | |||
| }; | |||
| // Utility function for injected shared_ptr | |||
| template <typename T> | |||
| Injected<std::shared_ptr<T>> injectShared(std::shared_ptr<T> shared) | |||
| { | |||
| return Injected<std::shared_ptr<T>>(std::move(shared)); | |||
| } | |||
| // Specialization for injected unique_ptr | |||
| template <typename T> | |||
| struct Injected<std::unique_ptr<T>> | |||
| { | |||
| using type = T; | |||
| explicit Injected(std::unique_ptr<T> pT) | |||
| : unique(std::move(pT)) | |||
| { | |||
| } | |||
| Injected(const Injected&) = default; | |||
| Injected& operator=(const Injected&) = default; | |||
| Injected(Injected&& rhs) | |||
| : unique(std::move(rhs.unique)) | |||
| { | |||
| } | |||
| Injected& operator=(Injected&& rhs) | |||
| { | |||
| unique = std::move(rhs.unique); | |||
| return *this; | |||
| } | |||
| T* operator->() | |||
| { | |||
| return unique.get(); | |||
| } | |||
| const T* operator->() const | |||
| { | |||
| return unique.get(); | |||
| } | |||
| T& operator*() | |||
| { | |||
| return *unique; | |||
| } | |||
| const T& operator*() const | |||
| { | |||
| return *unique; | |||
| } | |||
| std::unique_ptr<T> unique; | |||
| }; | |||
| // Utility function for injected unique_ptr | |||
| template <typename T> | |||
| Injected<std::unique_ptr<T>> injectUnique(std::unique_ptr<T> unique) | |||
| { | |||
| return Injected<std::unique_ptr<T>>(std::move(unique)); | |||
| } | |||
| } // namespace util | |||
| } // namespace ableton | |||
| @@ -0,0 +1,176 @@ | |||
| // Copyright: 2014, Ableton AG, Berlin, all rights reserved | |||
| #pragma once | |||
| #include <ableton/util/Injected.hpp> | |||
| #include <iostream> | |||
| #include <string> | |||
| namespace ableton | |||
| { | |||
| namespace util | |||
| { | |||
| // Null object for the Log concept | |||
| struct NullLog | |||
| { | |||
| template <typename T> | |||
| friend const NullLog& operator<<(const NullLog& log, const T&) | |||
| { | |||
| return log; | |||
| } | |||
| friend const NullLog& debug(const NullLog& log) | |||
| { | |||
| return log; | |||
| } | |||
| friend const NullLog& info(const NullLog& log) | |||
| { | |||
| return log; | |||
| } | |||
| friend const NullLog& warning(const NullLog& log) | |||
| { | |||
| return log; | |||
| } | |||
| friend const NullLog& error(const NullLog& log) | |||
| { | |||
| return log; | |||
| } | |||
| friend NullLog channel(const NullLog&, std::string) | |||
| { | |||
| return {}; | |||
| } | |||
| }; | |||
| // std streams-based log | |||
| struct StdLog | |||
| { | |||
| StdLog(std::string channelName = "") | |||
| : mChannelName(std::move(channelName)) | |||
| { | |||
| } | |||
| // Stream type used by std log to prepend the channel name to log messages | |||
| struct StdLogStream | |||
| { | |||
| StdLogStream(std::ostream& ioStream, const std::string& channelName) | |||
| : mpIoStream(&ioStream) | |||
| , mChannelName(channelName) | |||
| { | |||
| ioStream << "[" << mChannelName << "] "; | |||
| } | |||
| StdLogStream(StdLogStream&& rhs) | |||
| : mpIoStream(rhs.mpIoStream) | |||
| , mChannelName(rhs.mChannelName) | |||
| { | |||
| rhs.mpIoStream = nullptr; | |||
| } | |||
| ~StdLogStream() | |||
| { | |||
| if (mpIoStream) | |||
| { | |||
| (*mpIoStream) << "\n"; | |||
| } | |||
| } | |||
| template <typename T> | |||
| std::ostream& operator<<(const T& rhs) | |||
| { | |||
| (*mpIoStream) << rhs; | |||
| return *mpIoStream; | |||
| } | |||
| std::ostream* mpIoStream; | |||
| const std::string& mChannelName; | |||
| }; | |||
| friend StdLogStream debug(const StdLog& log) | |||
| { | |||
| return {std::clog, log.mChannelName}; | |||
| } | |||
| friend StdLogStream info(const StdLog& log) | |||
| { | |||
| return {std::clog, log.mChannelName}; | |||
| } | |||
| friend StdLogStream warning(const StdLog& log) | |||
| { | |||
| return {std::clog, log.mChannelName}; | |||
| } | |||
| friend StdLogStream error(const StdLog& log) | |||
| { | |||
| return {std::cerr, log.mChannelName}; | |||
| } | |||
| friend StdLog channel(const StdLog& log, const std::string& channelName) | |||
| { | |||
| auto compositeName = | |||
| log.mChannelName.empty() ? channelName : log.mChannelName + "::" + channelName; | |||
| return {std::move(compositeName)}; | |||
| } | |||
| std::string mChannelName; | |||
| }; | |||
| // Log adapter that adds timestamps | |||
| template <typename Log> | |||
| struct Timestamped | |||
| { | |||
| using InnerLog = typename util::Injected<Log>::type; | |||
| Timestamped() = default; | |||
| Timestamped(util::Injected<Log> log) | |||
| : mLog(std::move(log)) | |||
| { | |||
| } | |||
| util::Injected<Log> mLog; | |||
| friend decltype(debug(std::declval<InnerLog>())) debug(const Timestamped& log) | |||
| { | |||
| return log.logTimestamp(debug(*log.mLog)); | |||
| } | |||
| friend decltype(info(std::declval<InnerLog>())) info(const Timestamped& log) | |||
| { | |||
| return log.logTimestamp(info(*log.mLog)); | |||
| } | |||
| friend decltype(warning(std::declval<InnerLog>())) warning(const Timestamped& log) | |||
| { | |||
| return log.logTimestamp(warning(*log.mLog)); | |||
| } | |||
| friend decltype(error(std::declval<InnerLog>())) error(const Timestamped& log) | |||
| { | |||
| return log.logTimestamp(error(*log.mLog)); | |||
| } | |||
| friend Timestamped channel(const Timestamped& log, const std::string& channelName) | |||
| { | |||
| return {channel(*log.mLog, channelName)}; | |||
| } | |||
| template <typename Stream> | |||
| Stream logTimestamp(Stream&& streamRef) const | |||
| { | |||
| using namespace std::chrono; | |||
| Stream stream = std::forward<Stream>(streamRef); | |||
| stream << "|" | |||
| << duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() | |||
| << "ms| "; | |||
| return stream; | |||
| } | |||
| }; | |||
| } // namespace util | |||
| } // namespace ableton | |||
| @@ -0,0 +1,66 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <memory> | |||
| namespace ableton | |||
| { | |||
| namespace util | |||
| { | |||
| // A utility handler for passing to async functions that may call the | |||
| // handler past the lifetime of the wrapped delegate object. | |||
| // The need for this is particularly driven by boost::asio timer | |||
| // objects, which explicitly document that they may be called without | |||
| // an error code after they have been cancelled. This has led to | |||
| // several crashes. This handler wrapper implements a useful idiom for | |||
| // avoiding this problem. | |||
| template <typename Delegate> | |||
| struct SafeAsyncHandler | |||
| { | |||
| SafeAsyncHandler(const std::shared_ptr<Delegate>& pDelegate) | |||
| : mpDelegate(pDelegate) | |||
| { | |||
| } | |||
| template <typename... T> | |||
| void operator()(T&&... t) const | |||
| { | |||
| std::shared_ptr<Delegate> pDelegate = mpDelegate.lock(); | |||
| if (pDelegate) | |||
| { | |||
| (*pDelegate)(std::forward<T>(t)...); | |||
| } | |||
| } | |||
| std::weak_ptr<Delegate> mpDelegate; | |||
| }; | |||
| // Factory function for easily wrapping a shared_ptr to a handler | |||
| template <typename Delegate> | |||
| SafeAsyncHandler<Delegate> makeAsyncSafe(const std::shared_ptr<Delegate>& pDelegate) | |||
| { | |||
| return {pDelegate}; | |||
| } | |||
| } // namespace util | |||
| } // namespace ableton | |||
| @@ -0,0 +1,52 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <chrono> | |||
| namespace ableton | |||
| { | |||
| namespace util | |||
| { | |||
| /*! Utility type to convert between time and sample index given the | |||
| * time at the beginning of a buffer and the sample rate. | |||
| */ | |||
| struct SampleTiming | |||
| { | |||
| double sampleAtTime(std::chrono::microseconds time) const | |||
| { | |||
| using namespace std::chrono; | |||
| return duration_cast<duration<double>>(time - mBufferBegin).count() * mSampleRate; | |||
| } | |||
| std::chrono::microseconds timeAtSample(const double sample) const | |||
| { | |||
| using namespace std::chrono; | |||
| return mBufferBegin | |||
| + duration_cast<microseconds>(duration<double>{sample / mSampleRate}); | |||
| } | |||
| std::chrono::microseconds mBufferBegin; | |||
| double mSampleRate; | |||
| }; | |||
| } // util | |||
| } // ableton | |||
| @@ -0,0 +1,114 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <ableton/util/test/Timer.hpp> | |||
| namespace ableton | |||
| { | |||
| namespace util | |||
| { | |||
| namespace test | |||
| { | |||
| struct IoService | |||
| { | |||
| // Wrapper around the internal util::test::Timer in the list | |||
| struct Timer | |||
| { | |||
| using ErrorCode = test::Timer::ErrorCode; | |||
| using TimePoint = test::Timer::TimePoint; | |||
| Timer(util::test::Timer* pTimer) | |||
| : mpTimer(pTimer) | |||
| { | |||
| } | |||
| void expires_at(std::chrono::system_clock::time_point t) | |||
| { | |||
| mpTimer->expires_at(t); | |||
| } | |||
| template <typename T, typename Rep> | |||
| void expires_from_now(std::chrono::duration<T, Rep> duration) | |||
| { | |||
| mpTimer->expires_from_now(duration); | |||
| } | |||
| ErrorCode cancel() | |||
| { | |||
| return mpTimer->cancel(); | |||
| } | |||
| template <typename Handler> | |||
| void async_wait(Handler handler) | |||
| { | |||
| mpTimer->async_wait(std::move(handler)); | |||
| } | |||
| TimePoint now() const | |||
| { | |||
| return mpTimer->now(); | |||
| } | |||
| util::test::Timer* mpTimer; | |||
| }; | |||
| IoService() = default; | |||
| Timer makeTimer() | |||
| { | |||
| mTimers.emplace_back(); | |||
| return Timer{&mTimers.back()}; | |||
| } | |||
| template <typename Handler> | |||
| void post(Handler handler) | |||
| { | |||
| mHandlers.emplace_back(std::move(handler)); | |||
| } | |||
| template <typename T, typename Rep> | |||
| void advance(std::chrono::duration<T, Rep> duration) | |||
| { | |||
| runHandlers(); | |||
| for (auto& timer : mTimers) | |||
| { | |||
| timer.advance(duration); | |||
| } | |||
| } | |||
| void runHandlers() | |||
| { | |||
| for (auto& handler : mHandlers) | |||
| { | |||
| handler(); | |||
| } | |||
| mHandlers.clear(); | |||
| } | |||
| std::vector<std::function<void()>> mHandlers; | |||
| std::vector<util::test::Timer> mTimers; | |||
| }; | |||
| } // namespace test | |||
| } // namespace util | |||
| } // namespace ableton | |||
| @@ -0,0 +1,96 @@ | |||
| /* Copyright 2016, Ableton AG, Berlin. All rights reserved. | |||
| * | |||
| * This program is free software: you can redistribute it and/or modify | |||
| * it under the terms of the GNU General Public License as published by | |||
| * the Free Software Foundation, either version 2 of the License, or | |||
| * (at your option) any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * You should have received a copy of the GNU General Public License | |||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| * | |||
| * If you would like to incorporate Link into a proprietary software application, | |||
| * please contact <link-devs@ableton.com>. | |||
| */ | |||
| #pragma once | |||
| #include <chrono> | |||
| #include <functional> | |||
| namespace ableton | |||
| { | |||
| namespace util | |||
| { | |||
| namespace test | |||
| { | |||
| struct Timer | |||
| { | |||
| using ErrorCode = int; | |||
| using TimePoint = std::chrono::system_clock::time_point; | |||
| // Initialize timer with an arbitrary large value to simulate the | |||
| // time_since_epoch of a real clock. | |||
| Timer() | |||
| : mNow{std::chrono::milliseconds{123456789}} | |||
| { | |||
| } | |||
| void expires_at(std::chrono::system_clock::time_point t) | |||
| { | |||
| cancel(); | |||
| mFireAt = std::move(t); | |||
| } | |||
| template <typename T, typename Rep> | |||
| void expires_from_now(std::chrono::duration<T, Rep> duration) | |||
| { | |||
| cancel(); | |||
| mFireAt = now() + duration; | |||
| } | |||
| ErrorCode cancel() | |||
| { | |||
| if (mHandler) | |||
| { | |||
| mHandler(1); // call existing handler with truthy error code | |||
| } | |||
| mHandler = nullptr; | |||
| return 0; | |||
| } | |||
| template <typename Handler> | |||
| void async_wait(Handler handler) | |||
| { | |||
| mHandler = [handler](ErrorCode ec) { handler(ec); }; | |||
| } | |||
| std::chrono::system_clock::time_point now() const | |||
| { | |||
| return mNow; | |||
| } | |||
| template <typename T, typename Rep> | |||
| void advance(std::chrono::duration<T, Rep> duration) | |||
| { | |||
| mNow += duration; | |||
| if (mHandler && mFireAt < mNow) | |||
| { | |||
| mHandler(0); | |||
| mHandler = nullptr; | |||
| } | |||
| } | |||
| std::function<void(ErrorCode)> mHandler; | |||
| std::chrono::system_clock::time_point mFireAt; | |||
| std::chrono::system_clock::time_point mNow; | |||
| }; | |||
| } // namespace test | |||
| } // namespace util | |||
| } // namespace ableton | |||
| @@ -0,0 +1,144 @@ | |||
| // | |||
| // asio.hpp | |||
| // ~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_HPP | |||
| #define ASIO_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/associated_allocator.hpp" | |||
| #include "asio/associated_executor.hpp" | |||
| #include "asio/async_result.hpp" | |||
| #include "asio/basic_datagram_socket.hpp" | |||
| #include "asio/basic_deadline_timer.hpp" | |||
| #include "asio/basic_io_object.hpp" | |||
| #include "asio/basic_raw_socket.hpp" | |||
| #include "asio/basic_seq_packet_socket.hpp" | |||
| #include "asio/basic_serial_port.hpp" | |||
| #include "asio/basic_signal_set.hpp" | |||
| #include "asio/basic_socket_acceptor.hpp" | |||
| #include "asio/basic_socket_iostream.hpp" | |||
| #include "asio/basic_socket_streambuf.hpp" | |||
| #include "asio/basic_stream_socket.hpp" | |||
| #include "asio/basic_streambuf.hpp" | |||
| #include "asio/basic_waitable_timer.hpp" | |||
| #include "asio/bind_executor.hpp" | |||
| #include "asio/buffer.hpp" | |||
| #include "asio/buffered_read_stream_fwd.hpp" | |||
| #include "asio/buffered_read_stream.hpp" | |||
| #include "asio/buffered_stream_fwd.hpp" | |||
| #include "asio/buffered_stream.hpp" | |||
| #include "asio/buffered_write_stream_fwd.hpp" | |||
| #include "asio/buffered_write_stream.hpp" | |||
| #include "asio/buffers_iterator.hpp" | |||
| #include "asio/completion_condition.hpp" | |||
| #include "asio/connect.hpp" | |||
| #include "asio/coroutine.hpp" | |||
| #include "asio/datagram_socket_service.hpp" | |||
| #include "asio/deadline_timer_service.hpp" | |||
| #include "asio/deadline_timer.hpp" | |||
| #include "asio/defer.hpp" | |||
| #include "asio/dispatch.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/error_code.hpp" | |||
| #include "asio/execution_context.hpp" | |||
| #include "asio/executor.hpp" | |||
| #include "asio/executor_work_guard.hpp" | |||
| #include "asio/generic/basic_endpoint.hpp" | |||
| #include "asio/generic/datagram_protocol.hpp" | |||
| #include "asio/generic/raw_protocol.hpp" | |||
| #include "asio/generic/seq_packet_protocol.hpp" | |||
| #include "asio/generic/stream_protocol.hpp" | |||
| #include "asio/handler_alloc_hook.hpp" | |||
| #include "asio/handler_continuation_hook.hpp" | |||
| #include "asio/handler_invoke_hook.hpp" | |||
| #include "asio/handler_type.hpp" | |||
| #include "asio/io_context.hpp" | |||
| #include "asio/io_context_strand.hpp" | |||
| #include "asio/io_service.hpp" | |||
| #include "asio/io_service_strand.hpp" | |||
| #include "asio/ip/address.hpp" | |||
| #include "asio/ip/address_v4.hpp" | |||
| #include "asio/ip/address_v4_iterator.hpp" | |||
| #include "asio/ip/address_v4_range.hpp" | |||
| #include "asio/ip/address_v6.hpp" | |||
| #include "asio/ip/address_v6_iterator.hpp" | |||
| #include "asio/ip/address_v6_range.hpp" | |||
| #include "asio/ip/bad_address_cast.hpp" | |||
| #include "asio/ip/basic_endpoint.hpp" | |||
| #include "asio/ip/basic_resolver.hpp" | |||
| #include "asio/ip/basic_resolver_entry.hpp" | |||
| #include "asio/ip/basic_resolver_iterator.hpp" | |||
| #include "asio/ip/basic_resolver_query.hpp" | |||
| #include "asio/ip/host_name.hpp" | |||
| #include "asio/ip/icmp.hpp" | |||
| #include "asio/ip/multicast.hpp" | |||
| #include "asio/ip/resolver_base.hpp" | |||
| #include "asio/ip/resolver_query_base.hpp" | |||
| #include "asio/ip/resolver_service.hpp" | |||
| #include "asio/ip/tcp.hpp" | |||
| #include "asio/ip/udp.hpp" | |||
| #include "asio/ip/unicast.hpp" | |||
| #include "asio/ip/v6_only.hpp" | |||
| #include "asio/is_executor.hpp" | |||
| #include "asio/is_read_buffered.hpp" | |||
| #include "asio/is_write_buffered.hpp" | |||
| #include "asio/local/basic_endpoint.hpp" | |||
| #include "asio/local/connect_pair.hpp" | |||
| #include "asio/local/datagram_protocol.hpp" | |||
| #include "asio/local/stream_protocol.hpp" | |||
| #include "asio/placeholders.hpp" | |||
| #include "asio/posix/basic_descriptor.hpp" | |||
| #include "asio/posix/basic_stream_descriptor.hpp" | |||
| #include "asio/posix/descriptor_base.hpp" | |||
| #include "asio/posix/stream_descriptor.hpp" | |||
| #include "asio/posix/stream_descriptor_service.hpp" | |||
| #include "asio/post.hpp" | |||
| #include "asio/raw_socket_service.hpp" | |||
| #include "asio/read.hpp" | |||
| #include "asio/read_at.hpp" | |||
| #include "asio/read_until.hpp" | |||
| #include "asio/seq_packet_socket_service.hpp" | |||
| #include "asio/serial_port.hpp" | |||
| #include "asio/serial_port_base.hpp" | |||
| #include "asio/serial_port_service.hpp" | |||
| #include "asio/signal_set.hpp" | |||
| #include "asio/signal_set_service.hpp" | |||
| #include "asio/socket_acceptor_service.hpp" | |||
| #include "asio/socket_base.hpp" | |||
| #include "asio/strand.hpp" | |||
| #include "asio/stream_socket_service.hpp" | |||
| #include "asio/streambuf.hpp" | |||
| #include "asio/system_error.hpp" | |||
| #include "asio/system_executor.hpp" | |||
| #include "asio/thread.hpp" | |||
| #include "asio/thread_pool.hpp" | |||
| #include "asio/time_traits.hpp" | |||
| #include "asio/uses_executor.hpp" | |||
| #include "asio/version.hpp" | |||
| #include "asio/wait_traits.hpp" | |||
| #include "asio/waitable_timer_service.hpp" | |||
| #include "asio/windows/basic_handle.hpp" | |||
| #include "asio/windows/basic_object_handle.hpp" | |||
| #include "asio/windows/basic_random_access_handle.hpp" | |||
| #include "asio/windows/basic_stream_handle.hpp" | |||
| #include "asio/windows/object_handle.hpp" | |||
| #include "asio/windows/object_handle_service.hpp" | |||
| #include "asio/windows/overlapped_ptr.hpp" | |||
| #include "asio/windows/random_access_handle.hpp" | |||
| #include "asio/windows/random_access_handle_service.hpp" | |||
| #include "asio/windows/stream_handle.hpp" | |||
| #include "asio/windows/stream_handle_service.hpp" | |||
| #include "asio/write.hpp" | |||
| #include "asio/write_at.hpp" | |||
| #endif // ASIO_HPP | |||
| @@ -0,0 +1,123 @@ | |||
| // | |||
| // associated_allocator.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_ASSOCIATED_ALLOCATOR_HPP | |||
| #define ASIO_ASSOCIATED_ALLOCATOR_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <memory> | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| namespace detail { | |||
| template <typename> | |||
| struct associated_allocator_check | |||
| { | |||
| typedef void type; | |||
| }; | |||
| template <typename T, typename E, typename = void> | |||
| struct associated_allocator_impl | |||
| { | |||
| typedef E type; | |||
| static type get(const T&, const E& e) ASIO_NOEXCEPT | |||
| { | |||
| return e; | |||
| } | |||
| }; | |||
| template <typename T, typename E> | |||
| struct associated_allocator_impl<T, E, | |||
| typename associated_allocator_check<typename T::allocator_type>::type> | |||
| { | |||
| typedef typename T::allocator_type type; | |||
| static type get(const T& t, const E&) ASIO_NOEXCEPT | |||
| { | |||
| return t.get_allocator(); | |||
| } | |||
| }; | |||
| } // namespace detail | |||
| /// Traits type used to obtain the allocator associated with an object. | |||
| /** | |||
| * A program may specialise this traits type if the @c T template parameter in | |||
| * the specialisation is a user-defined type. The template parameter @c | |||
| * Allocator shall be a type meeting the Allocator requirements. | |||
| * | |||
| * Specialisations shall meet the following requirements, where @c t is a const | |||
| * reference to an object of type @c T, and @c a is an object of type @c | |||
| * Allocator. | |||
| * | |||
| * @li Provide a nested typedef @c type that identifies a type meeting the | |||
| * Allocator requirements. | |||
| * | |||
| * @li Provide a noexcept static member function named @c get, callable as @c | |||
| * get(t) and with return type @c type. | |||
| * | |||
| * @li Provide a noexcept static member function named @c get, callable as @c | |||
| * get(t,a) and with return type @c type. | |||
| */ | |||
| template <typename T, typename Allocator = std::allocator<void> > | |||
| struct associated_allocator | |||
| { | |||
| /// If @c T has a nested type @c allocator_type, <tt>T::allocator_type</tt>. | |||
| /// Otherwise @c Allocator. | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| typedef see_below type; | |||
| #else // defined(GENERATING_DOCUMENTATION) | |||
| typedef typename detail::associated_allocator_impl<T, Allocator>::type type; | |||
| #endif // defined(GENERATING_DOCUMENTATION) | |||
| /// If @c T has a nested type @c allocator_type, returns | |||
| /// <tt>t.get_allocator()</tt>. Otherwise returns @c a. | |||
| static type get(const T& t, | |||
| const Allocator& a = Allocator()) ASIO_NOEXCEPT | |||
| { | |||
| return detail::associated_allocator_impl<T, Allocator>::get(t, a); | |||
| } | |||
| }; | |||
| /// Helper function to obtain an object's associated allocator. | |||
| /** | |||
| * @returns <tt>associated_allocator<T>::get(t)</tt> | |||
| */ | |||
| template <typename T> | |||
| inline typename associated_allocator<T>::type | |||
| get_associated_allocator(const T& t) ASIO_NOEXCEPT | |||
| { | |||
| return associated_allocator<T>::get(t); | |||
| } | |||
| /// Helper function to obtain an object's associated allocator. | |||
| /** | |||
| * @returns <tt>associated_allocator<T, Allocator>::get(t, a)</tt> | |||
| */ | |||
| template <typename T, typename Allocator> | |||
| inline typename associated_allocator<T, Allocator>::type | |||
| get_associated_allocator(const T& t, const Allocator& a) ASIO_NOEXCEPT | |||
| { | |||
| return associated_allocator<T, Allocator>::get(t, a); | |||
| } | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_ASSOCIATED_ALLOCATOR_HPP | |||
| @@ -0,0 +1,142 @@ | |||
| // | |||
| // associated_executor.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_ASSOCIATED_EXECUTOR_HPP | |||
| #define ASIO_ASSOCIATED_EXECUTOR_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/is_executor.hpp" | |||
| #include "asio/system_executor.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| namespace detail { | |||
| template <typename> | |||
| struct associated_executor_check | |||
| { | |||
| typedef void type; | |||
| }; | |||
| template <typename T, typename E, typename = void> | |||
| struct associated_executor_impl | |||
| { | |||
| typedef E type; | |||
| static type get(const T&, const E& e) ASIO_NOEXCEPT | |||
| { | |||
| return e; | |||
| } | |||
| }; | |||
| template <typename T, typename E> | |||
| struct associated_executor_impl<T, E, | |||
| typename associated_executor_check<typename T::executor_type>::type> | |||
| { | |||
| typedef typename T::executor_type type; | |||
| static type get(const T& t, const E&) ASIO_NOEXCEPT | |||
| { | |||
| return t.get_executor(); | |||
| } | |||
| }; | |||
| } // namespace detail | |||
| /// Traits type used to obtain the executor associated with an object. | |||
| /** | |||
| * A program may specialise this traits type if the @c T template parameter in | |||
| * the specialisation is a user-defined type. The template parameter @c | |||
| * Executor shall be a type meeting the Executor requirements. | |||
| * | |||
| * Specialisations shall meet the following requirements, where @c t is a const | |||
| * reference to an object of type @c T, and @c e is an object of type @c | |||
| * Executor. | |||
| * | |||
| * @li Provide a nested typedef @c type that identifies a type meeting the | |||
| * Executor requirements. | |||
| * | |||
| * @li Provide a noexcept static member function named @c get, callable as @c | |||
| * get(t) and with return type @c type. | |||
| * | |||
| * @li Provide a noexcept static member function named @c get, callable as @c | |||
| * get(t,e) and with return type @c type. | |||
| */ | |||
| template <typename T, typename Executor = system_executor> | |||
| struct associated_executor | |||
| { | |||
| /// If @c T has a nested type @c executor_type, <tt>T::executor_type</tt>. | |||
| /// Otherwise @c Executor. | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| typedef see_below type; | |||
| #else // defined(GENERATING_DOCUMENTATION) | |||
| typedef typename detail::associated_executor_impl<T, Executor>::type type; | |||
| #endif // defined(GENERATING_DOCUMENTATION) | |||
| /// If @c T has a nested type @c executor_type, returns | |||
| /// <tt>t.get_executor()</tt>. Otherwise returns @c ex. | |||
| static type get(const T& t, | |||
| const Executor& ex = Executor()) ASIO_NOEXCEPT | |||
| { | |||
| return detail::associated_executor_impl<T, Executor>::get(t, ex); | |||
| } | |||
| }; | |||
| /// Helper function to obtain an object's associated executor. | |||
| /** | |||
| * @returns <tt>associated_executor<T>::get(t)</tt> | |||
| */ | |||
| template <typename T> | |||
| inline typename associated_executor<T>::type | |||
| get_associated_executor(const T& t) ASIO_NOEXCEPT | |||
| { | |||
| return associated_executor<T>::get(t); | |||
| } | |||
| /// Helper function to obtain an object's associated executor. | |||
| /** | |||
| * @returns <tt>associated_executor<T, Executor>::get(t, ex)</tt> | |||
| */ | |||
| template <typename T, typename Executor> | |||
| inline typename associated_executor<T, Executor>::type | |||
| get_associated_executor(const T& t, const Executor& ex, | |||
| typename enable_if<is_executor< | |||
| Executor>::value>::type* = 0) ASIO_NOEXCEPT | |||
| { | |||
| return associated_executor<T, Executor>::get(t, ex); | |||
| } | |||
| /// Helper function to obtain an object's associated executor. | |||
| /** | |||
| * @returns <tt>associated_executor<T, typename | |||
| * ExecutionContext::executor_type>::get(t, ctx.get_executor())</tt> | |||
| */ | |||
| template <typename T, typename ExecutionContext> | |||
| inline typename associated_executor<T, | |||
| typename ExecutionContext::executor_type>::type | |||
| get_associated_executor(const T& t, ExecutionContext& ctx, | |||
| typename enable_if<is_convertible<ExecutionContext&, | |||
| execution_context&>::value>::type* = 0) ASIO_NOEXCEPT | |||
| { | |||
| return associated_executor<T, | |||
| typename ExecutionContext::executor_type>::get(t, ctx.get_executor()); | |||
| } | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_ASSOCIATED_EXECUTOR_HPP | |||
| @@ -0,0 +1,125 @@ | |||
| // | |||
| // async_result.hpp | |||
| // ~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_ASYNC_RESULT_HPP | |||
| #define ASIO_ASYNC_RESULT_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/handler_type.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// An interface for customising the behaviour of an initiating function. | |||
| /** | |||
| * This template may be specialised for user-defined handler types. | |||
| */ | |||
| template <typename Handler> | |||
| class async_result | |||
| { | |||
| public: | |||
| /// The return type of the initiating function. | |||
| typedef void type; | |||
| /// Construct an async result from a given handler. | |||
| /** | |||
| * When using a specalised async_result, the constructor has an opportunity | |||
| * to initialise some state associated with the handler, which is then | |||
| * returned from the initiating function. | |||
| */ | |||
| explicit async_result(Handler&) | |||
| { | |||
| } | |||
| /// Obtain the value to be returned from the initiating function. | |||
| type get() | |||
| { | |||
| } | |||
| }; | |||
| /// Helper template to deduce the real type of a handler, capture a local copy | |||
| /// of the handler, and then create an async_result for the handler. | |||
| template <typename Handler, typename Signature> | |||
| struct async_completion | |||
| { | |||
| /// The real handler type to be used for the asynchronous operation. | |||
| typedef typename asio::handler_type< | |||
| Handler, Signature>::type handler_type; | |||
| /// Constructor. | |||
| /** | |||
| * The constructor creates the concrete handler and makes the link between | |||
| * the handler and the asynchronous result. | |||
| */ | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| explicit async_completion( | |||
| Handler& orig_handler) | |||
| : handler(static_cast<typename conditional< | |||
| is_same<Handler, handler_type>::value, | |||
| handler_type&, Handler&&>::type>(orig_handler)), | |||
| result(handler) | |||
| { | |||
| } | |||
| #else // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| explicit async_completion(const Handler& orig_handler) | |||
| : handler(orig_handler), | |||
| result(handler) | |||
| { | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// A copy of, or reference to, a real handler object. | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| typename conditional< | |||
| is_same<Handler, handler_type>::value, | |||
| handler_type&, handler_type>::type handler; | |||
| #else // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| typename asio::handler_type<Handler, Signature>::type handler; | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// The result of the asynchronous operation's initiating function. | |||
| async_result<typename asio::handler_type< | |||
| Handler, Signature>::type> result; | |||
| }; | |||
| namespace detail { | |||
| template <typename Handler, typename Signature> | |||
| struct async_result_type_helper | |||
| { | |||
| typedef typename async_result< | |||
| typename handler_type<Handler, Signature>::type | |||
| >::type type; | |||
| }; | |||
| } // namespace detail | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| # define ASIO_INITFN_RESULT_TYPE(h, sig) \ | |||
| void_or_deduced | |||
| #elif defined(_MSC_VER) && (_MSC_VER < 1500) | |||
| # define ASIO_INITFN_RESULT_TYPE(h, sig) \ | |||
| typename ::asio::detail::async_result_type_helper<h, sig>::type | |||
| #else | |||
| # define ASIO_INITFN_RESULT_TYPE(h, sig) \ | |||
| typename ::asio::async_result< \ | |||
| typename ::asio::handler_type<h, sig>::type>::type | |||
| #endif | |||
| #endif // ASIO_ASYNC_RESULT_HPP | |||
| @@ -0,0 +1,945 @@ | |||
| // | |||
| // basic_datagram_socket.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_DATAGRAM_SOCKET_HPP | |||
| #define ASIO_BASIC_DATAGRAM_SOCKET_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/basic_socket.hpp" | |||
| #include "asio/datagram_socket_service.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides datagram-oriented socket functionality. | |||
| /** | |||
| * The basic_datagram_socket class template provides asynchronous and blocking | |||
| * datagram-oriented socket functionality. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| */ | |||
| template <typename Protocol, | |||
| typename DatagramSocketService = datagram_socket_service<Protocol> > | |||
| class basic_datagram_socket | |||
| : public basic_socket<Protocol, DatagramSocketService> | |||
| { | |||
| public: | |||
| /// The native representation of a socket. | |||
| typedef typename DatagramSocketService::native_handle_type native_handle_type; | |||
| /// The protocol type. | |||
| typedef Protocol protocol_type; | |||
| /// The endpoint type. | |||
| typedef typename Protocol::endpoint endpoint_type; | |||
| /// Construct a basic_datagram_socket without opening it. | |||
| /** | |||
| * This constructor creates a datagram socket without opening it. The open() | |||
| * function must be called before data can be sent or received on the socket. | |||
| * | |||
| * @param io_context The io_context object that the datagram socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| */ | |||
| explicit basic_datagram_socket(asio::io_context& io_context) | |||
| : basic_socket<Protocol, DatagramSocketService>(io_context) | |||
| { | |||
| } | |||
| /// Construct and open a basic_datagram_socket. | |||
| /** | |||
| * This constructor creates and opens a datagram socket. | |||
| * | |||
| * @param io_context The io_context object that the datagram socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_datagram_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol) | |||
| : basic_socket<Protocol, DatagramSocketService>(io_context, protocol) | |||
| { | |||
| } | |||
| /// Construct a basic_datagram_socket, opening it and binding it to the given | |||
| /// local endpoint. | |||
| /** | |||
| * This constructor creates a datagram socket and automatically opens it bound | |||
| * to the specified endpoint on the local machine. The protocol used is the | |||
| * protocol associated with the given endpoint. | |||
| * | |||
| * @param io_context The io_context object that the datagram socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| * | |||
| * @param endpoint An endpoint on the local machine to which the datagram | |||
| * socket will be bound. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_datagram_socket(asio::io_context& io_context, | |||
| const endpoint_type& endpoint) | |||
| : basic_socket<Protocol, DatagramSocketService>(io_context, endpoint) | |||
| { | |||
| } | |||
| /// Construct a basic_datagram_socket on an existing native socket. | |||
| /** | |||
| * This constructor creates a datagram socket object to hold an existing | |||
| * native socket. | |||
| * | |||
| * @param io_context The io_context object that the datagram socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @param native_socket The new underlying socket implementation. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_datagram_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol, const native_handle_type& native_socket) | |||
| : basic_socket<Protocol, DatagramSocketService>( | |||
| io_context, protocol, native_socket) | |||
| { | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move-construct a basic_datagram_socket from another. | |||
| /** | |||
| * This constructor moves a datagram socket from one object to another. | |||
| * | |||
| * @param other The other basic_datagram_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_datagram_socket(io_context&) constructor. | |||
| */ | |||
| basic_datagram_socket(basic_datagram_socket&& other) | |||
| : basic_socket<Protocol, DatagramSocketService>( | |||
| ASIO_MOVE_CAST(basic_datagram_socket)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_datagram_socket from another. | |||
| /** | |||
| * This assignment operator moves a datagram socket from one object to | |||
| * another. | |||
| * | |||
| * @param other The other basic_datagram_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_datagram_socket(io_context&) constructor. | |||
| */ | |||
| basic_datagram_socket& operator=(basic_datagram_socket&& other) | |||
| { | |||
| basic_socket<Protocol, DatagramSocketService>::operator=( | |||
| ASIO_MOVE_CAST(basic_datagram_socket)(other)); | |||
| return *this; | |||
| } | |||
| /// Move-construct a basic_datagram_socket from a socket of another protocol | |||
| /// type. | |||
| /** | |||
| * This constructor moves a datagram socket from one object to another. | |||
| * | |||
| * @param other The other basic_datagram_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_datagram_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename DatagramSocketService1> | |||
| basic_datagram_socket( | |||
| basic_datagram_socket<Protocol1, DatagramSocketService1>&& other, | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value>::type* = 0) | |||
| : basic_socket<Protocol, DatagramSocketService>( | |||
| ASIO_MOVE_CAST2(basic_datagram_socket< | |||
| Protocol1, DatagramSocketService1>)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_datagram_socket from a socket of another protocol | |||
| /// type. | |||
| /** | |||
| * This assignment operator moves a datagram socket from one object to | |||
| * another. | |||
| * | |||
| * @param other The other basic_datagram_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_datagram_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename DatagramSocketService1> | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value, | |||
| basic_datagram_socket>::type& operator=( | |||
| basic_datagram_socket<Protocol1, DatagramSocketService1>&& other) | |||
| { | |||
| basic_socket<Protocol, DatagramSocketService>::operator=( | |||
| ASIO_MOVE_CAST2(basic_datagram_socket< | |||
| Protocol1, DatagramSocketService1>)(other)); | |||
| return *this; | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Send some data on a connected socket. | |||
| /** | |||
| * This function is used to send data on the datagram socket. The function | |||
| * call will block until the data has been sent successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One ore more data buffers to be sent on the socket. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The send operation can only be used with a connected socket. Use | |||
| * the send_to function to send data on an unconnected datagram socket. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code socket.send(asio::buffer(data, size)); @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "send"); | |||
| return s; | |||
| } | |||
| /// Send some data on a connected socket. | |||
| /** | |||
| * This function is used to send data on the datagram socket. The function | |||
| * call will block until the data has been sent successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One ore more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The send operation can only be used with a connected socket. Use | |||
| * the send_to function to send data on an unconnected datagram socket. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| asio::detail::throw_error(ec, "send"); | |||
| return s; | |||
| } | |||
| /// Send some data on a connected socket. | |||
| /** | |||
| * This function is used to send data on the datagram socket. The function | |||
| * call will block until the data has been sent successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @note The send operation can only be used with a connected socket. Use | |||
| * the send_to function to send data on an unconnected datagram socket. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| } | |||
| /// Start an asynchronous send on a connected socket. | |||
| /** | |||
| * This function is used to asynchronously send data on the datagram socket. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. Although | |||
| * the buffers object may be copied as necessary, ownership of the underlying | |||
| * memory blocks is retained by the caller, which must guarantee that they | |||
| * remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_send operation can only be used with a connected socket. | |||
| * Use the async_send_to function to send data on an unconnected datagram | |||
| * socket. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.async_send(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send(this->get_implementation(), | |||
| buffers, 0, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous send on a connected socket. | |||
| /** | |||
| * This function is used to asynchronously send data on the datagram socket. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. Although | |||
| * the buffers object may be copied as necessary, ownership of the underlying | |||
| * memory blocks is retained by the caller, which must guarantee that they | |||
| * remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_send operation can only be used with a connected socket. | |||
| * Use the async_send_to function to send data on an unconnected datagram | |||
| * socket. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send(this->get_implementation(), | |||
| buffers, flags, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Send a datagram to the specified endpoint. | |||
| /** | |||
| * This function is used to send a datagram to the specified remote endpoint. | |||
| * The function call will block until the data has been sent successfully or | |||
| * an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * asio::ip::udp::endpoint destination( | |||
| * asio::ip::address::from_string("1.2.3.4"), 12345); | |||
| * socket.send_to(asio::buffer(data, size), destination); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send_to( | |||
| this->get_implementation(), buffers, destination, 0, ec); | |||
| asio::detail::throw_error(ec, "send_to"); | |||
| return s; | |||
| } | |||
| /// Send a datagram to the specified endpoint. | |||
| /** | |||
| * This function is used to send a datagram to the specified remote endpoint. | |||
| * The function call will block until the data has been sent successfully or | |||
| * an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send_to( | |||
| this->get_implementation(), buffers, destination, flags, ec); | |||
| asio::detail::throw_error(ec, "send_to"); | |||
| return s; | |||
| } | |||
| /// Send a datagram to the specified endpoint. | |||
| /** | |||
| * This function is used to send a datagram to the specified remote endpoint. | |||
| * The function call will block until the data has been sent successfully or | |||
| * an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, socket_base::message_flags flags, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().send_to(this->get_implementation(), | |||
| buffers, destination, flags, ec); | |||
| } | |||
| /// Start an asynchronous send. | |||
| /** | |||
| * This function is used to asynchronously send a datagram to the specified | |||
| * remote endpoint. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * Copies will be made of the endpoint as required. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * asio::ip::udp::endpoint destination( | |||
| * asio::ip::address::from_string("1.2.3.4"), 12345); | |||
| * socket.async_send_to( | |||
| * asio::buffer(data, size), destination, handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send_to( | |||
| this->get_implementation(), buffers, destination, 0, | |||
| ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous send. | |||
| /** | |||
| * This function is used to asynchronously send a datagram to the specified | |||
| * remote endpoint. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * Copies will be made of the endpoint as required. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send_to( | |||
| this->get_implementation(), buffers, destination, flags, | |||
| ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the datagram socket. The function | |||
| * call will block until data has been received successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The receive operation can only be used with a connected socket. Use | |||
| * the receive_from function to receive data on an unconnected datagram | |||
| * socket. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code socket.receive(asio::buffer(data, size)); @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the datagram socket. The function | |||
| * call will block until data has been received successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The receive operation can only be used with a connected socket. Use | |||
| * the receive_from function to receive data on an unconnected datagram | |||
| * socket. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the datagram socket. The function | |||
| * call will block until data has been received successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @note The receive operation can only be used with a connected socket. Use | |||
| * the receive_from function to receive data on an unconnected datagram | |||
| * socket. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().receive( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| } | |||
| /// Start an asynchronous receive on a connected socket. | |||
| /** | |||
| * This function is used to asynchronously receive data from the datagram | |||
| * socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_receive operation can only be used with a connected socket. | |||
| * Use the async_receive_from function to receive data on an unconnected | |||
| * datagram socket. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.async_receive(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive(this->get_implementation(), | |||
| buffers, 0, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous receive on a connected socket. | |||
| /** | |||
| * This function is used to asynchronously receive data from the datagram | |||
| * socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_receive operation can only be used with a connected socket. | |||
| * Use the async_receive_from function to receive data on an unconnected | |||
| * datagram socket. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive(this->get_implementation(), | |||
| buffers, flags, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Receive a datagram with the endpoint of the sender. | |||
| /** | |||
| * This function is used to receive a datagram. The function call will block | |||
| * until data has been received successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the datagram. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * asio::ip::udp::endpoint sender_endpoint; | |||
| * socket.receive_from( | |||
| * asio::buffer(data, size), sender_endpoint); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, 0, ec); | |||
| asio::detail::throw_error(ec, "receive_from"); | |||
| return s; | |||
| } | |||
| /// Receive a datagram with the endpoint of the sender. | |||
| /** | |||
| * This function is used to receive a datagram. The function call will block | |||
| * until data has been received successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the datagram. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, flags, ec); | |||
| asio::detail::throw_error(ec, "receive_from"); | |||
| return s; | |||
| } | |||
| /// Receive a datagram with the endpoint of the sender. | |||
| /** | |||
| * This function is used to receive a datagram. The function call will block | |||
| * until data has been received successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the datagram. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes received. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, socket_base::message_flags flags, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().receive_from(this->get_implementation(), | |||
| buffers, sender_endpoint, flags, ec); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive a datagram. The function | |||
| * call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the datagram. Ownership of the sender_endpoint object | |||
| * is retained by the caller, which must guarantee that it is valid until the | |||
| * handler is called. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code socket.async_receive_from( | |||
| * asio::buffer(data, size), sender_endpoint, handler); @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, 0, | |||
| ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive a datagram. The function | |||
| * call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the datagram. Ownership of the sender_endpoint object | |||
| * is retained by the caller, which must guarantee that it is valid until the | |||
| * handler is called. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, flags, | |||
| ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BASIC_DATAGRAM_SOCKET_HPP | |||
| @@ -0,0 +1,521 @@ | |||
| // | |||
| // basic_deadline_timer.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_DEADLINE_TIMER_HPP | |||
| #define ASIO_BASIC_DEADLINE_TIMER_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) \ | |||
| || defined(GENERATING_DOCUMENTATION) | |||
| #include <cstddef> | |||
| #include "asio/basic_io_object.hpp" | |||
| #include "asio/deadline_timer_service.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides waitable timer functionality. | |||
| /** | |||
| * The basic_deadline_timer class template provides the ability to perform a | |||
| * blocking or asynchronous wait for a timer to expire. | |||
| * | |||
| * A deadline timer is always in one of two states: "expired" or "not expired". | |||
| * If the wait() or async_wait() function is called on an expired timer, the | |||
| * wait operation will complete immediately. | |||
| * | |||
| * Most applications will use the asio::deadline_timer typedef. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| * | |||
| * @par Examples | |||
| * Performing a blocking wait: | |||
| * @code | |||
| * // Construct a timer without setting an expiry time. | |||
| * asio::deadline_timer timer(io_context); | |||
| * | |||
| * // Set an expiry time relative to now. | |||
| * timer.expires_from_now(boost::posix_time::seconds(5)); | |||
| * | |||
| * // Wait for the timer to expire. | |||
| * timer.wait(); | |||
| * @endcode | |||
| * | |||
| * @par | |||
| * Performing an asynchronous wait: | |||
| * @code | |||
| * void handler(const asio::error_code& error) | |||
| * { | |||
| * if (!error) | |||
| * { | |||
| * // Timer expired. | |||
| * } | |||
| * } | |||
| * | |||
| * ... | |||
| * | |||
| * // Construct a timer with an absolute expiry time. | |||
| * asio::deadline_timer timer(io_context, | |||
| * boost::posix_time::time_from_string("2005-12-07 23:59:59.000")); | |||
| * | |||
| * // Start an asynchronous wait. | |||
| * timer.async_wait(handler); | |||
| * @endcode | |||
| * | |||
| * @par Changing an active deadline_timer's expiry time | |||
| * | |||
| * Changing the expiry time of a timer while there are pending asynchronous | |||
| * waits causes those wait operations to be cancelled. To ensure that the action | |||
| * associated with the timer is performed only once, use something like this: | |||
| * used: | |||
| * | |||
| * @code | |||
| * void on_some_event() | |||
| * { | |||
| * if (my_timer.expires_from_now(seconds(5)) > 0) | |||
| * { | |||
| * // We managed to cancel the timer. Start new asynchronous wait. | |||
| * my_timer.async_wait(on_timeout); | |||
| * } | |||
| * else | |||
| * { | |||
| * // Too late, timer has already expired! | |||
| * } | |||
| * } | |||
| * | |||
| * void on_timeout(const asio::error_code& e) | |||
| * { | |||
| * if (e != asio::error::operation_aborted) | |||
| * { | |||
| * // Timer was not cancelled, take necessary action. | |||
| * } | |||
| * } | |||
| * @endcode | |||
| * | |||
| * @li The asio::basic_deadline_timer::expires_from_now() function | |||
| * cancels any pending asynchronous waits, and returns the number of | |||
| * asynchronous waits that were cancelled. If it returns 0 then you were too | |||
| * late and the wait handler has already been executed, or will soon be | |||
| * executed. If it returns 1 then the wait handler was successfully cancelled. | |||
| * | |||
| * @li If a wait handler is cancelled, the asio::error_code passed to | |||
| * it contains the value asio::error::operation_aborted. | |||
| */ | |||
| template <typename Time, | |||
| typename TimeTraits = asio::time_traits<Time>, | |||
| typename TimerService = deadline_timer_service<Time, TimeTraits> > | |||
| class basic_deadline_timer | |||
| : public basic_io_object<TimerService> | |||
| { | |||
| public: | |||
| /// The time traits type. | |||
| typedef TimeTraits traits_type; | |||
| /// The time type. | |||
| typedef typename traits_type::time_type time_type; | |||
| /// The duration type. | |||
| typedef typename traits_type::duration_type duration_type; | |||
| /// Constructor. | |||
| /** | |||
| * This constructor creates a timer without setting an expiry time. The | |||
| * expires_at() or expires_from_now() functions must be called to set an | |||
| * expiry time before the timer can be waited on. | |||
| * | |||
| * @param io_context The io_context object that the timer will use to dispatch | |||
| * handlers for any asynchronous operations performed on the timer. | |||
| */ | |||
| explicit basic_deadline_timer(asio::io_context& io_context) | |||
| : basic_io_object<TimerService>(io_context) | |||
| { | |||
| } | |||
| /// Constructor to set a particular expiry time as an absolute time. | |||
| /** | |||
| * This constructor creates a timer and sets the expiry time. | |||
| * | |||
| * @param io_context The io_context object that the timer will use to dispatch | |||
| * handlers for any asynchronous operations performed on the timer. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer, expressed | |||
| * as an absolute time. | |||
| */ | |||
| basic_deadline_timer(asio::io_context& io_context, | |||
| const time_type& expiry_time) | |||
| : basic_io_object<TimerService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().expires_at(this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_at"); | |||
| } | |||
| /// Constructor to set a particular expiry time relative to now. | |||
| /** | |||
| * This constructor creates a timer and sets the expiry time. | |||
| * | |||
| * @param io_context The io_context object that the timer will use to dispatch | |||
| * handlers for any asynchronous operations performed on the timer. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer, relative to | |||
| * now. | |||
| */ | |||
| basic_deadline_timer(asio::io_context& io_context, | |||
| const duration_type& expiry_time) | |||
| : basic_io_object<TimerService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().expires_from_now( | |||
| this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_from_now"); | |||
| } | |||
| /// Cancel any asynchronous operations that are waiting on the timer. | |||
| /** | |||
| * This function forces the completion of any pending asynchronous wait | |||
| * operations against the timer. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when cancel() is called, then the | |||
| * handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel() | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().cancel(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "cancel"); | |||
| return s; | |||
| } | |||
| /// Cancel any asynchronous operations that are waiting on the timer. | |||
| /** | |||
| * This function forces the completion of any pending asynchronous wait | |||
| * operations against the timer. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @note If the timer has already expired when cancel() is called, then the | |||
| * handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel(asio::error_code& ec) | |||
| { | |||
| return this->get_service().cancel(this->get_implementation(), ec); | |||
| } | |||
| /// Cancels one asynchronous operation that is waiting on the timer. | |||
| /** | |||
| * This function forces the completion of one pending asynchronous wait | |||
| * operation against the timer. Handlers are cancelled in FIFO order. The | |||
| * handler for the cancelled operation will be invoked with the | |||
| * asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. That is, | |||
| * either 0 or 1. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when cancel_one() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel_one() | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().cancel_one( | |||
| this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "cancel_one"); | |||
| return s; | |||
| } | |||
| /// Cancels one asynchronous operation that is waiting on the timer. | |||
| /** | |||
| * This function forces the completion of one pending asynchronous wait | |||
| * operation against the timer. Handlers are cancelled in FIFO order. The | |||
| * handler for the cancelled operation will be invoked with the | |||
| * asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. That is, | |||
| * either 0 or 1. | |||
| * | |||
| * @note If the timer has already expired when cancel_one() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel_one(asio::error_code& ec) | |||
| { | |||
| return this->get_service().cancel_one(this->get_implementation(), ec); | |||
| } | |||
| /// Get the timer's expiry time as an absolute time. | |||
| /** | |||
| * This function may be used to obtain the timer's current expiry time. | |||
| * Whether the timer has expired or not does not affect this value. | |||
| */ | |||
| time_type expires_at() const | |||
| { | |||
| return this->get_service().expires_at(this->get_implementation()); | |||
| } | |||
| /// Set the timer's expiry time as an absolute time. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when expires_at() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_at(const time_type& expiry_time) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().expires_at( | |||
| this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_at"); | |||
| return s; | |||
| } | |||
| /// Set the timer's expiry time as an absolute time. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @note If the timer has already expired when expires_at() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_at(const time_type& expiry_time, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().expires_at( | |||
| this->get_implementation(), expiry_time, ec); | |||
| } | |||
| /// Get the timer's expiry time relative to now. | |||
| /** | |||
| * This function may be used to obtain the timer's current expiry time. | |||
| * Whether the timer has expired or not does not affect this value. | |||
| */ | |||
| duration_type expires_from_now() const | |||
| { | |||
| return this->get_service().expires_from_now(this->get_implementation()); | |||
| } | |||
| /// Set the timer's expiry time relative to now. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when expires_from_now() is called, | |||
| * then the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_from_now(const duration_type& expiry_time) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().expires_from_now( | |||
| this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_from_now"); | |||
| return s; | |||
| } | |||
| /// Set the timer's expiry time relative to now. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @note If the timer has already expired when expires_from_now() is called, | |||
| * then the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_from_now(const duration_type& expiry_time, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().expires_from_now( | |||
| this->get_implementation(), expiry_time, ec); | |||
| } | |||
| /// Perform a blocking wait on the timer. | |||
| /** | |||
| * This function is used to wait for the timer to expire. This function | |||
| * blocks and does not return until the timer has expired. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void wait() | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().wait(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "wait"); | |||
| } | |||
| /// Perform a blocking wait on the timer. | |||
| /** | |||
| * This function is used to wait for the timer to expire. This function | |||
| * blocks and does not return until the timer has expired. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| */ | |||
| void wait(asio::error_code& ec) | |||
| { | |||
| this->get_service().wait(this->get_implementation(), ec); | |||
| } | |||
| /// Start an asynchronous wait on the timer. | |||
| /** | |||
| * This function may be used to initiate an asynchronous wait against the | |||
| * timer. It always returns immediately. | |||
| * | |||
| * For each call to async_wait(), the supplied handler will be called exactly | |||
| * once. The handler will be called when: | |||
| * | |||
| * @li The timer has expired. | |||
| * | |||
| * @li The timer was cancelled, in which case the handler is passed the error | |||
| * code asio::error::operation_aborted. | |||
| * | |||
| * @param handler The handler to be called when the timer expires. Copies | |||
| * will be made of the handler as required. The function signature of the | |||
| * handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error // Result of operation. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| */ | |||
| template <typename WaitHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WaitHandler, | |||
| void (asio::error_code)) | |||
| async_wait(ASIO_MOVE_ARG(WaitHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WaitHandler. | |||
| ASIO_WAIT_HANDLER_CHECK(WaitHandler, handler) type_check; | |||
| return this->get_service().async_wait(this->get_implementation(), | |||
| ASIO_MOVE_CAST(WaitHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| // || defined(GENERATING_DOCUMENTATION) | |||
| #endif // ASIO_BASIC_DEADLINE_TIMER_HPP | |||
| @@ -0,0 +1,274 @@ | |||
| // | |||
| // basic_io_object.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_IO_OBJECT_HPP | |||
| #define ASIO_BASIC_IO_OBJECT_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include "asio/io_context.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| #if defined(ASIO_HAS_MOVE) | |||
| namespace detail | |||
| { | |||
| // Type trait used to determine whether a service supports move. | |||
| template <typename IoObjectService> | |||
| class service_has_move | |||
| { | |||
| private: | |||
| typedef IoObjectService service_type; | |||
| typedef typename service_type::implementation_type implementation_type; | |||
| template <typename T, typename U> | |||
| static auto eval(T* t, U* u) -> decltype(t->move_construct(*u, *u), char()); | |||
| static char (&eval(...))[2]; | |||
| public: | |||
| static const bool value = | |||
| sizeof(service_has_move::eval( | |||
| static_cast<service_type*>(0), | |||
| static_cast<implementation_type*>(0))) == 1; | |||
| }; | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) | |||
| /// Base class for all I/O objects. | |||
| /** | |||
| * @note All I/O objects are non-copyable. However, when using C++0x, certain | |||
| * I/O objects do support move construction and move assignment. | |||
| */ | |||
| #if !defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| template <typename IoObjectService> | |||
| #else | |||
| template <typename IoObjectService, | |||
| bool Movable = detail::service_has_move<IoObjectService>::value> | |||
| #endif | |||
| class basic_io_object | |||
| { | |||
| public: | |||
| /// The type of the service that will be used to provide I/O operations. | |||
| typedef IoObjectService service_type; | |||
| /// The underlying implementation type of I/O object. | |||
| typedef typename service_type::implementation_type implementation_type; | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| /** | |||
| * This function may be used to obtain the io_context object that the I/O | |||
| * object uses to dispatch handlers for asynchronous operations. | |||
| * | |||
| * @return A reference to the io_context object that the I/O object will use | |||
| * to dispatch handlers. Ownership is not transferred to the caller. | |||
| */ | |||
| asio::io_context& get_io_context() | |||
| { | |||
| return service_.get_io_context(); | |||
| } | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| /** | |||
| * This function may be used to obtain the io_context object that the I/O | |||
| * object uses to dispatch handlers for asynchronous operations. | |||
| * | |||
| * @return A reference to the io_context object that the I/O object will use | |||
| * to dispatch handlers. Ownership is not transferred to the caller. | |||
| */ | |||
| asio::io_context& get_io_service() | |||
| { | |||
| return service_.get_io_context(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// The type of the executor associated with the object. | |||
| typedef asio::io_context::executor_type executor_type; | |||
| /// Get the executor associated with the object. | |||
| executor_type get_executor() ASIO_NOEXCEPT | |||
| { | |||
| return service_.get_io_context().get_executor(); | |||
| } | |||
| protected: | |||
| /// Construct a basic_io_object. | |||
| /** | |||
| * Performs: | |||
| * @code get_service().construct(get_implementation()); @endcode | |||
| */ | |||
| explicit basic_io_object(asio::io_context& io_context) | |||
| : service_(asio::use_service<IoObjectService>(io_context)) | |||
| { | |||
| service_.construct(implementation_); | |||
| } | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// Move-construct a basic_io_object. | |||
| /** | |||
| * Performs: | |||
| * @code get_service().move_construct( | |||
| * get_implementation(), other.get_implementation()); @endcode | |||
| * | |||
| * @note Available only for services that support movability, | |||
| */ | |||
| basic_io_object(basic_io_object&& other); | |||
| /// Move-assign a basic_io_object. | |||
| /** | |||
| * Performs: | |||
| * @code get_service().move_assign(get_implementation(), | |||
| * other.get_service(), other.get_implementation()); @endcode | |||
| * | |||
| * @note Available only for services that support movability, | |||
| */ | |||
| basic_io_object& operator=(basic_io_object&& other); | |||
| #endif // defined(GENERATING_DOCUMENTATION) | |||
| /// Protected destructor to prevent deletion through this type. | |||
| /** | |||
| * Performs: | |||
| * @code get_service().destroy(get_implementation()); @endcode | |||
| */ | |||
| ~basic_io_object() | |||
| { | |||
| service_.destroy(implementation_); | |||
| } | |||
| /// Get the service associated with the I/O object. | |||
| service_type& get_service() | |||
| { | |||
| return service_; | |||
| } | |||
| /// Get the service associated with the I/O object. | |||
| const service_type& get_service() const | |||
| { | |||
| return service_; | |||
| } | |||
| /// Get the underlying implementation of the I/O object. | |||
| implementation_type& get_implementation() | |||
| { | |||
| return implementation_; | |||
| } | |||
| /// Get the underlying implementation of the I/O object. | |||
| const implementation_type& get_implementation() const | |||
| { | |||
| return implementation_; | |||
| } | |||
| private: | |||
| basic_io_object(const basic_io_object&); | |||
| basic_io_object& operator=(const basic_io_object&); | |||
| // The service associated with the I/O object. | |||
| service_type& service_; | |||
| /// The underlying implementation of the I/O object. | |||
| implementation_type implementation_; | |||
| }; | |||
| #if defined(ASIO_HAS_MOVE) | |||
| // Specialisation for movable objects. | |||
| template <typename IoObjectService> | |||
| class basic_io_object<IoObjectService, true> | |||
| { | |||
| public: | |||
| typedef IoObjectService service_type; | |||
| typedef typename service_type::implementation_type implementation_type; | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| asio::io_context& get_io_context() | |||
| { | |||
| return service_->get_io_context(); | |||
| } | |||
| asio::io_context& get_io_service() | |||
| { | |||
| return service_->get_io_context(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| typedef asio::io_context::executor_type executor_type; | |||
| executor_type get_executor() ASIO_NOEXCEPT | |||
| { | |||
| return service_->get_io_context().get_executor(); | |||
| } | |||
| protected: | |||
| explicit basic_io_object(asio::io_context& io_context) | |||
| : service_(&asio::use_service<IoObjectService>(io_context)) | |||
| { | |||
| service_->construct(implementation_); | |||
| } | |||
| basic_io_object(basic_io_object&& other) | |||
| : service_(&other.get_service()) | |||
| { | |||
| service_->move_construct(implementation_, other.implementation_); | |||
| } | |||
| ~basic_io_object() | |||
| { | |||
| service_->destroy(implementation_); | |||
| } | |||
| basic_io_object& operator=(basic_io_object&& other) | |||
| { | |||
| service_->move_assign(implementation_, | |||
| *other.service_, other.implementation_); | |||
| service_ = other.service_; | |||
| return *this; | |||
| } | |||
| service_type& get_service() | |||
| { | |||
| return *service_; | |||
| } | |||
| const service_type& get_service() const | |||
| { | |||
| return *service_; | |||
| } | |||
| implementation_type& get_implementation() | |||
| { | |||
| return implementation_; | |||
| } | |||
| const implementation_type& get_implementation() const | |||
| { | |||
| return implementation_; | |||
| } | |||
| private: | |||
| basic_io_object(const basic_io_object&); | |||
| void operator=(const basic_io_object&); | |||
| IoObjectService* service_; | |||
| implementation_type implementation_; | |||
| }; | |||
| #endif // defined(ASIO_HAS_MOVE) | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BASIC_IO_OBJECT_HPP | |||
| @@ -0,0 +1,936 @@ | |||
| // | |||
| // basic_raw_socket.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_RAW_SOCKET_HPP | |||
| #define ASIO_BASIC_RAW_SOCKET_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/basic_socket.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/raw_socket_service.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides raw-oriented socket functionality. | |||
| /** | |||
| * The basic_raw_socket class template provides asynchronous and blocking | |||
| * raw-oriented socket functionality. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| */ | |||
| template <typename Protocol, | |||
| typename RawSocketService = raw_socket_service<Protocol> > | |||
| class basic_raw_socket | |||
| : public basic_socket<Protocol, RawSocketService> | |||
| { | |||
| public: | |||
| /// The native representation of a socket. | |||
| typedef typename RawSocketService::native_handle_type native_handle_type; | |||
| /// The protocol type. | |||
| typedef Protocol protocol_type; | |||
| /// The endpoint type. | |||
| typedef typename Protocol::endpoint endpoint_type; | |||
| /// Construct a basic_raw_socket without opening it. | |||
| /** | |||
| * This constructor creates a raw socket without opening it. The open() | |||
| * function must be called before data can be sent or received on the socket. | |||
| * | |||
| * @param io_context The io_context object that the raw socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| */ | |||
| explicit basic_raw_socket(asio::io_context& io_context) | |||
| : basic_socket<Protocol, RawSocketService>(io_context) | |||
| { | |||
| } | |||
| /// Construct and open a basic_raw_socket. | |||
| /** | |||
| * This constructor creates and opens a raw socket. | |||
| * | |||
| * @param io_context The io_context object that the raw socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_raw_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol) | |||
| : basic_socket<Protocol, RawSocketService>(io_context, protocol) | |||
| { | |||
| } | |||
| /// Construct a basic_raw_socket, opening it and binding it to the given | |||
| /// local endpoint. | |||
| /** | |||
| * This constructor creates a raw socket and automatically opens it bound | |||
| * to the specified endpoint on the local machine. The protocol used is the | |||
| * protocol associated with the given endpoint. | |||
| * | |||
| * @param io_context The io_context object that the raw socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| * | |||
| * @param endpoint An endpoint on the local machine to which the raw | |||
| * socket will be bound. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_raw_socket(asio::io_context& io_context, | |||
| const endpoint_type& endpoint) | |||
| : basic_socket<Protocol, RawSocketService>(io_context, endpoint) | |||
| { | |||
| } | |||
| /// Construct a basic_raw_socket on an existing native socket. | |||
| /** | |||
| * This constructor creates a raw socket object to hold an existing | |||
| * native socket. | |||
| * | |||
| * @param io_context The io_context object that the raw socket will use | |||
| * to dispatch handlers for any asynchronous operations performed on the | |||
| * socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @param native_socket The new underlying socket implementation. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_raw_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol, const native_handle_type& native_socket) | |||
| : basic_socket<Protocol, RawSocketService>( | |||
| io_context, protocol, native_socket) | |||
| { | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move-construct a basic_raw_socket from another. | |||
| /** | |||
| * This constructor moves a raw socket from one object to another. | |||
| * | |||
| * @param other The other basic_raw_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_raw_socket(io_context&) constructor. | |||
| */ | |||
| basic_raw_socket(basic_raw_socket&& other) | |||
| : basic_socket<Protocol, RawSocketService>( | |||
| ASIO_MOVE_CAST(basic_raw_socket)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_raw_socket from another. | |||
| /** | |||
| * This assignment operator moves a raw socket from one object to another. | |||
| * | |||
| * @param other The other basic_raw_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_raw_socket(io_context&) constructor. | |||
| */ | |||
| basic_raw_socket& operator=(basic_raw_socket&& other) | |||
| { | |||
| basic_socket<Protocol, RawSocketService>::operator=( | |||
| ASIO_MOVE_CAST(basic_raw_socket)(other)); | |||
| return *this; | |||
| } | |||
| /// Move-construct a basic_raw_socket from a socket of another protocol type. | |||
| /** | |||
| * This constructor moves a raw socket from one object to another. | |||
| * | |||
| * @param other The other basic_raw_socket object from which the move will | |||
| * occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_raw_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename RawSocketService1> | |||
| basic_raw_socket(basic_raw_socket<Protocol1, RawSocketService1>&& other, | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value>::type* = 0) | |||
| : basic_socket<Protocol, RawSocketService>( | |||
| ASIO_MOVE_CAST2(basic_raw_socket< | |||
| Protocol1, RawSocketService1>)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_raw_socket from a socket of another protocol type. | |||
| /** | |||
| * This assignment operator moves a raw socket from one object to another. | |||
| * | |||
| * @param other The other basic_raw_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_raw_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename RawSocketService1> | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value, | |||
| basic_raw_socket>::type& operator=( | |||
| basic_raw_socket<Protocol1, RawSocketService1>&& other) | |||
| { | |||
| basic_socket<Protocol, RawSocketService>::operator=( | |||
| ASIO_MOVE_CAST2(basic_raw_socket< | |||
| Protocol1, RawSocketService1>)(other)); | |||
| return *this; | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Send some data on a connected socket. | |||
| /** | |||
| * This function is used to send data on the raw socket. The function call | |||
| * will block until the data has been sent successfully or an error occurs. | |||
| * | |||
| * @param buffers One ore more data buffers to be sent on the socket. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The send operation can only be used with a connected socket. Use | |||
| * the send_to function to send data on an unconnected raw socket. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code socket.send(asio::buffer(data, size)); @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "send"); | |||
| return s; | |||
| } | |||
| /// Send some data on a connected socket. | |||
| /** | |||
| * This function is used to send data on the raw socket. The function call | |||
| * will block until the data has been sent successfully or an error occurs. | |||
| * | |||
| * @param buffers One ore more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The send operation can only be used with a connected socket. Use | |||
| * the send_to function to send data on an unconnected raw socket. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| asio::detail::throw_error(ec, "send"); | |||
| return s; | |||
| } | |||
| /// Send some data on a connected socket. | |||
| /** | |||
| * This function is used to send data on the raw socket. The function call | |||
| * will block until the data has been sent successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @note The send operation can only be used with a connected socket. Use | |||
| * the send_to function to send data on an unconnected raw socket. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| } | |||
| /// Start an asynchronous send on a connected socket. | |||
| /** | |||
| * This function is used to send data on the raw socket. The function call | |||
| * will block until the data has been sent successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. Although | |||
| * the buffers object may be copied as necessary, ownership of the underlying | |||
| * memory blocks is retained by the caller, which must guarantee that they | |||
| * remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_send operation can only be used with a connected socket. | |||
| * Use the async_send_to function to send data on an unconnected raw | |||
| * socket. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.async_send(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send(this->get_implementation(), | |||
| buffers, 0, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous send on a connected socket. | |||
| /** | |||
| * This function is used to send data on the raw socket. The function call | |||
| * will block until the data has been sent successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. Although | |||
| * the buffers object may be copied as necessary, ownership of the underlying | |||
| * memory blocks is retained by the caller, which must guarantee that they | |||
| * remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_send operation can only be used with a connected socket. | |||
| * Use the async_send_to function to send data on an unconnected raw | |||
| * socket. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send(this->get_implementation(), | |||
| buffers, flags, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Send raw data to the specified endpoint. | |||
| /** | |||
| * This function is used to send raw data to the specified remote endpoint. | |||
| * The function call will block until the data has been sent successfully or | |||
| * an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * asio::ip::udp::endpoint destination( | |||
| * asio::ip::address::from_string("1.2.3.4"), 12345); | |||
| * socket.send_to(asio::buffer(data, size), destination); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send_to( | |||
| this->get_implementation(), buffers, destination, 0, ec); | |||
| asio::detail::throw_error(ec, "send_to"); | |||
| return s; | |||
| } | |||
| /// Send raw data to the specified endpoint. | |||
| /** | |||
| * This function is used to send raw data to the specified remote endpoint. | |||
| * The function call will block until the data has been sent successfully or | |||
| * an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send_to( | |||
| this->get_implementation(), buffers, destination, flags, ec); | |||
| asio::detail::throw_error(ec, "send_to"); | |||
| return s; | |||
| } | |||
| /// Send raw data to the specified endpoint. | |||
| /** | |||
| * This function is used to send raw data to the specified remote endpoint. | |||
| * The function call will block until the data has been sent successfully or | |||
| * an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, socket_base::message_flags flags, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().send_to(this->get_implementation(), | |||
| buffers, destination, flags, ec); | |||
| } | |||
| /// Start an asynchronous send. | |||
| /** | |||
| * This function is used to asynchronously send raw data to the specified | |||
| * remote endpoint. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * Copies will be made of the endpoint as required. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * asio::ip::udp::endpoint destination( | |||
| * asio::ip::address::from_string("1.2.3.4"), 12345); | |||
| * socket.async_send_to( | |||
| * asio::buffer(data, size), destination, handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send_to(this->get_implementation(), | |||
| buffers, destination, 0, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous send. | |||
| /** | |||
| * This function is used to asynchronously send raw data to the specified | |||
| * remote endpoint. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent to the remote endpoint. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param destination The remote endpoint to which the data will be sent. | |||
| * Copies will be made of the endpoint as required. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send_to(const ConstBufferSequence& buffers, | |||
| const endpoint_type& destination, socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send_to( | |||
| this->get_implementation(), buffers, destination, flags, | |||
| ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the raw socket. The function | |||
| * call will block until data has been received successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The receive operation can only be used with a connected socket. Use | |||
| * the receive_from function to receive data on an unconnected raw | |||
| * socket. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code socket.receive(asio::buffer(data, size)); @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the raw socket. The function | |||
| * call will block until data has been received successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The receive operation can only be used with a connected socket. Use | |||
| * the receive_from function to receive data on an unconnected raw | |||
| * socket. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the raw socket. The function | |||
| * call will block until data has been received successfully or an error | |||
| * occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @note The receive operation can only be used with a connected socket. Use | |||
| * the receive_from function to receive data on an unconnected raw | |||
| * socket. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().receive( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| } | |||
| /// Start an asynchronous receive on a connected socket. | |||
| /** | |||
| * This function is used to asynchronously receive data from the raw | |||
| * socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_receive operation can only be used with a connected socket. | |||
| * Use the async_receive_from function to receive data on an unconnected | |||
| * raw socket. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.async_receive(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive(this->get_implementation(), | |||
| buffers, 0, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous receive on a connected socket. | |||
| /** | |||
| * This function is used to asynchronously receive data from the raw | |||
| * socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The async_receive operation can only be used with a connected socket. | |||
| * Use the async_receive_from function to receive data on an unconnected | |||
| * raw socket. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive(this->get_implementation(), | |||
| buffers, flags, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Receive raw data with the endpoint of the sender. | |||
| /** | |||
| * This function is used to receive raw data. The function call will block | |||
| * until data has been received successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the data. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * asio::ip::udp::endpoint sender_endpoint; | |||
| * socket.receive_from( | |||
| * asio::buffer(data, size), sender_endpoint); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, 0, ec); | |||
| asio::detail::throw_error(ec, "receive_from"); | |||
| return s; | |||
| } | |||
| /// Receive raw data with the endpoint of the sender. | |||
| /** | |||
| * This function is used to receive raw data. The function call will block | |||
| * until data has been received successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the data. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, flags, ec); | |||
| asio::detail::throw_error(ec, "receive_from"); | |||
| return s; | |||
| } | |||
| /// Receive raw data with the endpoint of the sender. | |||
| /** | |||
| * This function is used to receive raw data. The function call will block | |||
| * until data has been received successfully or an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the data. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes received. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, socket_base::message_flags flags, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().receive_from(this->get_implementation(), | |||
| buffers, sender_endpoint, flags, ec); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive raw data. The function | |||
| * call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the data. Ownership of the sender_endpoint object | |||
| * is retained by the caller, which must guarantee that it is valid until the | |||
| * handler is called. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code socket.async_receive_from( | |||
| * asio::buffer(data, size), 0, sender_endpoint, handler); @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, 0, | |||
| ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive raw data. The function | |||
| * call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param sender_endpoint An endpoint object that receives the endpoint of | |||
| * the remote sender of the data. Ownership of the sender_endpoint object | |||
| * is retained by the caller, which must guarantee that it is valid until the | |||
| * handler is called. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive_from(const MutableBufferSequence& buffers, | |||
| endpoint_type& sender_endpoint, socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive_from( | |||
| this->get_implementation(), buffers, sender_endpoint, flags, | |||
| ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BASIC_RAW_SOCKET_HPP | |||
| @@ -0,0 +1,561 @@ | |||
| // | |||
| // basic_seq_packet_socket.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_SEQ_PACKET_SOCKET_HPP | |||
| #define ASIO_BASIC_SEQ_PACKET_SOCKET_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/basic_socket.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/seq_packet_socket_service.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides sequenced packet socket functionality. | |||
| /** | |||
| * The basic_seq_packet_socket class template provides asynchronous and blocking | |||
| * sequenced packet socket functionality. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| */ | |||
| template <typename Protocol, | |||
| typename SeqPacketSocketService = seq_packet_socket_service<Protocol> > | |||
| class basic_seq_packet_socket | |||
| : public basic_socket<Protocol, SeqPacketSocketService> | |||
| { | |||
| public: | |||
| /// The native representation of a socket. | |||
| typedef typename SeqPacketSocketService::native_handle_type | |||
| native_handle_type; | |||
| /// The protocol type. | |||
| typedef Protocol protocol_type; | |||
| /// The endpoint type. | |||
| typedef typename Protocol::endpoint endpoint_type; | |||
| /// Construct a basic_seq_packet_socket without opening it. | |||
| /** | |||
| * This constructor creates a sequenced packet socket without opening it. The | |||
| * socket needs to be opened and then connected or accepted before data can | |||
| * be sent or received on it. | |||
| * | |||
| * @param io_context The io_context object that the sequenced packet socket | |||
| * will use to dispatch handlers for any asynchronous operations performed on | |||
| * the socket. | |||
| */ | |||
| explicit basic_seq_packet_socket(asio::io_context& io_context) | |||
| : basic_socket<Protocol, SeqPacketSocketService>(io_context) | |||
| { | |||
| } | |||
| /// Construct and open a basic_seq_packet_socket. | |||
| /** | |||
| * This constructor creates and opens a sequenced_packet socket. The socket | |||
| * needs to be connected or accepted before data can be sent or received on | |||
| * it. | |||
| * | |||
| * @param io_context The io_context object that the sequenced packet socket | |||
| * will use to dispatch handlers for any asynchronous operations performed on | |||
| * the socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_seq_packet_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol) | |||
| : basic_socket<Protocol, SeqPacketSocketService>(io_context, protocol) | |||
| { | |||
| } | |||
| /// Construct a basic_seq_packet_socket, opening it and binding it to the | |||
| /// given local endpoint. | |||
| /** | |||
| * This constructor creates a sequenced packet socket and automatically opens | |||
| * it bound to the specified endpoint on the local machine. The protocol used | |||
| * is the protocol associated with the given endpoint. | |||
| * | |||
| * @param io_context The io_context object that the sequenced packet socket | |||
| * will use to dispatch handlers for any asynchronous operations performed on | |||
| * the socket. | |||
| * | |||
| * @param endpoint An endpoint on the local machine to which the sequenced | |||
| * packet socket will be bound. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_seq_packet_socket(asio::io_context& io_context, | |||
| const endpoint_type& endpoint) | |||
| : basic_socket<Protocol, SeqPacketSocketService>(io_context, endpoint) | |||
| { | |||
| } | |||
| /// Construct a basic_seq_packet_socket on an existing native socket. | |||
| /** | |||
| * This constructor creates a sequenced packet socket object to hold an | |||
| * existing native socket. | |||
| * | |||
| * @param io_context The io_context object that the sequenced packet socket | |||
| * will use to dispatch handlers for any asynchronous operations performed on | |||
| * the socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @param native_socket The new underlying socket implementation. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_seq_packet_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol, const native_handle_type& native_socket) | |||
| : basic_socket<Protocol, SeqPacketSocketService>( | |||
| io_context, protocol, native_socket) | |||
| { | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move-construct a basic_seq_packet_socket from another. | |||
| /** | |||
| * This constructor moves a sequenced packet socket from one object to | |||
| * another. | |||
| * | |||
| * @param other The other basic_seq_packet_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_seq_packet_socket(io_context&) constructor. | |||
| */ | |||
| basic_seq_packet_socket(basic_seq_packet_socket&& other) | |||
| : basic_socket<Protocol, SeqPacketSocketService>( | |||
| ASIO_MOVE_CAST(basic_seq_packet_socket)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_seq_packet_socket from another. | |||
| /** | |||
| * This assignment operator moves a sequenced packet socket from one object to | |||
| * another. | |||
| * | |||
| * @param other The other basic_seq_packet_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_seq_packet_socket(io_context&) constructor. | |||
| */ | |||
| basic_seq_packet_socket& operator=(basic_seq_packet_socket&& other) | |||
| { | |||
| basic_socket<Protocol, SeqPacketSocketService>::operator=( | |||
| ASIO_MOVE_CAST(basic_seq_packet_socket)(other)); | |||
| return *this; | |||
| } | |||
| /// Move-construct a basic_seq_packet_socket from a socket of another protocol | |||
| /// type. | |||
| /** | |||
| * This constructor moves a sequenced packet socket from one object to | |||
| * another. | |||
| * | |||
| * @param other The other basic_seq_packet_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_seq_packet_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename SeqPacketSocketService1> | |||
| basic_seq_packet_socket( | |||
| basic_seq_packet_socket<Protocol1, SeqPacketSocketService1>&& other, | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value>::type* = 0) | |||
| : basic_socket<Protocol, SeqPacketSocketService>( | |||
| ASIO_MOVE_CAST2(basic_seq_packet_socket< | |||
| Protocol1, SeqPacketSocketService1>)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_seq_packet_socket from a socket of another protocol | |||
| /// type. | |||
| /** | |||
| * This assignment operator moves a sequenced packet socket from one object to | |||
| * another. | |||
| * | |||
| * @param other The other basic_seq_packet_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_seq_packet_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename SeqPacketSocketService1> | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value, | |||
| basic_seq_packet_socket>::type& operator=( | |||
| basic_seq_packet_socket<Protocol1, SeqPacketSocketService1>&& other) | |||
| { | |||
| basic_socket<Protocol, SeqPacketSocketService>::operator=( | |||
| ASIO_MOVE_CAST2(basic_seq_packet_socket< | |||
| Protocol1, SeqPacketSocketService1>)(other)); | |||
| return *this; | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Send some data on the socket. | |||
| /** | |||
| * This function is used to send data on the sequenced packet socket. The | |||
| * function call will block until the data has been sent successfully, or an | |||
| * until error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.send(asio::buffer(data, size), 0); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| asio::detail::throw_error(ec, "send"); | |||
| return s; | |||
| } | |||
| /// Send some data on the socket. | |||
| /** | |||
| * This function is used to send data on the sequenced packet socket. The | |||
| * function call will block the data has been sent successfully, or an until | |||
| * error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes sent. Returns 0 if an error occurred. | |||
| * | |||
| * @note The send operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref write function if you need to ensure that all data | |||
| * is written before the blocking operation completes. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| } | |||
| /// Start an asynchronous send. | |||
| /** | |||
| * This function is used to asynchronously send data on the sequenced packet | |||
| * socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. Although | |||
| * the buffers object may be copied as necessary, ownership of the underlying | |||
| * memory blocks is retained by the caller, which must guarantee that they | |||
| * remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.async_send(asio::buffer(data, size), 0, handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send(this->get_implementation(), | |||
| buffers, flags, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Receive some data on the socket. | |||
| /** | |||
| * This function is used to receive data on the sequenced packet socket. The | |||
| * function call will block until data has been received successfully, or | |||
| * until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param out_flags After the receive call completes, contains flags | |||
| * associated with the received data. For example, if the | |||
| * socket_base::message_end_of_record bit is set then the received data marks | |||
| * the end of a record. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.receive(asio::buffer(data, size), out_flags); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags& out_flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, 0, out_flags, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on the socket. | |||
| /** | |||
| * This function is used to receive data on the sequenced packet socket. The | |||
| * function call will block until data has been received successfully, or | |||
| * until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param in_flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param out_flags After the receive call completes, contains flags | |||
| * associated with the received data. For example, if the | |||
| * socket_base::message_end_of_record bit is set then the received data marks | |||
| * the end of a record. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @note The receive operation may not receive all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that the | |||
| * requested amount of data is read before the blocking operation completes. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.receive(asio::buffer(data, size), 0, out_flags); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags in_flags, | |||
| socket_base::message_flags& out_flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, in_flags, out_flags, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the sequenced packet socket. The | |||
| * function call will block until data has been received successfully, or | |||
| * until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param in_flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param out_flags After the receive call completes, contains flags | |||
| * associated with the received data. For example, if the | |||
| * socket_base::message_end_of_record bit is set then the received data marks | |||
| * the end of a record. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes received. Returns 0 if an error occurred. | |||
| * | |||
| * @note The receive operation may not receive all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that the | |||
| * requested amount of data is read before the blocking operation completes. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags in_flags, | |||
| socket_base::message_flags& out_flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().receive(this->get_implementation(), | |||
| buffers, in_flags, out_flags, ec); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive data from the sequenced | |||
| * packet socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param out_flags Once the asynchronous operation completes, contains flags | |||
| * associated with the received data. For example, if the | |||
| * socket_base::message_end_of_record bit is set then the received data marks | |||
| * the end of a record. The caller must guarantee that the referenced | |||
| * variable remains valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.async_receive(asio::buffer(data, size), out_flags, handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags& out_flags, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive( | |||
| this->get_implementation(), buffers, 0, out_flags, | |||
| ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive data from the sequenced | |||
| * data socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param in_flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param out_flags Once the asynchronous operation completes, contains flags | |||
| * associated with the received data. For example, if the | |||
| * socket_base::message_end_of_record bit is set then the received data marks | |||
| * the end of a record. The caller must guarantee that the referenced | |||
| * variable remains valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.async_receive( | |||
| * asio::buffer(data, size), | |||
| * 0, out_flags, handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags in_flags, | |||
| socket_base::message_flags& out_flags, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive( | |||
| this->get_implementation(), buffers, in_flags, out_flags, | |||
| ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BASIC_SEQ_PACKET_SOCKET_HPP | |||
| @@ -0,0 +1,679 @@ | |||
| // | |||
| // basic_serial_port.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // Copyright (c) 2008 Rep Invariant Systems, Inc. (info@repinvariant.com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_SERIAL_PORT_HPP | |||
| #define ASIO_BASIC_SERIAL_PORT_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #if defined(ASIO_HAS_SERIAL_PORT) \ | |||
| || defined(GENERATING_DOCUMENTATION) | |||
| #include <string> | |||
| #include "asio/basic_io_object.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/serial_port_base.hpp" | |||
| #include "asio/serial_port_service.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides serial port functionality. | |||
| /** | |||
| * The basic_serial_port class template provides functionality that is common | |||
| * to all serial ports. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| */ | |||
| template <typename SerialPortService = serial_port_service> | |||
| class basic_serial_port | |||
| : public basic_io_object<SerialPortService>, | |||
| public serial_port_base | |||
| { | |||
| public: | |||
| /// The native representation of a serial port. | |||
| typedef typename SerialPortService::native_handle_type native_handle_type; | |||
| /// A basic_serial_port is always the lowest layer. | |||
| typedef basic_serial_port<SerialPortService> lowest_layer_type; | |||
| /// Construct a basic_serial_port without opening it. | |||
| /** | |||
| * This constructor creates a serial port without opening it. | |||
| * | |||
| * @param io_context The io_context object that the serial port will use to | |||
| * dispatch handlers for any asynchronous operations performed on the port. | |||
| */ | |||
| explicit basic_serial_port(asio::io_context& io_context) | |||
| : basic_io_object<SerialPortService>(io_context) | |||
| { | |||
| } | |||
| /// Construct and open a basic_serial_port. | |||
| /** | |||
| * This constructor creates and opens a serial port for the specified device | |||
| * name. | |||
| * | |||
| * @param io_context The io_context object that the serial port will use to | |||
| * dispatch handlers for any asynchronous operations performed on the port. | |||
| * | |||
| * @param device The platform-specific device name for this serial | |||
| * port. | |||
| */ | |||
| explicit basic_serial_port(asio::io_context& io_context, | |||
| const char* device) | |||
| : basic_io_object<SerialPortService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().open(this->get_implementation(), device, ec); | |||
| asio::detail::throw_error(ec, "open"); | |||
| } | |||
| /// Construct and open a basic_serial_port. | |||
| /** | |||
| * This constructor creates and opens a serial port for the specified device | |||
| * name. | |||
| * | |||
| * @param io_context The io_context object that the serial port will use to | |||
| * dispatch handlers for any asynchronous operations performed on the port. | |||
| * | |||
| * @param device The platform-specific device name for this serial | |||
| * port. | |||
| */ | |||
| explicit basic_serial_port(asio::io_context& io_context, | |||
| const std::string& device) | |||
| : basic_io_object<SerialPortService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().open(this->get_implementation(), device, ec); | |||
| asio::detail::throw_error(ec, "open"); | |||
| } | |||
| /// Construct a basic_serial_port on an existing native serial port. | |||
| /** | |||
| * This constructor creates a serial port object to hold an existing native | |||
| * serial port. | |||
| * | |||
| * @param io_context The io_context object that the serial port will use to | |||
| * dispatch handlers for any asynchronous operations performed on the port. | |||
| * | |||
| * @param native_serial_port A native serial port. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_serial_port(asio::io_context& io_context, | |||
| const native_handle_type& native_serial_port) | |||
| : basic_io_object<SerialPortService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().assign(this->get_implementation(), | |||
| native_serial_port, ec); | |||
| asio::detail::throw_error(ec, "assign"); | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move-construct a basic_serial_port from another. | |||
| /** | |||
| * This constructor moves a serial port from one object to another. | |||
| * | |||
| * @param other The other basic_serial_port object from which the move will | |||
| * occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_serial_port(io_context&) constructor. | |||
| */ | |||
| basic_serial_port(basic_serial_port&& other) | |||
| : basic_io_object<SerialPortService>( | |||
| ASIO_MOVE_CAST(basic_serial_port)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_serial_port from another. | |||
| /** | |||
| * This assignment operator moves a serial port from one object to another. | |||
| * | |||
| * @param other The other basic_serial_port object from which the move will | |||
| * occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_serial_port(io_context&) constructor. | |||
| */ | |||
| basic_serial_port& operator=(basic_serial_port&& other) | |||
| { | |||
| basic_io_object<SerialPortService>::operator=( | |||
| ASIO_MOVE_CAST(basic_serial_port)(other)); | |||
| return *this; | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Get a reference to the lowest layer. | |||
| /** | |||
| * This function returns a reference to the lowest layer in a stack of | |||
| * layers. Since a basic_serial_port cannot contain any further layers, it | |||
| * simply returns a reference to itself. | |||
| * | |||
| * @return A reference to the lowest layer in the stack of layers. Ownership | |||
| * is not transferred to the caller. | |||
| */ | |||
| lowest_layer_type& lowest_layer() | |||
| { | |||
| return *this; | |||
| } | |||
| /// Get a const reference to the lowest layer. | |||
| /** | |||
| * This function returns a const reference to the lowest layer in a stack of | |||
| * layers. Since a basic_serial_port cannot contain any further layers, it | |||
| * simply returns a reference to itself. | |||
| * | |||
| * @return A const reference to the lowest layer in the stack of layers. | |||
| * Ownership is not transferred to the caller. | |||
| */ | |||
| const lowest_layer_type& lowest_layer() const | |||
| { | |||
| return *this; | |||
| } | |||
| /// Open the serial port using the specified device name. | |||
| /** | |||
| * This function opens the serial port for the specified device name. | |||
| * | |||
| * @param device The platform-specific device name. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void open(const std::string& device) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().open(this->get_implementation(), device, ec); | |||
| asio::detail::throw_error(ec, "open"); | |||
| } | |||
| /// Open the serial port using the specified device name. | |||
| /** | |||
| * This function opens the serial port using the given platform-specific | |||
| * device name. | |||
| * | |||
| * @param device The platform-specific device name. | |||
| * | |||
| * @param ec Set the indicate what error occurred, if any. | |||
| */ | |||
| asio::error_code open(const std::string& device, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().open(this->get_implementation(), device, ec); | |||
| } | |||
| /// Assign an existing native serial port to the serial port. | |||
| /* | |||
| * This function opens the serial port to hold an existing native serial port. | |||
| * | |||
| * @param native_serial_port A native serial port. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void assign(const native_handle_type& native_serial_port) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().assign(this->get_implementation(), | |||
| native_serial_port, ec); | |||
| asio::detail::throw_error(ec, "assign"); | |||
| } | |||
| /// Assign an existing native serial port to the serial port. | |||
| /* | |||
| * This function opens the serial port to hold an existing native serial port. | |||
| * | |||
| * @param native_serial_port A native serial port. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| */ | |||
| asio::error_code assign(const native_handle_type& native_serial_port, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().assign(this->get_implementation(), | |||
| native_serial_port, ec); | |||
| } | |||
| /// Determine whether the serial port is open. | |||
| bool is_open() const | |||
| { | |||
| return this->get_service().is_open(this->get_implementation()); | |||
| } | |||
| /// Close the serial port. | |||
| /** | |||
| * This function is used to close the serial port. Any asynchronous read or | |||
| * write operations will be cancelled immediately, and will complete with the | |||
| * asio::error::operation_aborted error. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void close() | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().close(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "close"); | |||
| } | |||
| /// Close the serial port. | |||
| /** | |||
| * This function is used to close the serial port. Any asynchronous read or | |||
| * write operations will be cancelled immediately, and will complete with the | |||
| * asio::error::operation_aborted error. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| */ | |||
| asio::error_code close(asio::error_code& ec) | |||
| { | |||
| return this->get_service().close(this->get_implementation(), ec); | |||
| } | |||
| /// Get the native serial port representation. | |||
| /** | |||
| * This function may be used to obtain the underlying representation of the | |||
| * serial port. This is intended to allow access to native serial port | |||
| * functionality that is not otherwise provided. | |||
| */ | |||
| native_handle_type native_handle() | |||
| { | |||
| return this->get_service().native_handle(this->get_implementation()); | |||
| } | |||
| /// Cancel all asynchronous operations associated with the serial port. | |||
| /** | |||
| * This function causes all outstanding asynchronous read or write operations | |||
| * to finish immediately, and the handlers for cancelled operations will be | |||
| * passed the asio::error::operation_aborted error. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void cancel() | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().cancel(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "cancel"); | |||
| } | |||
| /// Cancel all asynchronous operations associated with the serial port. | |||
| /** | |||
| * This function causes all outstanding asynchronous read or write operations | |||
| * to finish immediately, and the handlers for cancelled operations will be | |||
| * passed the asio::error::operation_aborted error. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| */ | |||
| asio::error_code cancel(asio::error_code& ec) | |||
| { | |||
| return this->get_service().cancel(this->get_implementation(), ec); | |||
| } | |||
| /// Send a break sequence to the serial port. | |||
| /** | |||
| * This function causes a break sequence of platform-specific duration to be | |||
| * sent out the serial port. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void send_break() | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().send_break(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "send_break"); | |||
| } | |||
| /// Send a break sequence to the serial port. | |||
| /** | |||
| * This function causes a break sequence of platform-specific duration to be | |||
| * sent out the serial port. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| */ | |||
| asio::error_code send_break(asio::error_code& ec) | |||
| { | |||
| return this->get_service().send_break(this->get_implementation(), ec); | |||
| } | |||
| /// Set an option on the serial port. | |||
| /** | |||
| * This function is used to set an option on the serial port. | |||
| * | |||
| * @param option The option value to be set on the serial port. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @sa SettableSerialPortOption @n | |||
| * asio::serial_port_base::baud_rate @n | |||
| * asio::serial_port_base::flow_control @n | |||
| * asio::serial_port_base::parity @n | |||
| * asio::serial_port_base::stop_bits @n | |||
| * asio::serial_port_base::character_size | |||
| */ | |||
| template <typename SettableSerialPortOption> | |||
| void set_option(const SettableSerialPortOption& option) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().set_option(this->get_implementation(), option, ec); | |||
| asio::detail::throw_error(ec, "set_option"); | |||
| } | |||
| /// Set an option on the serial port. | |||
| /** | |||
| * This function is used to set an option on the serial port. | |||
| * | |||
| * @param option The option value to be set on the serial port. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @sa SettableSerialPortOption @n | |||
| * asio::serial_port_base::baud_rate @n | |||
| * asio::serial_port_base::flow_control @n | |||
| * asio::serial_port_base::parity @n | |||
| * asio::serial_port_base::stop_bits @n | |||
| * asio::serial_port_base::character_size | |||
| */ | |||
| template <typename SettableSerialPortOption> | |||
| asio::error_code set_option(const SettableSerialPortOption& option, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().set_option( | |||
| this->get_implementation(), option, ec); | |||
| } | |||
| /// Get an option from the serial port. | |||
| /** | |||
| * This function is used to get the current value of an option on the serial | |||
| * port. | |||
| * | |||
| * @param option The option value to be obtained from the serial port. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @sa GettableSerialPortOption @n | |||
| * asio::serial_port_base::baud_rate @n | |||
| * asio::serial_port_base::flow_control @n | |||
| * asio::serial_port_base::parity @n | |||
| * asio::serial_port_base::stop_bits @n | |||
| * asio::serial_port_base::character_size | |||
| */ | |||
| template <typename GettableSerialPortOption> | |||
| void get_option(GettableSerialPortOption& option) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().get_option(this->get_implementation(), option, ec); | |||
| asio::detail::throw_error(ec, "get_option"); | |||
| } | |||
| /// Get an option from the serial port. | |||
| /** | |||
| * This function is used to get the current value of an option on the serial | |||
| * port. | |||
| * | |||
| * @param option The option value to be obtained from the serial port. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @sa GettableSerialPortOption @n | |||
| * asio::serial_port_base::baud_rate @n | |||
| * asio::serial_port_base::flow_control @n | |||
| * asio::serial_port_base::parity @n | |||
| * asio::serial_port_base::stop_bits @n | |||
| * asio::serial_port_base::character_size | |||
| */ | |||
| template <typename GettableSerialPortOption> | |||
| asio::error_code get_option(GettableSerialPortOption& option, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().get_option( | |||
| this->get_implementation(), option, ec); | |||
| } | |||
| /// Write some data to the serial port. | |||
| /** | |||
| * This function is used to write data to the serial port. The function call | |||
| * will block until one or more bytes of the data has been written | |||
| * successfully, or until an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be written to the serial port. | |||
| * | |||
| * @returns The number of bytes written. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @note The write_some operation may not transmit all of the data to the | |||
| * peer. Consider using the @ref write function if you need to ensure that | |||
| * all data is written before the blocking operation completes. | |||
| * | |||
| * @par Example | |||
| * To write a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * serial_port.write_some(asio::buffer(data, size)); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on writing multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().write_some( | |||
| this->get_implementation(), buffers, ec); | |||
| asio::detail::throw_error(ec, "write_some"); | |||
| return s; | |||
| } | |||
| /// Write some data to the serial port. | |||
| /** | |||
| * This function is used to write data to the serial port. The function call | |||
| * will block until one or more bytes of the data has been written | |||
| * successfully, or until an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be written to the serial port. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes written. Returns 0 if an error occurred. | |||
| * | |||
| * @note The write_some operation may not transmit all of the data to the | |||
| * peer. Consider using the @ref write function if you need to ensure that | |||
| * all data is written before the blocking operation completes. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().write_some( | |||
| this->get_implementation(), buffers, ec); | |||
| } | |||
| /// Start an asynchronous write. | |||
| /** | |||
| * This function is used to asynchronously write data to the serial port. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be written to the serial port. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the write operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes written. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The write operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref async_write function if you need to ensure that all | |||
| * data is written before the asynchronous operation completes. | |||
| * | |||
| * @par Example | |||
| * To write a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * serial_port.async_write_some(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on writing multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_write_some(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_write_some(this->get_implementation(), | |||
| buffers, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Read some data from the serial port. | |||
| /** | |||
| * This function is used to read data from the serial port. The function | |||
| * call will block until one or more bytes of data has been read successfully, | |||
| * or until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be read. | |||
| * | |||
| * @returns The number of bytes read. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @note The read_some operation may not read all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that | |||
| * the requested amount of data is read before the blocking operation | |||
| * completes. | |||
| * | |||
| * @par Example | |||
| * To read into a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * serial_port.read_some(asio::buffer(data, size)); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on reading into multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().read_some( | |||
| this->get_implementation(), buffers, ec); | |||
| asio::detail::throw_error(ec, "read_some"); | |||
| return s; | |||
| } | |||
| /// Read some data from the serial port. | |||
| /** | |||
| * This function is used to read data from the serial port. The function | |||
| * call will block until one or more bytes of data has been read successfully, | |||
| * or until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be read. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes read. Returns 0 if an error occurred. | |||
| * | |||
| * @note The read_some operation may not read all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that | |||
| * the requested amount of data is read before the blocking operation | |||
| * completes. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().read_some( | |||
| this->get_implementation(), buffers, ec); | |||
| } | |||
| /// Start an asynchronous read. | |||
| /** | |||
| * This function is used to asynchronously read data from the serial port. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be read. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the read operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes read. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The read operation may not read all of the requested number of bytes. | |||
| * Consider using the @ref async_read function if you need to ensure that the | |||
| * requested amount of data is read before the asynchronous operation | |||
| * completes. | |||
| * | |||
| * @par Example | |||
| * To read into a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * serial_port.async_read_some(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on reading into multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_read_some(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_read_some(this->get_implementation(), | |||
| buffers, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // defined(ASIO_HAS_SERIAL_PORT) | |||
| // || defined(GENERATING_DOCUMENTATION) | |||
| #endif // ASIO_BASIC_SERIAL_PORT_HPP | |||
| @@ -0,0 +1,386 @@ | |||
| // | |||
| // basic_signal_set.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_SIGNAL_SET_HPP | |||
| #define ASIO_BASIC_SIGNAL_SET_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include "asio/basic_io_object.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/signal_set_service.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides signal functionality. | |||
| /** | |||
| * The basic_signal_set class template provides the ability to perform an | |||
| * asynchronous wait for one or more signals to occur. | |||
| * | |||
| * Most applications will use the asio::signal_set typedef. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| * | |||
| * @par Example | |||
| * Performing an asynchronous wait: | |||
| * @code | |||
| * void handler( | |||
| * const asio::error_code& error, | |||
| * int signal_number) | |||
| * { | |||
| * if (!error) | |||
| * { | |||
| * // A signal occurred. | |||
| * } | |||
| * } | |||
| * | |||
| * ... | |||
| * | |||
| * // Construct a signal set registered for process termination. | |||
| * asio::signal_set signals(io_context, SIGINT, SIGTERM); | |||
| * | |||
| * // Start an asynchronous wait for one of the signals to occur. | |||
| * signals.async_wait(handler); | |||
| * @endcode | |||
| * | |||
| * @par Queueing of signal notifications | |||
| * | |||
| * If a signal is registered with a signal_set, and the signal occurs when | |||
| * there are no waiting handlers, then the signal notification is queued. The | |||
| * next async_wait operation on that signal_set will dequeue the notification. | |||
| * If multiple notifications are queued, subsequent async_wait operations | |||
| * dequeue them one at a time. Signal notifications are dequeued in order of | |||
| * ascending signal number. | |||
| * | |||
| * If a signal number is removed from a signal_set (using the @c remove or @c | |||
| * erase member functions) then any queued notifications for that signal are | |||
| * discarded. | |||
| * | |||
| * @par Multiple registration of signals | |||
| * | |||
| * The same signal number may be registered with different signal_set objects. | |||
| * When the signal occurs, one handler is called for each signal_set object. | |||
| * | |||
| * Note that multiple registration only works for signals that are registered | |||
| * using Asio. The application must not also register a signal handler using | |||
| * functions such as @c signal() or @c sigaction(). | |||
| * | |||
| * @par Signal masking on POSIX platforms | |||
| * | |||
| * POSIX allows signals to be blocked using functions such as @c sigprocmask() | |||
| * and @c pthread_sigmask(). For signals to be delivered, programs must ensure | |||
| * that any signals registered using signal_set objects are unblocked in at | |||
| * least one thread. | |||
| */ | |||
| template <typename SignalSetService = signal_set_service> | |||
| class basic_signal_set | |||
| : public basic_io_object<SignalSetService> | |||
| { | |||
| public: | |||
| /// Construct a signal set without adding any signals. | |||
| /** | |||
| * This constructor creates a signal set without registering for any signals. | |||
| * | |||
| * @param io_context The io_context object that the signal set will use to | |||
| * dispatch handlers for any asynchronous operations performed on the set. | |||
| */ | |||
| explicit basic_signal_set(asio::io_context& io_context) | |||
| : basic_io_object<SignalSetService>(io_context) | |||
| { | |||
| } | |||
| /// Construct a signal set and add one signal. | |||
| /** | |||
| * This constructor creates a signal set and registers for one signal. | |||
| * | |||
| * @param io_context The io_context object that the signal set will use to | |||
| * dispatch handlers for any asynchronous operations performed on the set. | |||
| * | |||
| * @param signal_number_1 The signal number to be added. | |||
| * | |||
| * @note This constructor is equivalent to performing: | |||
| * @code asio::signal_set signals(io_context); | |||
| * signals.add(signal_number_1); @endcode | |||
| */ | |||
| basic_signal_set(asio::io_context& io_context, int signal_number_1) | |||
| : basic_io_object<SignalSetService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().add(this->get_implementation(), signal_number_1, ec); | |||
| asio::detail::throw_error(ec, "add"); | |||
| } | |||
| /// Construct a signal set and add two signals. | |||
| /** | |||
| * This constructor creates a signal set and registers for two signals. | |||
| * | |||
| * @param io_context The io_context object that the signal set will use to | |||
| * dispatch handlers for any asynchronous operations performed on the set. | |||
| * | |||
| * @param signal_number_1 The first signal number to be added. | |||
| * | |||
| * @param signal_number_2 The second signal number to be added. | |||
| * | |||
| * @note This constructor is equivalent to performing: | |||
| * @code asio::signal_set signals(io_context); | |||
| * signals.add(signal_number_1); | |||
| * signals.add(signal_number_2); @endcode | |||
| */ | |||
| basic_signal_set(asio::io_context& io_context, int signal_number_1, | |||
| int signal_number_2) | |||
| : basic_io_object<SignalSetService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().add(this->get_implementation(), signal_number_1, ec); | |||
| asio::detail::throw_error(ec, "add"); | |||
| this->get_service().add(this->get_implementation(), signal_number_2, ec); | |||
| asio::detail::throw_error(ec, "add"); | |||
| } | |||
| /// Construct a signal set and add three signals. | |||
| /** | |||
| * This constructor creates a signal set and registers for three signals. | |||
| * | |||
| * @param io_context The io_context object that the signal set will use to | |||
| * dispatch handlers for any asynchronous operations performed on the set. | |||
| * | |||
| * @param signal_number_1 The first signal number to be added. | |||
| * | |||
| * @param signal_number_2 The second signal number to be added. | |||
| * | |||
| * @param signal_number_3 The third signal number to be added. | |||
| * | |||
| * @note This constructor is equivalent to performing: | |||
| * @code asio::signal_set signals(io_context); | |||
| * signals.add(signal_number_1); | |||
| * signals.add(signal_number_2); | |||
| * signals.add(signal_number_3); @endcode | |||
| */ | |||
| basic_signal_set(asio::io_context& io_context, int signal_number_1, | |||
| int signal_number_2, int signal_number_3) | |||
| : basic_io_object<SignalSetService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().add(this->get_implementation(), signal_number_1, ec); | |||
| asio::detail::throw_error(ec, "add"); | |||
| this->get_service().add(this->get_implementation(), signal_number_2, ec); | |||
| asio::detail::throw_error(ec, "add"); | |||
| this->get_service().add(this->get_implementation(), signal_number_3, ec); | |||
| asio::detail::throw_error(ec, "add"); | |||
| } | |||
| /// Add a signal to a signal_set. | |||
| /** | |||
| * This function adds the specified signal to the set. It has no effect if the | |||
| * signal is already in the set. | |||
| * | |||
| * @param signal_number The signal to be added to the set. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void add(int signal_number) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().add(this->get_implementation(), signal_number, ec); | |||
| asio::detail::throw_error(ec, "add"); | |||
| } | |||
| /// Add a signal to a signal_set. | |||
| /** | |||
| * This function adds the specified signal to the set. It has no effect if the | |||
| * signal is already in the set. | |||
| * | |||
| * @param signal_number The signal to be added to the set. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| */ | |||
| asio::error_code add(int signal_number, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().add( | |||
| this->get_implementation(), signal_number, ec); | |||
| } | |||
| /// Remove a signal from a signal_set. | |||
| /** | |||
| * This function removes the specified signal from the set. It has no effect | |||
| * if the signal is not in the set. | |||
| * | |||
| * @param signal_number The signal to be removed from the set. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note Removes any notifications that have been queued for the specified | |||
| * signal number. | |||
| */ | |||
| void remove(int signal_number) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().remove(this->get_implementation(), signal_number, ec); | |||
| asio::detail::throw_error(ec, "remove"); | |||
| } | |||
| /// Remove a signal from a signal_set. | |||
| /** | |||
| * This function removes the specified signal from the set. It has no effect | |||
| * if the signal is not in the set. | |||
| * | |||
| * @param signal_number The signal to be removed from the set. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @note Removes any notifications that have been queued for the specified | |||
| * signal number. | |||
| */ | |||
| asio::error_code remove(int signal_number, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().remove( | |||
| this->get_implementation(), signal_number, ec); | |||
| } | |||
| /// Remove all signals from a signal_set. | |||
| /** | |||
| * This function removes all signals from the set. It has no effect if the set | |||
| * is already empty. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note Removes all queued notifications. | |||
| */ | |||
| void clear() | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().clear(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "clear"); | |||
| } | |||
| /// Remove all signals from a signal_set. | |||
| /** | |||
| * This function removes all signals from the set. It has no effect if the set | |||
| * is already empty. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @note Removes all queued notifications. | |||
| */ | |||
| asio::error_code clear(asio::error_code& ec) | |||
| { | |||
| return this->get_service().clear(this->get_implementation(), ec); | |||
| } | |||
| /// Cancel all operations associated with the signal set. | |||
| /** | |||
| * This function forces the completion of any pending asynchronous wait | |||
| * operations against the signal set. The handler for each cancelled | |||
| * operation will be invoked with the asio::error::operation_aborted | |||
| * error code. | |||
| * | |||
| * Cancellation does not alter the set of registered signals. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If a registered signal occurred before cancel() is called, then the | |||
| * handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| void cancel() | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().cancel(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "cancel"); | |||
| } | |||
| /// Cancel all operations associated with the signal set. | |||
| /** | |||
| * This function forces the completion of any pending asynchronous wait | |||
| * operations against the signal set. The handler for each cancelled | |||
| * operation will be invoked with the asio::error::operation_aborted | |||
| * error code. | |||
| * | |||
| * Cancellation does not alter the set of registered signals. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @note If a registered signal occurred before cancel() is called, then the | |||
| * handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| asio::error_code cancel(asio::error_code& ec) | |||
| { | |||
| return this->get_service().cancel(this->get_implementation(), ec); | |||
| } | |||
| /// Start an asynchronous operation to wait for a signal to be delivered. | |||
| /** | |||
| * This function may be used to initiate an asynchronous wait against the | |||
| * signal set. It always returns immediately. | |||
| * | |||
| * For each call to async_wait(), the supplied handler will be called exactly | |||
| * once. The handler will be called when: | |||
| * | |||
| * @li One of the registered signals in the signal set occurs; or | |||
| * | |||
| * @li The signal set was cancelled, in which case the handler is passed the | |||
| * error code asio::error::operation_aborted. | |||
| * | |||
| * @param handler The handler to be called when the signal occurs. Copies | |||
| * will be made of the handler as required. The function signature of the | |||
| * handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * int signal_number // Indicates which signal occurred. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| */ | |||
| template <typename SignalHandler> | |||
| ASIO_INITFN_RESULT_TYPE(SignalHandler, | |||
| void (asio::error_code, int)) | |||
| async_wait(ASIO_MOVE_ARG(SignalHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a SignalHandler. | |||
| ASIO_SIGNAL_HANDLER_CHECK(SignalHandler, handler) type_check; | |||
| return this->get_service().async_wait(this->get_implementation(), | |||
| ASIO_MOVE_CAST(SignalHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BASIC_SIGNAL_SET_HPP | |||
| @@ -0,0 +1,325 @@ | |||
| // | |||
| // basic_socket_iostream.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_SOCKET_IOSTREAM_HPP | |||
| #define ASIO_BASIC_SOCKET_IOSTREAM_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #if !defined(ASIO_NO_IOSTREAM) | |||
| #include <istream> | |||
| #include <ostream> | |||
| #include "asio/basic_socket_streambuf.hpp" | |||
| #include "asio/stream_socket_service.hpp" | |||
| #if !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| # include "asio/detail/variadic_templates.hpp" | |||
| // A macro that should expand to: | |||
| // template <typename T1, ..., typename Tn> | |||
| // explicit basic_socket_iostream(T1 x1, ..., Tn xn) | |||
| // : std::basic_iostream<char>( | |||
| // &this->detail::socket_iostream_base< | |||
| // Protocol, StreamSocketService, Time, | |||
| // TimeTraits, TimerService>::streambuf_) | |||
| // { | |||
| // if (rdbuf()->connect(x1, ..., xn) == 0) | |||
| // this->setstate(std::ios_base::failbit); | |||
| // } | |||
| // This macro should only persist within this file. | |||
| # define ASIO_PRIVATE_CTR_DEF(n) \ | |||
| template <ASIO_VARIADIC_TPARAMS(n)> \ | |||
| explicit basic_socket_iostream(ASIO_VARIADIC_BYVAL_PARAMS(n)) \ | |||
| : std::basic_iostream<char>( \ | |||
| &this->detail::socket_iostream_base< \ | |||
| Protocol, StreamSocketService, Time, \ | |||
| TimeTraits, TimerService>::streambuf_) \ | |||
| { \ | |||
| this->setf(std::ios_base::unitbuf); \ | |||
| if (rdbuf()->connect(ASIO_VARIADIC_BYVAL_ARGS(n)) == 0) \ | |||
| this->setstate(std::ios_base::failbit); \ | |||
| } \ | |||
| /**/ | |||
| // A macro that should expand to: | |||
| // template <typename T1, ..., typename Tn> | |||
| // void connect(T1 x1, ..., Tn xn) | |||
| // { | |||
| // if (rdbuf()->connect(x1, ..., xn) == 0) | |||
| // this->setstate(std::ios_base::failbit); | |||
| // } | |||
| // This macro should only persist within this file. | |||
| # define ASIO_PRIVATE_CONNECT_DEF(n) \ | |||
| template <ASIO_VARIADIC_TPARAMS(n)> \ | |||
| void connect(ASIO_VARIADIC_BYVAL_PARAMS(n)) \ | |||
| { \ | |||
| if (rdbuf()->connect(ASIO_VARIADIC_BYVAL_ARGS(n)) == 0) \ | |||
| this->setstate(std::ios_base::failbit); \ | |||
| } \ | |||
| /**/ | |||
| #endif // !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| namespace detail { | |||
| // A separate base class is used to ensure that the streambuf is initialised | |||
| // prior to the basic_socket_iostream's basic_iostream base class. | |||
| template <typename Protocol, typename StreamSocketService, | |||
| typename Time, typename TimeTraits, typename TimerService> | |||
| class socket_iostream_base | |||
| { | |||
| protected: | |||
| basic_socket_streambuf<Protocol, StreamSocketService, | |||
| Time, TimeTraits, TimerService> streambuf_; | |||
| }; | |||
| } | |||
| /// Iostream interface for a socket. | |||
| template <typename Protocol, | |||
| typename StreamSocketService = stream_socket_service<Protocol>, | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) \ | |||
| || defined(GENERATING_DOCUMENTATION) | |||
| typename Time = boost::posix_time::ptime, | |||
| typename TimeTraits = asio::time_traits<Time>, | |||
| typename TimerService = deadline_timer_service<Time, TimeTraits> > | |||
| #else | |||
| typename Time = steady_timer::clock_type, | |||
| typename TimeTraits = steady_timer::traits_type, | |||
| typename TimerService = steady_timer::service_type> | |||
| #endif | |||
| class basic_socket_iostream | |||
| : private detail::socket_iostream_base<Protocol, | |||
| StreamSocketService, Time, TimeTraits, TimerService>, | |||
| public std::basic_iostream<char> | |||
| { | |||
| private: | |||
| // These typedefs are intended keep this class's implementation independent | |||
| // of whether it's using Boost.DateTime, Boost.Chrono or std::chrono. | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| typedef TimeTraits traits_helper; | |||
| #else | |||
| typedef detail::chrono_time_traits<Time, TimeTraits> traits_helper; | |||
| #endif | |||
| public: | |||
| /// The endpoint type. | |||
| typedef typename Protocol::endpoint endpoint_type; | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// (Deprecated: Use time_point.) The time type. | |||
| typedef typename TimeTraits::time_type time_type; | |||
| /// The time type. | |||
| typedef typename TimeTraits::time_point time_point; | |||
| /// (Deprecated: Use duration.) The duration type. | |||
| typedef typename TimeTraits::duration_type duration_type; | |||
| /// The duration type. | |||
| typedef typename TimeTraits::duration duration; | |||
| #else | |||
| # if !defined(ASIO_NO_DEPRECATED) | |||
| typedef typename traits_helper::time_type time_type; | |||
| typedef typename traits_helper::duration_type duration_type; | |||
| # endif // !defined(ASIO_NO_DEPRECATED) | |||
| typedef typename traits_helper::time_type time_point; | |||
| typedef typename traits_helper::duration_type duration; | |||
| #endif | |||
| /// Construct a basic_socket_iostream without establishing a connection. | |||
| basic_socket_iostream() | |||
| : std::basic_iostream<char>( | |||
| &this->detail::socket_iostream_base< | |||
| Protocol, StreamSocketService, Time, | |||
| TimeTraits, TimerService>::streambuf_) | |||
| { | |||
| this->setf(std::ios_base::unitbuf); | |||
| } | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// Establish a connection to an endpoint corresponding to a resolver query. | |||
| /** | |||
| * This constructor automatically establishes a connection based on the | |||
| * supplied resolver query parameters. The arguments are used to construct | |||
| * a resolver query object. | |||
| */ | |||
| template <typename T1, ..., typename TN> | |||
| explicit basic_socket_iostream(T1 t1, ..., TN tn); | |||
| #elif defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| template <typename... T> | |||
| explicit basic_socket_iostream(T... x) | |||
| : std::basic_iostream<char>( | |||
| &this->detail::socket_iostream_base< | |||
| Protocol, StreamSocketService, Time, | |||
| TimeTraits, TimerService>::streambuf_) | |||
| { | |||
| this->setf(std::ios_base::unitbuf); | |||
| if (rdbuf()->connect(x...) == 0) | |||
| this->setstate(std::ios_base::failbit); | |||
| } | |||
| #else | |||
| ASIO_VARIADIC_GENERATE(ASIO_PRIVATE_CTR_DEF) | |||
| #endif | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// Establish a connection to an endpoint corresponding to a resolver query. | |||
| /** | |||
| * This function automatically establishes a connection based on the supplied | |||
| * resolver query parameters. The arguments are used to construct a resolver | |||
| * query object. | |||
| */ | |||
| template <typename T1, ..., typename TN> | |||
| void connect(T1 t1, ..., TN tn); | |||
| #elif defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| template <typename... T> | |||
| void connect(T... x) | |||
| { | |||
| if (rdbuf()->connect(x...) == 0) | |||
| this->setstate(std::ios_base::failbit); | |||
| } | |||
| #else | |||
| ASIO_VARIADIC_GENERATE(ASIO_PRIVATE_CONNECT_DEF) | |||
| #endif | |||
| /// Close the connection. | |||
| void close() | |||
| { | |||
| if (rdbuf()->close() == 0) | |||
| this->setstate(std::ios_base::failbit); | |||
| } | |||
| /// Return a pointer to the underlying streambuf. | |||
| basic_socket_streambuf<Protocol, StreamSocketService, | |||
| Time, TimeTraits, TimerService>* rdbuf() const | |||
| { | |||
| return const_cast<basic_socket_streambuf<Protocol, StreamSocketService, | |||
| Time, TimeTraits, TimerService>*>( | |||
| &this->detail::socket_iostream_base< | |||
| Protocol, StreamSocketService, Time, | |||
| TimeTraits, TimerService>::streambuf_); | |||
| } | |||
| /// Get the last error associated with the stream. | |||
| /** | |||
| * @return An \c error_code corresponding to the last error from the stream. | |||
| * | |||
| * @par Example | |||
| * To print the error associated with a failure to establish a connection: | |||
| * @code tcp::iostream s("www.boost.org", "http"); | |||
| * if (!s) | |||
| * { | |||
| * std::cout << "Error: " << s.error().message() << std::endl; | |||
| * } @endcode | |||
| */ | |||
| const asio::error_code& error() const | |||
| { | |||
| return rdbuf()->puberror(); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use expiry().) Get the stream's expiry time as an absolute | |||
| /// time. | |||
| /** | |||
| * @return An absolute time value representing the stream's expiry time. | |||
| */ | |||
| time_point expires_at() const | |||
| { | |||
| return rdbuf()->expires_at(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// Get the stream's expiry time as an absolute time. | |||
| /** | |||
| * @return An absolute time value representing the stream's expiry time. | |||
| */ | |||
| time_point expiry() const | |||
| { | |||
| return rdbuf()->expiry(); | |||
| } | |||
| /// Set the stream's expiry time as an absolute time. | |||
| /** | |||
| * This function sets the expiry time associated with the stream. Stream | |||
| * operations performed after this time (where the operations cannot be | |||
| * completed using the internal buffers) will fail with the error | |||
| * asio::error::operation_aborted. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the stream. | |||
| */ | |||
| void expires_at(const time_point& expiry_time) | |||
| { | |||
| rdbuf()->expires_at(expiry_time); | |||
| } | |||
| /// Set the stream's expiry time relative to now. | |||
| /** | |||
| * This function sets the expiry time associated with the stream. Stream | |||
| * operations performed after this time (where the operations cannot be | |||
| * completed using the internal buffers) will fail with the error | |||
| * asio::error::operation_aborted. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| */ | |||
| void expires_after(const duration& expiry_time) | |||
| { | |||
| rdbuf()->expires_after(expiry_time); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use expiry().) Get the stream's expiry time relative to now. | |||
| /** | |||
| * @return A relative time value representing the stream's expiry time. | |||
| */ | |||
| duration expires_from_now() const | |||
| { | |||
| return rdbuf()->expires_from_now(); | |||
| } | |||
| /// (Deprecated: Use expires_after().) Set the stream's expiry time relative | |||
| /// to now. | |||
| /** | |||
| * This function sets the expiry time associated with the stream. Stream | |||
| * operations performed after this time (where the operations cannot be | |||
| * completed using the internal buffers) will fail with the error | |||
| * asio::error::operation_aborted. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| */ | |||
| void expires_from_now(const duration& expiry_time) | |||
| { | |||
| rdbuf()->expires_from_now(expiry_time); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #if !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| # undef ASIO_PRIVATE_CTR_DEF | |||
| # undef ASIO_PRIVATE_CONNECT_DEF | |||
| #endif // !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| #endif // !defined(ASIO_NO_IOSTREAM) | |||
| #endif // ASIO_BASIC_SOCKET_IOSTREAM_HPP | |||
| @@ -0,0 +1,646 @@ | |||
| // | |||
| // basic_socket_streambuf.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_SOCKET_STREAMBUF_HPP | |||
| #define ASIO_BASIC_SOCKET_STREAMBUF_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #if !defined(ASIO_NO_IOSTREAM) | |||
| #include <streambuf> | |||
| #include "asio/basic_socket.hpp" | |||
| #include "asio/deadline_timer_service.hpp" | |||
| #include "asio/detail/array.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/io_context.hpp" | |||
| #include "asio/stream_socket_service.hpp" | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| # include "asio/deadline_timer.hpp" | |||
| #else | |||
| # include "asio/steady_timer.hpp" | |||
| #endif | |||
| #if !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| # include "asio/detail/variadic_templates.hpp" | |||
| // A macro that should expand to: | |||
| // template <typename T1, ..., typename Tn> | |||
| // basic_socket_streambuf<Protocol, StreamSocketService, | |||
| // Time, TimeTraits, TimerService>* connect( | |||
| // T1 x1, ..., Tn xn) | |||
| // { | |||
| // init_buffers(); | |||
| // this->basic_socket<Protocol, StreamSocketService>::close(ec_); | |||
| // typedef typename Protocol::resolver resolver_type; | |||
| // resolver_type resolver(detail::socket_streambuf_base::io_context_); | |||
| // connect_to_endpoints( | |||
| // resolver.resolve(x1, ..., xn, ec_)); | |||
| // return !ec_ ? this : 0; | |||
| // } | |||
| // This macro should only persist within this file. | |||
| # define ASIO_PRIVATE_CONNECT_DEF(n) \ | |||
| template <ASIO_VARIADIC_TPARAMS(n)> \ | |||
| basic_socket_streambuf<Protocol, StreamSocketService, \ | |||
| Time, TimeTraits, TimerService>* connect( \ | |||
| ASIO_VARIADIC_BYVAL_PARAMS(n)) \ | |||
| { \ | |||
| init_buffers(); \ | |||
| this->basic_socket<Protocol, StreamSocketService>::close(ec_); \ | |||
| typedef typename Protocol::resolver resolver_type; \ | |||
| resolver_type resolver(detail::socket_streambuf_base::io_context_); \ | |||
| connect_to_endpoints( \ | |||
| resolver.resolve(ASIO_VARIADIC_BYVAL_ARGS(n), ec_)); \ | |||
| return !ec_ ? this : 0; \ | |||
| } \ | |||
| /**/ | |||
| #endif // !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| namespace detail { | |||
| // A separate base class is used to ensure that the io_context is initialised | |||
| // prior to the basic_socket_streambuf's basic_socket base class. | |||
| class socket_streambuf_base | |||
| { | |||
| protected: | |||
| io_context io_context_; | |||
| }; | |||
| } // namespace detail | |||
| /// Iostream streambuf for a socket. | |||
| template <typename Protocol, | |||
| typename StreamSocketService = stream_socket_service<Protocol>, | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) \ | |||
| || defined(GENERATING_DOCUMENTATION) | |||
| typename Time = boost::posix_time::ptime, | |||
| typename TimeTraits = asio::time_traits<Time>, | |||
| typename TimerService = deadline_timer_service<Time, TimeTraits> > | |||
| #else | |||
| typename Time = steady_timer::clock_type, | |||
| typename TimeTraits = steady_timer::traits_type, | |||
| typename TimerService = steady_timer::service_type> | |||
| #endif | |||
| class basic_socket_streambuf | |||
| : public std::streambuf, | |||
| private detail::socket_streambuf_base, | |||
| public basic_socket<Protocol, StreamSocketService> | |||
| { | |||
| private: | |||
| // These typedefs are intended keep this class's implementation independent | |||
| // of whether it's using Boost.DateTime, Boost.Chrono or std::chrono. | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| typedef TimeTraits traits_helper; | |||
| #else | |||
| typedef detail::chrono_time_traits<Time, TimeTraits> traits_helper; | |||
| #endif | |||
| public: | |||
| /// The endpoint type. | |||
| typedef typename Protocol::endpoint endpoint_type; | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// (Deprecated: Use time_point.) The time type. | |||
| typedef typename TimeTraits::time_type time_type; | |||
| /// The time type. | |||
| typedef typename TimeTraits::time_point time_point; | |||
| /// (Deprecated: Use duration.) The duration type. | |||
| typedef typename TimeTraits::duration_type duration_type; | |||
| /// The duration type. | |||
| typedef typename TimeTraits::duration duration; | |||
| #else | |||
| # if !defined(ASIO_NO_DEPRECATED) | |||
| typedef typename traits_helper::time_type time_type; | |||
| typedef typename traits_helper::duration_type duration_type; | |||
| # endif // !defined(ASIO_NO_DEPRECATED) | |||
| typedef typename traits_helper::time_type time_point; | |||
| typedef typename traits_helper::duration_type duration; | |||
| #endif | |||
| /// Construct a basic_socket_streambuf without establishing a connection. | |||
| basic_socket_streambuf() | |||
| : basic_socket<Protocol, StreamSocketService>( | |||
| this->detail::socket_streambuf_base::io_context_), | |||
| unbuffered_(false), | |||
| timer_service_(0), | |||
| timer_state_(no_timer) | |||
| { | |||
| init_buffers(); | |||
| } | |||
| /// Destructor flushes buffered data. | |||
| virtual ~basic_socket_streambuf() | |||
| { | |||
| if (pptr() != pbase()) | |||
| overflow(traits_type::eof()); | |||
| destroy_timer(); | |||
| } | |||
| /// Establish a connection. | |||
| /** | |||
| * This function establishes a connection to the specified endpoint. | |||
| * | |||
| * @return \c this if a connection was successfully established, a null | |||
| * pointer otherwise. | |||
| */ | |||
| basic_socket_streambuf<Protocol, StreamSocketService, | |||
| Time, TimeTraits, TimerService>* connect( | |||
| const endpoint_type& endpoint) | |||
| { | |||
| init_buffers(); | |||
| this->basic_socket<Protocol, StreamSocketService>::close(ec_); | |||
| if (timer_state_ == timer_has_expired) | |||
| { | |||
| ec_ = asio::error::operation_aborted; | |||
| return 0; | |||
| } | |||
| io_handler handler = { this }; | |||
| this->basic_socket<Protocol, StreamSocketService>::async_connect( | |||
| endpoint, handler); | |||
| ec_ = asio::error::would_block; | |||
| this->get_service().get_io_context().restart(); | |||
| do this->get_service().get_io_context().run_one(); | |||
| while (ec_ == asio::error::would_block); | |||
| return !ec_ ? this : 0; | |||
| } | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// Establish a connection. | |||
| /** | |||
| * This function automatically establishes a connection based on the supplied | |||
| * resolver query parameters. The arguments are used to construct a resolver | |||
| * query object. | |||
| * | |||
| * @return \c this if a connection was successfully established, a null | |||
| * pointer otherwise. | |||
| */ | |||
| template <typename T1, ..., typename TN> | |||
| basic_socket_streambuf<Protocol, StreamSocketService>* connect( | |||
| T1 t1, ..., TN tn); | |||
| #elif defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| template <typename... T> | |||
| basic_socket_streambuf<Protocol, StreamSocketService, | |||
| Time, TimeTraits, TimerService>* connect(T... x) | |||
| { | |||
| init_buffers(); | |||
| this->basic_socket<Protocol, StreamSocketService>::close(ec_); | |||
| typedef typename Protocol::resolver resolver_type; | |||
| resolver_type resolver(detail::socket_streambuf_base::io_context_); | |||
| connect_to_endpoints(resolver.resolve(x..., ec_)); | |||
| return !ec_ ? this : 0; | |||
| } | |||
| #else | |||
| ASIO_VARIADIC_GENERATE(ASIO_PRIVATE_CONNECT_DEF) | |||
| #endif | |||
| /// Close the connection. | |||
| /** | |||
| * @return \c this if a connection was successfully established, a null | |||
| * pointer otherwise. | |||
| */ | |||
| basic_socket_streambuf<Protocol, StreamSocketService, | |||
| Time, TimeTraits, TimerService>* close() | |||
| { | |||
| sync(); | |||
| this->basic_socket<Protocol, StreamSocketService>::close(ec_); | |||
| if (!ec_) | |||
| init_buffers(); | |||
| return !ec_ ? this : 0; | |||
| } | |||
| /// Get the last error associated with the stream buffer. | |||
| /** | |||
| * @return An \c error_code corresponding to the last error from the stream | |||
| * buffer. | |||
| */ | |||
| const asio::error_code& puberror() const | |||
| { | |||
| return error(); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use expiry().) Get the stream buffer's expiry time as an | |||
| /// absolute time. | |||
| /** | |||
| * @return An absolute time value representing the stream buffer's expiry | |||
| * time. | |||
| */ | |||
| time_point expires_at() const | |||
| { | |||
| return timer_service_ | |||
| ? timer_service_->expires_at(timer_implementation_) | |||
| : time_point(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// Get the stream buffer's expiry time as an absolute time. | |||
| /** | |||
| * @return An absolute time value representing the stream buffer's expiry | |||
| * time. | |||
| */ | |||
| time_point expiry() const | |||
| { | |||
| return timer_service_ | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| ? timer_service_->expires_at(timer_implementation_) | |||
| #else // defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| ? timer_service_->expiry(timer_implementation_) | |||
| #endif // defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| : time_point(); | |||
| } | |||
| /// Set the stream buffer's expiry time as an absolute time. | |||
| /** | |||
| * This function sets the expiry time associated with the stream. Stream | |||
| * operations performed after this time (where the operations cannot be | |||
| * completed using the internal buffers) will fail with the error | |||
| * asio::error::operation_aborted. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the stream. | |||
| */ | |||
| void expires_at(const time_point& expiry_time) | |||
| { | |||
| construct_timer(); | |||
| asio::error_code ec; | |||
| timer_service_->expires_at(timer_implementation_, expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_at"); | |||
| start_timer(); | |||
| } | |||
| /// Set the stream buffer's expiry time relative to now. | |||
| /** | |||
| * This function sets the expiry time associated with the stream. Stream | |||
| * operations performed after this time (where the operations cannot be | |||
| * completed using the internal buffers) will fail with the error | |||
| * asio::error::operation_aborted. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| */ | |||
| void expires_at(const duration& expiry_time) | |||
| { | |||
| construct_timer(); | |||
| asio::error_code ec; | |||
| timer_service_->expires_from_now(timer_implementation_, expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_from_now"); | |||
| start_timer(); | |||
| } | |||
| /// Set the stream buffer's expiry time relative to now. | |||
| /** | |||
| * This function sets the expiry time associated with the stream. Stream | |||
| * operations performed after this time (where the operations cannot be | |||
| * completed using the internal buffers) will fail with the error | |||
| * asio::error::operation_aborted. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| */ | |||
| void expires_after(const duration& expiry_time) | |||
| { | |||
| construct_timer(); | |||
| asio::error_code ec; | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| timer_service_->expires_from_now(timer_implementation_, expiry_time, ec); | |||
| #else // defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| timer_service_->expires_after(timer_implementation_, expiry_time, ec); | |||
| #endif // defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| asio::detail::throw_error(ec, "after"); | |||
| start_timer(); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use expiry().) Get the stream buffer's expiry time relative | |||
| /// to now. | |||
| /** | |||
| * @return A relative time value representing the stream buffer's expiry time. | |||
| */ | |||
| duration expires_from_now() const | |||
| { | |||
| return traits_helper::subtract(expires_at(), traits_helper::now()); | |||
| } | |||
| /// (Deprecated: Use expires_after().) Set the stream buffer's expiry time | |||
| /// relative to now. | |||
| /** | |||
| * This function sets the expiry time associated with the stream. Stream | |||
| * operations performed after this time (where the operations cannot be | |||
| * completed using the internal buffers) will fail with the error | |||
| * asio::error::operation_aborted. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| */ | |||
| void expires_from_now(const duration& expiry_time) | |||
| { | |||
| construct_timer(); | |||
| asio::error_code ec; | |||
| timer_service_->expires_from_now(timer_implementation_, expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_from_now"); | |||
| start_timer(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| protected: | |||
| int_type underflow() | |||
| { | |||
| if (gptr() == egptr()) | |||
| { | |||
| if (timer_state_ == timer_has_expired) | |||
| { | |||
| ec_ = asio::error::operation_aborted; | |||
| return traits_type::eof(); | |||
| } | |||
| io_handler handler = { this }; | |||
| this->get_service().async_receive(this->get_implementation(), | |||
| asio::buffer(asio::buffer(get_buffer_) + putback_max), | |||
| 0, handler); | |||
| ec_ = asio::error::would_block; | |||
| this->get_service().get_io_context().restart(); | |||
| do this->get_service().get_io_context().run_one(); | |||
| while (ec_ == asio::error::would_block); | |||
| if (ec_) | |||
| return traits_type::eof(); | |||
| setg(&get_buffer_[0], &get_buffer_[0] + putback_max, | |||
| &get_buffer_[0] + putback_max + bytes_transferred_); | |||
| return traits_type::to_int_type(*gptr()); | |||
| } | |||
| else | |||
| { | |||
| return traits_type::eof(); | |||
| } | |||
| } | |||
| int_type overflow(int_type c) | |||
| { | |||
| if (unbuffered_) | |||
| { | |||
| if (traits_type::eq_int_type(c, traits_type::eof())) | |||
| { | |||
| // Nothing to do. | |||
| return traits_type::not_eof(c); | |||
| } | |||
| else | |||
| { | |||
| if (timer_state_ == timer_has_expired) | |||
| { | |||
| ec_ = asio::error::operation_aborted; | |||
| return traits_type::eof(); | |||
| } | |||
| // Send the single character immediately. | |||
| char_type ch = traits_type::to_char_type(c); | |||
| io_handler handler = { this }; | |||
| this->get_service().async_send(this->get_implementation(), | |||
| asio::buffer(&ch, sizeof(char_type)), 0, handler); | |||
| ec_ = asio::error::would_block; | |||
| this->get_service().get_io_context().restart(); | |||
| do this->get_service().get_io_context().run_one(); | |||
| while (ec_ == asio::error::would_block); | |||
| if (ec_) | |||
| return traits_type::eof(); | |||
| return c; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // Send all data in the output buffer. | |||
| asio::const_buffer buffer = | |||
| asio::buffer(pbase(), pptr() - pbase()); | |||
| while (buffer.size() > 0) | |||
| { | |||
| if (timer_state_ == timer_has_expired) | |||
| { | |||
| ec_ = asio::error::operation_aborted; | |||
| return traits_type::eof(); | |||
| } | |||
| io_handler handler = { this }; | |||
| this->get_service().async_send(this->get_implementation(), | |||
| asio::buffer(buffer), 0, handler); | |||
| ec_ = asio::error::would_block; | |||
| this->get_service().get_io_context().restart(); | |||
| do this->get_service().get_io_context().run_one(); | |||
| while (ec_ == asio::error::would_block); | |||
| if (ec_) | |||
| return traits_type::eof(); | |||
| buffer = buffer + bytes_transferred_; | |||
| } | |||
| setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size()); | |||
| // If the new character is eof then our work here is done. | |||
| if (traits_type::eq_int_type(c, traits_type::eof())) | |||
| return traits_type::not_eof(c); | |||
| // Add the new character to the output buffer. | |||
| *pptr() = traits_type::to_char_type(c); | |||
| pbump(1); | |||
| return c; | |||
| } | |||
| } | |||
| int sync() | |||
| { | |||
| return overflow(traits_type::eof()); | |||
| } | |||
| std::streambuf* setbuf(char_type* s, std::streamsize n) | |||
| { | |||
| if (pptr() == pbase() && s == 0 && n == 0) | |||
| { | |||
| unbuffered_ = true; | |||
| setp(0, 0); | |||
| return this; | |||
| } | |||
| return 0; | |||
| } | |||
| /// Get the last error associated with the stream buffer. | |||
| /** | |||
| * @return An \c error_code corresponding to the last error from the stream | |||
| * buffer. | |||
| */ | |||
| virtual const asio::error_code& error() const | |||
| { | |||
| return ec_; | |||
| } | |||
| private: | |||
| void init_buffers() | |||
| { | |||
| setg(&get_buffer_[0], | |||
| &get_buffer_[0] + putback_max, | |||
| &get_buffer_[0] + putback_max); | |||
| if (unbuffered_) | |||
| setp(0, 0); | |||
| else | |||
| setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size()); | |||
| } | |||
| template <typename EndpointSequence> | |||
| void connect_to_endpoints(const EndpointSequence& endpoints) | |||
| { | |||
| if (!ec_) | |||
| { | |||
| typename EndpointSequence::iterator i = endpoints.begin(); | |||
| typename EndpointSequence::iterator end = endpoints.end(); | |||
| ec_ = asio::error::host_not_found; | |||
| while (ec_ && i != end) | |||
| { | |||
| this->basic_socket<Protocol, StreamSocketService>::close(ec_); | |||
| if (timer_state_ == timer_has_expired) | |||
| { | |||
| ec_ = asio::error::operation_aborted; | |||
| return; | |||
| } | |||
| io_handler handler = { this }; | |||
| this->basic_socket<Protocol, StreamSocketService>::async_connect( | |||
| *i, handler); | |||
| ec_ = asio::error::would_block; | |||
| this->get_service().get_io_context().restart(); | |||
| do this->get_service().get_io_context().run_one(); | |||
| while (ec_ == asio::error::would_block); | |||
| ++i; | |||
| } | |||
| } | |||
| } | |||
| struct io_handler; | |||
| friend struct io_handler; | |||
| struct io_handler | |||
| { | |||
| basic_socket_streambuf* this_; | |||
| void operator()(const asio::error_code& ec, | |||
| std::size_t bytes_transferred = 0) | |||
| { | |||
| this_->ec_ = ec; | |||
| this_->bytes_transferred_ = bytes_transferred; | |||
| } | |||
| }; | |||
| struct timer_handler; | |||
| friend struct timer_handler; | |||
| struct timer_handler | |||
| { | |||
| basic_socket_streambuf* this_; | |||
| void operator()(const asio::error_code&) | |||
| { | |||
| time_point now = traits_helper::now(); | |||
| #if defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| time_point expiry_time = this_->timer_service_->expires_at( | |||
| this_->timer_implementation_); | |||
| #else // defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| time_point expiry_time = this_->timer_service_->expiry( | |||
| this_->timer_implementation_); | |||
| #endif // defined(ASIO_HAS_BOOST_DATE_TIME) | |||
| if (traits_helper::less_than(now, expiry_time)) | |||
| { | |||
| this_->timer_state_ = timer_is_pending; | |||
| this_->timer_service_->async_wait(this_->timer_implementation_, *this); | |||
| } | |||
| else | |||
| { | |||
| this_->timer_state_ = timer_has_expired; | |||
| asio::error_code ec; | |||
| this_->basic_socket<Protocol, StreamSocketService>::close(ec); | |||
| } | |||
| } | |||
| }; | |||
| void construct_timer() | |||
| { | |||
| if (timer_service_ == 0) | |||
| { | |||
| TimerService& timer_service = use_service<TimerService>( | |||
| detail::socket_streambuf_base::io_context_); | |||
| timer_service.construct(timer_implementation_); | |||
| timer_service_ = &timer_service; | |||
| } | |||
| } | |||
| void destroy_timer() | |||
| { | |||
| if (timer_service_) | |||
| timer_service_->destroy(timer_implementation_); | |||
| } | |||
| void start_timer() | |||
| { | |||
| if (timer_state_ != timer_is_pending) | |||
| { | |||
| timer_handler handler = { this }; | |||
| handler(asio::error_code()); | |||
| } | |||
| } | |||
| enum { putback_max = 8 }; | |||
| enum { buffer_size = 512 }; | |||
| asio::detail::array<char, buffer_size> get_buffer_; | |||
| asio::detail::array<char, buffer_size> put_buffer_; | |||
| bool unbuffered_; | |||
| asio::error_code ec_; | |||
| std::size_t bytes_transferred_; | |||
| TimerService* timer_service_; | |||
| typename TimerService::implementation_type timer_implementation_; | |||
| enum state { no_timer, timer_is_pending, timer_has_expired } timer_state_; | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #if !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| # undef ASIO_PRIVATE_CONNECT_DEF | |||
| #endif // !defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| #endif // !defined(ASIO_NO_IOSTREAM) | |||
| #endif // ASIO_BASIC_SOCKET_STREAMBUF_HPP | |||
| @@ -0,0 +1,848 @@ | |||
| // | |||
| // basic_stream_socket.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_STREAM_SOCKET_HPP | |||
| #define ASIO_BASIC_STREAM_SOCKET_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/async_result.hpp" | |||
| #include "asio/basic_socket.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/stream_socket_service.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides stream-oriented socket functionality. | |||
| /** | |||
| * The basic_stream_socket class template provides asynchronous and blocking | |||
| * stream-oriented socket functionality. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| * | |||
| * @par Concepts: | |||
| * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. | |||
| */ | |||
| template <typename Protocol, | |||
| typename StreamSocketService = stream_socket_service<Protocol> > | |||
| class basic_stream_socket | |||
| : public basic_socket<Protocol, StreamSocketService> | |||
| { | |||
| public: | |||
| /// The native representation of a socket. | |||
| typedef typename StreamSocketService::native_handle_type native_handle_type; | |||
| /// The protocol type. | |||
| typedef Protocol protocol_type; | |||
| /// The endpoint type. | |||
| typedef typename Protocol::endpoint endpoint_type; | |||
| /// Construct a basic_stream_socket without opening it. | |||
| /** | |||
| * This constructor creates a stream socket without opening it. The socket | |||
| * needs to be opened and then connected or accepted before data can be sent | |||
| * or received on it. | |||
| * | |||
| * @param io_context The io_context object that the stream socket will use to | |||
| * dispatch handlers for any asynchronous operations performed on the socket. | |||
| */ | |||
| explicit basic_stream_socket(asio::io_context& io_context) | |||
| : basic_socket<Protocol, StreamSocketService>(io_context) | |||
| { | |||
| } | |||
| /// Construct and open a basic_stream_socket. | |||
| /** | |||
| * This constructor creates and opens a stream socket. The socket needs to be | |||
| * connected or accepted before data can be sent or received on it. | |||
| * | |||
| * @param io_context The io_context object that the stream socket will use to | |||
| * dispatch handlers for any asynchronous operations performed on the socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_stream_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol) | |||
| : basic_socket<Protocol, StreamSocketService>(io_context, protocol) | |||
| { | |||
| } | |||
| /// Construct a basic_stream_socket, opening it and binding it to the given | |||
| /// local endpoint. | |||
| /** | |||
| * This constructor creates a stream socket and automatically opens it bound | |||
| * to the specified endpoint on the local machine. The protocol used is the | |||
| * protocol associated with the given endpoint. | |||
| * | |||
| * @param io_context The io_context object that the stream socket will use to | |||
| * dispatch handlers for any asynchronous operations performed on the socket. | |||
| * | |||
| * @param endpoint An endpoint on the local machine to which the stream | |||
| * socket will be bound. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_stream_socket(asio::io_context& io_context, | |||
| const endpoint_type& endpoint) | |||
| : basic_socket<Protocol, StreamSocketService>(io_context, endpoint) | |||
| { | |||
| } | |||
| /// Construct a basic_stream_socket on an existing native socket. | |||
| /** | |||
| * This constructor creates a stream socket object to hold an existing native | |||
| * socket. | |||
| * | |||
| * @param io_context The io_context object that the stream socket will use to | |||
| * dispatch handlers for any asynchronous operations performed on the socket. | |||
| * | |||
| * @param protocol An object specifying protocol parameters to be used. | |||
| * | |||
| * @param native_socket The new underlying socket implementation. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| basic_stream_socket(asio::io_context& io_context, | |||
| const protocol_type& protocol, const native_handle_type& native_socket) | |||
| : basic_socket<Protocol, StreamSocketService>( | |||
| io_context, protocol, native_socket) | |||
| { | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move-construct a basic_stream_socket from another. | |||
| /** | |||
| * This constructor moves a stream socket from one object to another. | |||
| * | |||
| * @param other The other basic_stream_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_stream_socket(io_context&) constructor. | |||
| */ | |||
| basic_stream_socket(basic_stream_socket&& other) | |||
| : basic_socket<Protocol, StreamSocketService>( | |||
| ASIO_MOVE_CAST(basic_stream_socket)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_stream_socket from another. | |||
| /** | |||
| * This assignment operator moves a stream socket from one object to another. | |||
| * | |||
| * @param other The other basic_stream_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_stream_socket(io_context&) constructor. | |||
| */ | |||
| basic_stream_socket& operator=(basic_stream_socket&& other) | |||
| { | |||
| basic_socket<Protocol, StreamSocketService>::operator=( | |||
| ASIO_MOVE_CAST(basic_stream_socket)(other)); | |||
| return *this; | |||
| } | |||
| /// Move-construct a basic_stream_socket from a socket of another protocol | |||
| /// type. | |||
| /** | |||
| * This constructor moves a stream socket from one object to another. | |||
| * | |||
| * @param other The other basic_stream_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_stream_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename StreamSocketService1> | |||
| basic_stream_socket( | |||
| basic_stream_socket<Protocol1, StreamSocketService1>&& other, | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value>::type* = 0) | |||
| : basic_socket<Protocol, StreamSocketService>( | |||
| ASIO_MOVE_CAST2(basic_stream_socket< | |||
| Protocol1, StreamSocketService1>)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_stream_socket from a socket of another protocol type. | |||
| /** | |||
| * This assignment operator moves a stream socket from one object to another. | |||
| * | |||
| * @param other The other basic_stream_socket object from which the move | |||
| * will occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_stream_socket(io_context&) constructor. | |||
| */ | |||
| template <typename Protocol1, typename StreamSocketService1> | |||
| typename enable_if<is_convertible<Protocol1, Protocol>::value, | |||
| basic_stream_socket>::type& operator=( | |||
| basic_stream_socket<Protocol1, StreamSocketService1>&& other) | |||
| { | |||
| basic_socket<Protocol, StreamSocketService>::operator=( | |||
| ASIO_MOVE_CAST2(basic_stream_socket< | |||
| Protocol1, StreamSocketService1>)(other)); | |||
| return *this; | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Send some data on the socket. | |||
| /** | |||
| * This function is used to send data on the stream socket. The function | |||
| * call will block until one or more bytes of the data has been sent | |||
| * successfully, or an until error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The send operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref write function if you need to ensure that all data | |||
| * is written before the blocking operation completes. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.send(asio::buffer(data, size)); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "send"); | |||
| return s; | |||
| } | |||
| /// Send some data on the socket. | |||
| /** | |||
| * This function is used to send data on the stream socket. The function | |||
| * call will block until one or more bytes of the data has been sent | |||
| * successfully, or an until error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @returns The number of bytes sent. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note The send operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref write function if you need to ensure that all data | |||
| * is written before the blocking operation completes. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.send(asio::buffer(data, size), 0); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| asio::detail::throw_error(ec, "send"); | |||
| return s; | |||
| } | |||
| /// Send some data on the socket. | |||
| /** | |||
| * This function is used to send data on the stream socket. The function | |||
| * call will block until one or more bytes of the data has been sent | |||
| * successfully, or an until error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes sent. Returns 0 if an error occurred. | |||
| * | |||
| * @note The send operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref write function if you need to ensure that all data | |||
| * is written before the blocking operation completes. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().send( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| } | |||
| /// Start an asynchronous send. | |||
| /** | |||
| * This function is used to asynchronously send data on the stream socket. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. Although | |||
| * the buffers object may be copied as necessary, ownership of the underlying | |||
| * memory blocks is retained by the caller, which must guarantee that they | |||
| * remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The send operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref async_write function if you need to ensure that all | |||
| * data is written before the asynchronous operation completes. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.async_send(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send( | |||
| this->get_implementation(), buffers, 0, | |||
| ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous send. | |||
| /** | |||
| * This function is used to asynchronously send data on the stream socket. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be sent on the socket. Although | |||
| * the buffers object may be copied as necessary, ownership of the underlying | |||
| * memory blocks is retained by the caller, which must guarantee that they | |||
| * remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the send call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the send operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes sent. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The send operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref async_write function if you need to ensure that all | |||
| * data is written before the asynchronous operation completes. | |||
| * | |||
| * @par Example | |||
| * To send a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.async_send(asio::buffer(data, size), 0, handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on sending multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_send(const ConstBufferSequence& buffers, | |||
| socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send( | |||
| this->get_implementation(), buffers, flags, | |||
| ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Receive some data on the socket. | |||
| /** | |||
| * This function is used to receive data on the stream socket. The function | |||
| * call will block until one or more bytes of data has been received | |||
| * successfully, or until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @note The receive operation may not receive all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that the | |||
| * requested amount of data is read before the blocking operation completes. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.receive(asio::buffer(data, size)); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on the socket. | |||
| /** | |||
| * This function is used to receive data on the stream socket. The function | |||
| * call will block until one or more bytes of data has been received | |||
| * successfully, or until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @returns The number of bytes received. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @note The receive operation may not receive all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that the | |||
| * requested amount of data is read before the blocking operation completes. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.receive(asio::buffer(data, size), 0); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| asio::detail::throw_error(ec, "receive"); | |||
| return s; | |||
| } | |||
| /// Receive some data on a connected socket. | |||
| /** | |||
| * This function is used to receive data on the stream socket. The function | |||
| * call will block until one or more bytes of data has been received | |||
| * successfully, or until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes received. Returns 0 if an error occurred. | |||
| * | |||
| * @note The receive operation may not receive all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that the | |||
| * requested amount of data is read before the blocking operation completes. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags, asio::error_code& ec) | |||
| { | |||
| return this->get_service().receive( | |||
| this->get_implementation(), buffers, flags, ec); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive data from the stream | |||
| * socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The receive operation may not receive all of the requested number of | |||
| * bytes. Consider using the @ref async_read function if you need to ensure | |||
| * that the requested amount of data is received before the asynchronous | |||
| * operation completes. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.async_receive(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive(this->get_implementation(), | |||
| buffers, 0, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Start an asynchronous receive. | |||
| /** | |||
| * This function is used to asynchronously receive data from the stream | |||
| * socket. The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be received. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param flags Flags specifying how the receive call is to be made. | |||
| * | |||
| * @param handler The handler to be called when the receive operation | |||
| * completes. Copies will be made of the handler as required. The function | |||
| * signature of the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes received. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The receive operation may not receive all of the requested number of | |||
| * bytes. Consider using the @ref async_read function if you need to ensure | |||
| * that the requested amount of data is received before the asynchronous | |||
| * operation completes. | |||
| * | |||
| * @par Example | |||
| * To receive into a single data buffer use the @ref buffer function as | |||
| * follows: | |||
| * @code | |||
| * socket.async_receive(asio::buffer(data, size), 0, handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on receiving into | |||
| * multiple buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_receive(const MutableBufferSequence& buffers, | |||
| socket_base::message_flags flags, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive(this->get_implementation(), | |||
| buffers, flags, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Write some data to the socket. | |||
| /** | |||
| * This function is used to write data to the stream socket. The function call | |||
| * will block until one or more bytes of the data has been written | |||
| * successfully, or until an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be written to the socket. | |||
| * | |||
| * @returns The number of bytes written. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @note The write_some operation may not transmit all of the data to the | |||
| * peer. Consider using the @ref write function if you need to ensure that | |||
| * all data is written before the blocking operation completes. | |||
| * | |||
| * @par Example | |||
| * To write a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.write_some(asio::buffer(data, size)); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on writing multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().send( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "write_some"); | |||
| return s; | |||
| } | |||
| /// Write some data to the socket. | |||
| /** | |||
| * This function is used to write data to the stream socket. The function call | |||
| * will block until one or more bytes of the data has been written | |||
| * successfully, or until an error occurs. | |||
| * | |||
| * @param buffers One or more data buffers to be written to the socket. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes written. Returns 0 if an error occurred. | |||
| * | |||
| * @note The write_some operation may not transmit all of the data to the | |||
| * peer. Consider using the @ref write function if you need to ensure that | |||
| * all data is written before the blocking operation completes. | |||
| */ | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().send(this->get_implementation(), buffers, 0, ec); | |||
| } | |||
| /// Start an asynchronous write. | |||
| /** | |||
| * This function is used to asynchronously write data to the stream socket. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more data buffers to be written to the socket. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the write operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes written. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The write operation may not transmit all of the data to the peer. | |||
| * Consider using the @ref async_write function if you need to ensure that all | |||
| * data is written before the asynchronous operation completes. | |||
| * | |||
| * @par Example | |||
| * To write a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.async_write_some(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on writing multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_write_some(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WriteHandler. | |||
| ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; | |||
| return this->get_service().async_send(this->get_implementation(), | |||
| buffers, 0, ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Read some data from the socket. | |||
| /** | |||
| * This function is used to read data from the stream socket. The function | |||
| * call will block until one or more bytes of data has been read successfully, | |||
| * or until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be read. | |||
| * | |||
| * @returns The number of bytes read. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. An error code of | |||
| * asio::error::eof indicates that the connection was closed by the | |||
| * peer. | |||
| * | |||
| * @note The read_some operation may not read all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that | |||
| * the requested amount of data is read before the blocking operation | |||
| * completes. | |||
| * | |||
| * @par Example | |||
| * To read into a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.read_some(asio::buffer(data, size)); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on reading into multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().receive( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| asio::detail::throw_error(ec, "read_some"); | |||
| return s; | |||
| } | |||
| /// Read some data from the socket. | |||
| /** | |||
| * This function is used to read data from the stream socket. The function | |||
| * call will block until one or more bytes of data has been read successfully, | |||
| * or until an error occurs. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be read. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @returns The number of bytes read. Returns 0 if an error occurred. | |||
| * | |||
| * @note The read_some operation may not read all of the requested number of | |||
| * bytes. Consider using the @ref read function if you need to ensure that | |||
| * the requested amount of data is read before the blocking operation | |||
| * completes. | |||
| */ | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().receive( | |||
| this->get_implementation(), buffers, 0, ec); | |||
| } | |||
| /// Start an asynchronous read. | |||
| /** | |||
| * This function is used to asynchronously read data from the stream socket. | |||
| * The function call always returns immediately. | |||
| * | |||
| * @param buffers One or more buffers into which the data will be read. | |||
| * Although the buffers object may be copied as necessary, ownership of the | |||
| * underlying memory blocks is retained by the caller, which must guarantee | |||
| * that they remain valid until the handler is called. | |||
| * | |||
| * @param handler The handler to be called when the read operation completes. | |||
| * Copies will be made of the handler as required. The function signature of | |||
| * the handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error, // Result of operation. | |||
| * std::size_t bytes_transferred // Number of bytes read. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| * | |||
| * @note The read operation may not read all of the requested number of bytes. | |||
| * Consider using the @ref async_read function if you need to ensure that the | |||
| * requested amount of data is read before the asynchronous operation | |||
| * completes. | |||
| * | |||
| * @par Example | |||
| * To read into a single data buffer use the @ref buffer function as follows: | |||
| * @code | |||
| * socket.async_read_some(asio::buffer(data, size), handler); | |||
| * @endcode | |||
| * See the @ref buffer documentation for information on reading into multiple | |||
| * buffers in one go, and how to use it with arrays, boost::array or | |||
| * std::vector. | |||
| */ | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_read_some(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a ReadHandler. | |||
| ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; | |||
| return this->get_service().async_receive(this->get_implementation(), | |||
| buffers, 0, ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BASIC_STREAM_SOCKET_HPP | |||
| @@ -0,0 +1,453 @@ | |||
| // | |||
| // basic_streambuf.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_STREAMBUF_HPP | |||
| #define ASIO_BASIC_STREAMBUF_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #if !defined(ASIO_NO_IOSTREAM) | |||
| #include <algorithm> | |||
| #include <cstring> | |||
| #include <stdexcept> | |||
| #include <streambuf> | |||
| #include <vector> | |||
| #include "asio/basic_streambuf_fwd.hpp" | |||
| #include "asio/buffer.hpp" | |||
| #include "asio/detail/limits.hpp" | |||
| #include "asio/detail/noncopyable.hpp" | |||
| #include "asio/detail/throw_exception.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Automatically resizable buffer class based on std::streambuf. | |||
| /** | |||
| * The @c basic_streambuf class is derived from @c std::streambuf to associate | |||
| * the streambuf's input and output sequences with one or more character | |||
| * arrays. These character arrays are internal to the @c basic_streambuf | |||
| * object, but direct access to the array elements is provided to permit them | |||
| * to be used efficiently with I/O operations. Characters written to the output | |||
| * sequence of a @c basic_streambuf object are appended to the input sequence | |||
| * of the same object. | |||
| * | |||
| * The @c basic_streambuf class's public interface is intended to permit the | |||
| * following implementation strategies: | |||
| * | |||
| * @li A single contiguous character array, which is reallocated as necessary | |||
| * to accommodate changes in the size of the character sequence. This is the | |||
| * implementation approach currently used in Asio. | |||
| * | |||
| * @li A sequence of one or more character arrays, where each array is of the | |||
| * same size. Additional character array objects are appended to the sequence | |||
| * to accommodate changes in the size of the character sequence. | |||
| * | |||
| * @li A sequence of one or more character arrays of varying sizes. Additional | |||
| * character array objects are appended to the sequence to accommodate changes | |||
| * in the size of the character sequence. | |||
| * | |||
| * The constructor for basic_streambuf accepts a @c size_t argument specifying | |||
| * the maximum of the sum of the sizes of the input sequence and output | |||
| * sequence. During the lifetime of the @c basic_streambuf object, the following | |||
| * invariant holds: | |||
| * @code size() <= max_size()@endcode | |||
| * Any member function that would, if successful, cause the invariant to be | |||
| * violated shall throw an exception of class @c std::length_error. | |||
| * | |||
| * The constructor for @c basic_streambuf takes an Allocator argument. A copy | |||
| * of this argument is used for any memory allocation performed, by the | |||
| * constructor and by all member functions, during the lifetime of each @c | |||
| * basic_streambuf object. | |||
| * | |||
| * @par Examples | |||
| * Writing directly from an streambuf to a socket: | |||
| * @code | |||
| * asio::streambuf b; | |||
| * std::ostream os(&b); | |||
| * os << "Hello, World!\n"; | |||
| * | |||
| * // try sending some data in input sequence | |||
| * size_t n = sock.send(b.data()); | |||
| * | |||
| * b.consume(n); // sent data is removed from input sequence | |||
| * @endcode | |||
| * | |||
| * Reading from a socket directly into a streambuf: | |||
| * @code | |||
| * asio::streambuf b; | |||
| * | |||
| * // reserve 512 bytes in output sequence | |||
| * asio::streambuf::mutable_buffers_type bufs = b.prepare(512); | |||
| * | |||
| * size_t n = sock.receive(bufs); | |||
| * | |||
| * // received data is "committed" from output sequence to input sequence | |||
| * b.commit(n); | |||
| * | |||
| * std::istream is(&b); | |||
| * std::string s; | |||
| * is >> s; | |||
| * @endcode | |||
| */ | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| template <typename Allocator = std::allocator<char> > | |||
| #else | |||
| template <typename Allocator> | |||
| #endif | |||
| class basic_streambuf | |||
| : public std::streambuf, | |||
| private noncopyable | |||
| { | |||
| public: | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// The type used to represent the input sequence as a list of buffers. | |||
| typedef implementation_defined const_buffers_type; | |||
| /// The type used to represent the output sequence as a list of buffers. | |||
| typedef implementation_defined mutable_buffers_type; | |||
| #else | |||
| typedef asio::const_buffers_1 const_buffers_type; | |||
| typedef asio::mutable_buffers_1 mutable_buffers_type; | |||
| #endif | |||
| /// Construct a basic_streambuf object. | |||
| /** | |||
| * Constructs a streambuf with the specified maximum size. The initial size | |||
| * of the streambuf's input sequence is 0. | |||
| */ | |||
| explicit basic_streambuf( | |||
| std::size_t maximum_size = (std::numeric_limits<std::size_t>::max)(), | |||
| const Allocator& allocator = Allocator()) | |||
| : max_size_(maximum_size), | |||
| buffer_(allocator) | |||
| { | |||
| std::size_t pend = (std::min<std::size_t>)(max_size_, buffer_delta); | |||
| buffer_.resize((std::max<std::size_t>)(pend, 1)); | |||
| setg(&buffer_[0], &buffer_[0], &buffer_[0]); | |||
| setp(&buffer_[0], &buffer_[0] + pend); | |||
| } | |||
| /// Get the size of the input sequence. | |||
| /** | |||
| * @returns The size of the input sequence. The value is equal to that | |||
| * calculated for @c s in the following code: | |||
| * @code | |||
| * size_t s = 0; | |||
| * const_buffers_type bufs = data(); | |||
| * const_buffers_type::const_iterator i = bufs.begin(); | |||
| * while (i != bufs.end()) | |||
| * { | |||
| * const_buffer buf(*i++); | |||
| * s += buf.size(); | |||
| * } | |||
| * @endcode | |||
| */ | |||
| std::size_t size() const ASIO_NOEXCEPT | |||
| { | |||
| return pptr() - gptr(); | |||
| } | |||
| /// Get the maximum size of the basic_streambuf. | |||
| /** | |||
| * @returns The allowed maximum of the sum of the sizes of the input sequence | |||
| * and output sequence. | |||
| */ | |||
| std::size_t max_size() const ASIO_NOEXCEPT | |||
| { | |||
| return max_size_; | |||
| } | |||
| /// Get the current capacity of the basic_streambuf. | |||
| /** | |||
| * @returns The current total capacity of the streambuf, i.e. for both the | |||
| * input sequence and output sequence. | |||
| */ | |||
| std::size_t capacity() const ASIO_NOEXCEPT | |||
| { | |||
| return buffer_.capacity(); | |||
| } | |||
| /// Get a list of buffers that represents the input sequence. | |||
| /** | |||
| * @returns An object of type @c const_buffers_type that satisfies | |||
| * ConstBufferSequence requirements, representing all character arrays in the | |||
| * input sequence. | |||
| * | |||
| * @note The returned object is invalidated by any @c basic_streambuf member | |||
| * function that modifies the input sequence or output sequence. | |||
| */ | |||
| const_buffers_type data() const ASIO_NOEXCEPT | |||
| { | |||
| return asio::buffer(asio::const_buffer(gptr(), | |||
| (pptr() - gptr()) * sizeof(char_type))); | |||
| } | |||
| /// Get a list of buffers that represents the output sequence, with the given | |||
| /// size. | |||
| /** | |||
| * Ensures that the output sequence can accommodate @c n characters, | |||
| * reallocating character array objects as necessary. | |||
| * | |||
| * @returns An object of type @c mutable_buffers_type that satisfies | |||
| * MutableBufferSequence requirements, representing character array objects | |||
| * at the start of the output sequence such that the sum of the buffer sizes | |||
| * is @c n. | |||
| * | |||
| * @throws std::length_error If <tt>size() + n > max_size()</tt>. | |||
| * | |||
| * @note The returned object is invalidated by any @c basic_streambuf member | |||
| * function that modifies the input sequence or output sequence. | |||
| */ | |||
| mutable_buffers_type prepare(std::size_t n) | |||
| { | |||
| reserve(n); | |||
| return asio::buffer(asio::mutable_buffer( | |||
| pptr(), n * sizeof(char_type))); | |||
| } | |||
| /// Move characters from the output sequence to the input sequence. | |||
| /** | |||
| * Appends @c n characters from the start of the output sequence to the input | |||
| * sequence. The beginning of the output sequence is advanced by @c n | |||
| * characters. | |||
| * | |||
| * Requires a preceding call <tt>prepare(x)</tt> where <tt>x >= n</tt>, and | |||
| * no intervening operations that modify the input or output sequence. | |||
| * | |||
| * @note If @c n is greater than the size of the output sequence, the entire | |||
| * output sequence is moved to the input sequence and no error is issued. | |||
| */ | |||
| void commit(std::size_t n) | |||
| { | |||
| if (pptr() + n > epptr()) | |||
| n = epptr() - pptr(); | |||
| pbump(static_cast<int>(n)); | |||
| setg(eback(), gptr(), pptr()); | |||
| } | |||
| /// Remove characters from the input sequence. | |||
| /** | |||
| * Removes @c n characters from the beginning of the input sequence. | |||
| * | |||
| * @note If @c n is greater than the size of the input sequence, the entire | |||
| * input sequence is consumed and no error is issued. | |||
| */ | |||
| void consume(std::size_t n) | |||
| { | |||
| if (egptr() < pptr()) | |||
| setg(&buffer_[0], gptr(), pptr()); | |||
| if (gptr() + n > pptr()) | |||
| n = pptr() - gptr(); | |||
| gbump(static_cast<int>(n)); | |||
| } | |||
| protected: | |||
| enum { buffer_delta = 128 }; | |||
| /// Override std::streambuf behaviour. | |||
| /** | |||
| * Behaves according to the specification of @c std::streambuf::underflow(). | |||
| */ | |||
| int_type underflow() | |||
| { | |||
| if (gptr() < pptr()) | |||
| { | |||
| setg(&buffer_[0], gptr(), pptr()); | |||
| return traits_type::to_int_type(*gptr()); | |||
| } | |||
| else | |||
| { | |||
| return traits_type::eof(); | |||
| } | |||
| } | |||
| /// Override std::streambuf behaviour. | |||
| /** | |||
| * Behaves according to the specification of @c std::streambuf::overflow(), | |||
| * with the specialisation that @c std::length_error is thrown if appending | |||
| * the character to the input sequence would require the condition | |||
| * <tt>size() > max_size()</tt> to be true. | |||
| */ | |||
| int_type overflow(int_type c) | |||
| { | |||
| if (!traits_type::eq_int_type(c, traits_type::eof())) | |||
| { | |||
| if (pptr() == epptr()) | |||
| { | |||
| std::size_t buffer_size = pptr() - gptr(); | |||
| if (buffer_size < max_size_ && max_size_ - buffer_size < buffer_delta) | |||
| { | |||
| reserve(max_size_ - buffer_size); | |||
| } | |||
| else | |||
| { | |||
| reserve(buffer_delta); | |||
| } | |||
| } | |||
| *pptr() = traits_type::to_char_type(c); | |||
| pbump(1); | |||
| return c; | |||
| } | |||
| return traits_type::not_eof(c); | |||
| } | |||
| void reserve(std::size_t n) | |||
| { | |||
| // Get current stream positions as offsets. | |||
| std::size_t gnext = gptr() - &buffer_[0]; | |||
| std::size_t pnext = pptr() - &buffer_[0]; | |||
| std::size_t pend = epptr() - &buffer_[0]; | |||
| // Check if there is already enough space in the put area. | |||
| if (n <= pend - pnext) | |||
| { | |||
| return; | |||
| } | |||
| // Shift existing contents of get area to start of buffer. | |||
| if (gnext > 0) | |||
| { | |||
| pnext -= gnext; | |||
| std::memmove(&buffer_[0], &buffer_[0] + gnext, pnext); | |||
| } | |||
| // Ensure buffer is large enough to hold at least the specified size. | |||
| if (n > pend - pnext) | |||
| { | |||
| if (n <= max_size_ && pnext <= max_size_ - n) | |||
| { | |||
| pend = pnext + n; | |||
| buffer_.resize((std::max<std::size_t>)(pend, 1)); | |||
| } | |||
| else | |||
| { | |||
| std::length_error ex("asio::streambuf too long"); | |||
| asio::detail::throw_exception(ex); | |||
| } | |||
| } | |||
| // Update stream positions. | |||
| setg(&buffer_[0], &buffer_[0], &buffer_[0] + pnext); | |||
| setp(&buffer_[0] + pnext, &buffer_[0] + pend); | |||
| } | |||
| private: | |||
| std::size_t max_size_; | |||
| std::vector<char_type, Allocator> buffer_; | |||
| // Helper function to get the preferred size for reading data. | |||
| friend std::size_t read_size_helper( | |||
| basic_streambuf& sb, std::size_t max_size) | |||
| { | |||
| return std::min<std::size_t>( | |||
| std::max<std::size_t>(512, sb.buffer_.capacity() - sb.size()), | |||
| std::min<std::size_t>(max_size, sb.max_size() - sb.size())); | |||
| } | |||
| }; | |||
| /// Adapts basic_streambuf to the dynamic buffer sequence type requirements. | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| template <typename Allocator = std::allocator<char> > | |||
| #else | |||
| template <typename Allocator> | |||
| #endif | |||
| class basic_streambuf_ref | |||
| { | |||
| public: | |||
| /// The type used to represent the input sequence as a list of buffers. | |||
| typedef typename basic_streambuf<Allocator>::const_buffers_type | |||
| const_buffers_type; | |||
| /// The type used to represent the output sequence as a list of buffers. | |||
| typedef typename basic_streambuf<Allocator>::mutable_buffers_type | |||
| mutable_buffers_type; | |||
| /// Construct a basic_streambuf_ref for the given basic_streambuf object. | |||
| explicit basic_streambuf_ref(basic_streambuf<Allocator>& sb) | |||
| : sb_(sb) | |||
| { | |||
| } | |||
| /// Copy construct a basic_streambuf_ref. | |||
| basic_streambuf_ref(const basic_streambuf_ref& other) ASIO_NOEXCEPT | |||
| : sb_(other.sb_) | |||
| { | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move construct a basic_streambuf_ref. | |||
| basic_streambuf_ref(basic_streambuf_ref&& other) ASIO_NOEXCEPT | |||
| : sb_(other.sb_) | |||
| { | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Get the size of the input sequence. | |||
| std::size_t size() const ASIO_NOEXCEPT | |||
| { | |||
| return sb_.size(); | |||
| } | |||
| /// Get the maximum size of the dynamic buffer. | |||
| std::size_t max_size() const ASIO_NOEXCEPT | |||
| { | |||
| return sb_.max_size(); | |||
| } | |||
| /// Get the current capacity of the dynamic buffer. | |||
| std::size_t capacity() const ASIO_NOEXCEPT | |||
| { | |||
| return sb_.capacity(); | |||
| } | |||
| /// Get a list of buffers that represents the input sequence. | |||
| const_buffers_type data() const ASIO_NOEXCEPT | |||
| { | |||
| return sb_.data(); | |||
| } | |||
| /// Get a list of buffers that represents the output sequence, with the given | |||
| /// size. | |||
| mutable_buffers_type prepare(std::size_t n) | |||
| { | |||
| return sb_.prepare(n); | |||
| } | |||
| /// Move bytes from the output sequence to the input sequence. | |||
| void commit(std::size_t n) | |||
| { | |||
| return sb_.commit(n); | |||
| } | |||
| /// Remove characters from the input sequence. | |||
| void consume(std::size_t n) | |||
| { | |||
| return sb_.consume(n); | |||
| } | |||
| private: | |||
| basic_streambuf<Allocator>& sb_; | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // !defined(ASIO_NO_IOSTREAM) | |||
| #endif // ASIO_BASIC_STREAMBUF_HPP | |||
| @@ -0,0 +1,36 @@ | |||
| // | |||
| // basic_streambuf_fwd.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_STREAMBUF_FWD_HPP | |||
| #define ASIO_BASIC_STREAMBUF_FWD_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #if !defined(ASIO_NO_IOSTREAM) | |||
| #include <memory> | |||
| namespace asio { | |||
| template <typename Allocator = std::allocator<char> > | |||
| class basic_streambuf; | |||
| template <typename Allocator = std::allocator<char> > | |||
| class basic_streambuf_ref; | |||
| } // namespace asio | |||
| #endif // !defined(ASIO_NO_IOSTREAM) | |||
| #endif // ASIO_BASIC_STREAMBUF_FWD_HPP | |||
| @@ -0,0 +1,634 @@ | |||
| // | |||
| // basic_waitable_timer.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BASIC_WAITABLE_TIMER_HPP | |||
| #define ASIO_BASIC_WAITABLE_TIMER_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/basic_io_object.hpp" | |||
| #include "asio/detail/handler_type_requirements.hpp" | |||
| #include "asio/detail/throw_error.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/wait_traits.hpp" | |||
| #include "asio/waitable_timer_service.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Provides waitable timer functionality. | |||
| /** | |||
| * The basic_waitable_timer class template provides the ability to perform a | |||
| * blocking or asynchronous wait for a timer to expire. | |||
| * | |||
| * A waitable timer is always in one of two states: "expired" or "not expired". | |||
| * If the wait() or async_wait() function is called on an expired timer, the | |||
| * wait operation will complete immediately. | |||
| * | |||
| * Most applications will use one of the asio::steady_timer, | |||
| * asio::system_timer or asio::high_resolution_timer typedefs. | |||
| * | |||
| * @note This waitable timer functionality is for use with the C++11 standard | |||
| * library's @c <chrono> facility, or with the Boost.Chrono library. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| * | |||
| * @par Examples | |||
| * Performing a blocking wait (C++11): | |||
| * @code | |||
| * // Construct a timer without setting an expiry time. | |||
| * asio::steady_timer timer(io_context); | |||
| * | |||
| * // Set an expiry time relative to now. | |||
| * timer.expires_after(std::chrono::seconds(5)); | |||
| * | |||
| * // Wait for the timer to expire. | |||
| * timer.wait(); | |||
| * @endcode | |||
| * | |||
| * @par | |||
| * Performing an asynchronous wait (C++11): | |||
| * @code | |||
| * void handler(const asio::error_code& error) | |||
| * { | |||
| * if (!error) | |||
| * { | |||
| * // Timer expired. | |||
| * } | |||
| * } | |||
| * | |||
| * ... | |||
| * | |||
| * // Construct a timer with an absolute expiry time. | |||
| * asio::steady_timer timer(io_context, | |||
| * std::chrono::steady_clock::now() + std::chrono::seconds(60)); | |||
| * | |||
| * // Start an asynchronous wait. | |||
| * timer.async_wait(handler); | |||
| * @endcode | |||
| * | |||
| * @par Changing an active waitable timer's expiry time | |||
| * | |||
| * Changing the expiry time of a timer while there are pending asynchronous | |||
| * waits causes those wait operations to be cancelled. To ensure that the action | |||
| * associated with the timer is performed only once, use something like this: | |||
| * used: | |||
| * | |||
| * @code | |||
| * void on_some_event() | |||
| * { | |||
| * if (my_timer.expires_after(seconds(5)) > 0) | |||
| * { | |||
| * // We managed to cancel the timer. Start new asynchronous wait. | |||
| * my_timer.async_wait(on_timeout); | |||
| * } | |||
| * else | |||
| * { | |||
| * // Too late, timer has already expired! | |||
| * } | |||
| * } | |||
| * | |||
| * void on_timeout(const asio::error_code& e) | |||
| * { | |||
| * if (e != asio::error::operation_aborted) | |||
| * { | |||
| * // Timer was not cancelled, take necessary action. | |||
| * } | |||
| * } | |||
| * @endcode | |||
| * | |||
| * @li The asio::basic_waitable_timer::expires_after() function | |||
| * cancels any pending asynchronous waits, and returns the number of | |||
| * asynchronous waits that were cancelled. If it returns 0 then you were too | |||
| * late and the wait handler has already been executed, or will soon be | |||
| * executed. If it returns 1 then the wait handler was successfully cancelled. | |||
| * | |||
| * @li If a wait handler is cancelled, the asio::error_code passed to | |||
| * it contains the value asio::error::operation_aborted. | |||
| */ | |||
| template <typename Clock, | |||
| typename WaitTraits = asio::wait_traits<Clock>, | |||
| typename WaitableTimerService = waitable_timer_service<Clock, WaitTraits> > | |||
| class basic_waitable_timer | |||
| : public basic_io_object<WaitableTimerService> | |||
| { | |||
| public: | |||
| /// The clock type. | |||
| typedef Clock clock_type; | |||
| /// The duration type of the clock. | |||
| typedef typename clock_type::duration duration; | |||
| /// The time point type of the clock. | |||
| typedef typename clock_type::time_point time_point; | |||
| /// The wait traits type. | |||
| typedef WaitTraits traits_type; | |||
| /// Constructor. | |||
| /** | |||
| * This constructor creates a timer without setting an expiry time. The | |||
| * expires_at() or expires_after() functions must be called to set an expiry | |||
| * time before the timer can be waited on. | |||
| * | |||
| * @param io_context The io_context object that the timer will use to dispatch | |||
| * handlers for any asynchronous operations performed on the timer. | |||
| */ | |||
| explicit basic_waitable_timer(asio::io_context& io_context) | |||
| : basic_io_object<WaitableTimerService>(io_context) | |||
| { | |||
| } | |||
| /// Constructor to set a particular expiry time as an absolute time. | |||
| /** | |||
| * This constructor creates a timer and sets the expiry time. | |||
| * | |||
| * @param io_context The io_context object that the timer will use to dispatch | |||
| * handlers for any asynchronous operations performed on the timer. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer, expressed | |||
| * as an absolute time. | |||
| */ | |||
| basic_waitable_timer(asio::io_context& io_context, | |||
| const time_point& expiry_time) | |||
| : basic_io_object<WaitableTimerService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().expires_at(this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_at"); | |||
| } | |||
| /// Constructor to set a particular expiry time relative to now. | |||
| /** | |||
| * This constructor creates a timer and sets the expiry time. | |||
| * | |||
| * @param io_context The io_context object that the timer will use to dispatch | |||
| * handlers for any asynchronous operations performed on the timer. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer, relative to | |||
| * now. | |||
| */ | |||
| basic_waitable_timer(asio::io_context& io_context, | |||
| const duration& expiry_time) | |||
| : basic_io_object<WaitableTimerService>(io_context) | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().expires_after( | |||
| this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_after"); | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move-construct a basic_waitable_timer from another. | |||
| /** | |||
| * This constructor moves a timer from one object to another. | |||
| * | |||
| * @param other The other basic_waitable_timer object from which the move will | |||
| * occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_waitable_timer(io_context&) constructor. | |||
| */ | |||
| basic_waitable_timer(basic_waitable_timer&& other) | |||
| : basic_io_object<WaitableTimerService>( | |||
| ASIO_MOVE_CAST(basic_waitable_timer)(other)) | |||
| { | |||
| } | |||
| /// Move-assign a basic_waitable_timer from another. | |||
| /** | |||
| * This assignment operator moves a timer from one object to another. | |||
| * | |||
| * @param other The other basic_waitable_timer object from which the move will | |||
| * occur. | |||
| * | |||
| * @note Following the move, the moved-from object is in the same state as if | |||
| * constructed using the @c basic_waitable_timer(io_context&) constructor. | |||
| */ | |||
| basic_waitable_timer& operator=(basic_waitable_timer&& other) | |||
| { | |||
| basic_io_object<WaitableTimerService>::operator=( | |||
| ASIO_MOVE_CAST(basic_waitable_timer)(other)); | |||
| return *this; | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Cancel any asynchronous operations that are waiting on the timer. | |||
| /** | |||
| * This function forces the completion of any pending asynchronous wait | |||
| * operations against the timer. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when cancel() is called, then the | |||
| * handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel() | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().cancel(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "cancel"); | |||
| return s; | |||
| } | |||
| /// Cancel any asynchronous operations that are waiting on the timer. | |||
| /** | |||
| * This function forces the completion of any pending asynchronous wait | |||
| * operations against the timer. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @note If the timer has already expired when cancel() is called, then the | |||
| * handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel(asio::error_code& ec) | |||
| { | |||
| return this->get_service().cancel(this->get_implementation(), ec); | |||
| } | |||
| /// Cancels one asynchronous operation that is waiting on the timer. | |||
| /** | |||
| * This function forces the completion of one pending asynchronous wait | |||
| * operation against the timer. Handlers are cancelled in FIFO order. The | |||
| * handler for the cancelled operation will be invoked with the | |||
| * asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. That is, | |||
| * either 0 or 1. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when cancel_one() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel_one() | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().cancel_one( | |||
| this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "cancel_one"); | |||
| return s; | |||
| } | |||
| /// Cancels one asynchronous operation that is waiting on the timer. | |||
| /** | |||
| * This function forces the completion of one pending asynchronous wait | |||
| * operation against the timer. Handlers are cancelled in FIFO order. The | |||
| * handler for the cancelled operation will be invoked with the | |||
| * asio::error::operation_aborted error code. | |||
| * | |||
| * Cancelling the timer does not change the expiry time. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. That is, | |||
| * either 0 or 1. | |||
| * | |||
| * @note If the timer has already expired when cancel_one() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t cancel_one(asio::error_code& ec) | |||
| { | |||
| return this->get_service().cancel_one(this->get_implementation(), ec); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use expiry().) Get the timer's expiry time as an absolute | |||
| /// time. | |||
| /** | |||
| * This function may be used to obtain the timer's current expiry time. | |||
| * Whether the timer has expired or not does not affect this value. | |||
| */ | |||
| time_point expires_at() const | |||
| { | |||
| return this->get_service().expires_at(this->get_implementation()); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// Get the timer's expiry time as an absolute time. | |||
| /** | |||
| * This function may be used to obtain the timer's current expiry time. | |||
| * Whether the timer has expired or not does not affect this value. | |||
| */ | |||
| time_point expiry() const | |||
| { | |||
| return this->get_service().expiry(this->get_implementation()); | |||
| } | |||
| /// Set the timer's expiry time as an absolute time. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when expires_at() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_at(const time_point& expiry_time) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().expires_at( | |||
| this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_at"); | |||
| return s; | |||
| } | |||
| /// Set the timer's expiry time as an absolute time. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @note If the timer has already expired when expires_at() is called, then | |||
| * the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_at(const time_point& expiry_time, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().expires_at( | |||
| this->get_implementation(), expiry_time, ec); | |||
| } | |||
| /// Set the timer's expiry time relative to now. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when expires_after() is called, | |||
| * then the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_after(const duration& expiry_time) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().expires_after( | |||
| this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_after"); | |||
| return s; | |||
| } | |||
| /// Set the timer's expiry time relative to now. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @note If the timer has already expired when expires_after() is called, | |||
| * then the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_after(const duration& expiry_time, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().expires_after( | |||
| this->get_implementation(), expiry_time, ec); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use expiry().) Get the timer's expiry time relative to now. | |||
| /** | |||
| * This function may be used to obtain the timer's current expiry time. | |||
| * Whether the timer has expired or not does not affect this value. | |||
| */ | |||
| duration expires_from_now() const | |||
| { | |||
| return this->get_service().expires_from_now(this->get_implementation()); | |||
| } | |||
| /// (Deprecated: Use expires_after().) Set the timer's expiry time relative | |||
| /// to now. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| * | |||
| * @note If the timer has already expired when expires_from_now() is called, | |||
| * then the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_from_now(const duration& expiry_time) | |||
| { | |||
| asio::error_code ec; | |||
| std::size_t s = this->get_service().expires_from_now( | |||
| this->get_implementation(), expiry_time, ec); | |||
| asio::detail::throw_error(ec, "expires_from_now"); | |||
| return s; | |||
| } | |||
| /// (Deprecated: Use expires_after().) Set the timer's expiry time relative | |||
| /// to now. | |||
| /** | |||
| * This function sets the expiry time. Any pending asynchronous wait | |||
| * operations will be cancelled. The handler for each cancelled operation will | |||
| * be invoked with the asio::error::operation_aborted error code. | |||
| * | |||
| * @param expiry_time The expiry time to be used for the timer. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| * | |||
| * @return The number of asynchronous operations that were cancelled. | |||
| * | |||
| * @note If the timer has already expired when expires_from_now() is called, | |||
| * then the handlers for asynchronous wait operations will: | |||
| * | |||
| * @li have already been invoked; or | |||
| * | |||
| * @li have been queued for invocation in the near future. | |||
| * | |||
| * These handlers can no longer be cancelled, and therefore are passed an | |||
| * error code that indicates the successful completion of the wait operation. | |||
| */ | |||
| std::size_t expires_from_now(const duration& expiry_time, | |||
| asio::error_code& ec) | |||
| { | |||
| return this->get_service().expires_from_now( | |||
| this->get_implementation(), expiry_time, ec); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// Perform a blocking wait on the timer. | |||
| /** | |||
| * This function is used to wait for the timer to expire. This function | |||
| * blocks and does not return until the timer has expired. | |||
| * | |||
| * @throws asio::system_error Thrown on failure. | |||
| */ | |||
| void wait() | |||
| { | |||
| asio::error_code ec; | |||
| this->get_service().wait(this->get_implementation(), ec); | |||
| asio::detail::throw_error(ec, "wait"); | |||
| } | |||
| /// Perform a blocking wait on the timer. | |||
| /** | |||
| * This function is used to wait for the timer to expire. This function | |||
| * blocks and does not return until the timer has expired. | |||
| * | |||
| * @param ec Set to indicate what error occurred, if any. | |||
| */ | |||
| void wait(asio::error_code& ec) | |||
| { | |||
| this->get_service().wait(this->get_implementation(), ec); | |||
| } | |||
| /// Start an asynchronous wait on the timer. | |||
| /** | |||
| * This function may be used to initiate an asynchronous wait against the | |||
| * timer. It always returns immediately. | |||
| * | |||
| * For each call to async_wait(), the supplied handler will be called exactly | |||
| * once. The handler will be called when: | |||
| * | |||
| * @li The timer has expired. | |||
| * | |||
| * @li The timer was cancelled, in which case the handler is passed the error | |||
| * code asio::error::operation_aborted. | |||
| * | |||
| * @param handler The handler to be called when the timer expires. Copies | |||
| * will be made of the handler as required. The function signature of the | |||
| * handler must be: | |||
| * @code void handler( | |||
| * const asio::error_code& error // Result of operation. | |||
| * ); @endcode | |||
| * Regardless of whether the asynchronous operation completes immediately or | |||
| * not, the handler will not be invoked from within this function. Invocation | |||
| * of the handler will be performed in a manner equivalent to using | |||
| * asio::io_context::post(). | |||
| */ | |||
| template <typename WaitHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WaitHandler, | |||
| void (asio::error_code)) | |||
| async_wait(ASIO_MOVE_ARG(WaitHandler) handler) | |||
| { | |||
| // If you get an error on the following line it means that your handler does | |||
| // not meet the documented type requirements for a WaitHandler. | |||
| ASIO_WAIT_HANDLER_CHECK(WaitHandler, handler) type_check; | |||
| return this->get_service().async_wait(this->get_implementation(), | |||
| ASIO_MOVE_CAST(WaitHandler)(handler)); | |||
| } | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BASIC_WAITABLE_TIMER_HPP | |||
| @@ -0,0 +1,578 @@ | |||
| // | |||
| // bind_executor.hpp | |||
| // ~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BIND_EXECUTOR_HPP | |||
| #define ASIO_BIND_EXECUTOR_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/detail/variadic_templates.hpp" | |||
| #include "asio/associated_allocator.hpp" | |||
| #include "asio/async_result.hpp" | |||
| #include "asio/handler_type.hpp" | |||
| #include "asio/uses_executor.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| namespace detail { | |||
| template <typename T> | |||
| struct executor_binder_check | |||
| { | |||
| typedef void type; | |||
| }; | |||
| // Helper to automatically define nested typedef result_type. | |||
| template <typename T, typename = void> | |||
| struct executor_binder_result_type | |||
| { | |||
| protected: | |||
| typedef void result_type_or_void; | |||
| }; | |||
| template <typename T> | |||
| struct executor_binder_result_type<T, | |||
| typename executor_binder_check<typename T::result_type>::type> | |||
| { | |||
| typedef typename T::result_type result_type; | |||
| protected: | |||
| typedef result_type result_type_or_void; | |||
| }; | |||
| template <typename R> | |||
| struct executor_binder_result_type<R(*)()> | |||
| { | |||
| typedef R result_type; | |||
| protected: | |||
| typedef result_type result_type_or_void; | |||
| }; | |||
| template <typename R> | |||
| struct executor_binder_result_type<R(&)()> | |||
| { | |||
| typedef R result_type; | |||
| protected: | |||
| typedef result_type result_type_or_void; | |||
| }; | |||
| template <typename R, typename A1> | |||
| struct executor_binder_result_type<R(*)(A1)> | |||
| { | |||
| typedef R result_type; | |||
| protected: | |||
| typedef result_type result_type_or_void; | |||
| }; | |||
| template <typename R, typename A1> | |||
| struct executor_binder_result_type<R(&)(A1)> | |||
| { | |||
| typedef R result_type; | |||
| protected: | |||
| typedef result_type result_type_or_void; | |||
| }; | |||
| template <typename R, typename A1, typename A2> | |||
| struct executor_binder_result_type<R(*)(A1, A2)> | |||
| { | |||
| typedef R result_type; | |||
| protected: | |||
| typedef result_type result_type_or_void; | |||
| }; | |||
| template <typename R, typename A1, typename A2> | |||
| struct executor_binder_result_type<R(&)(A1, A2)> | |||
| { | |||
| typedef R result_type; | |||
| protected: | |||
| typedef result_type result_type_or_void; | |||
| }; | |||
| // Helper to automatically define nested typedef argument_type. | |||
| template <typename T, typename = void> | |||
| struct executor_binder_argument_type {}; | |||
| template <typename T> | |||
| struct executor_binder_argument_type<T, | |||
| typename executor_binder_check<typename T::argument_type>::type> | |||
| { | |||
| typedef typename T::argument_type argument_type; | |||
| }; | |||
| template <typename R, typename A1> | |||
| struct executor_binder_argument_type<R(*)(A1)> | |||
| { | |||
| typedef A1 argument_type; | |||
| }; | |||
| template <typename R, typename A1> | |||
| struct executor_binder_argument_type<R(&)(A1)> | |||
| { | |||
| typedef A1 argument_type; | |||
| }; | |||
| // Helper to automatically define nested typedefs first_argument_type and | |||
| // second_argument_type. | |||
| template <typename T, typename = void> | |||
| struct executor_binder_argument_types {}; | |||
| template <typename T> | |||
| struct executor_binder_argument_types<T, | |||
| typename executor_binder_check<typename T::first_argument_type>::type> | |||
| { | |||
| typedef typename T::first_argument_type first_argument_type; | |||
| typedef typename T::second_argument_type second_argument_type; | |||
| }; | |||
| template <typename R, typename A1, typename A2> | |||
| struct executor_binder_argument_type<R(*)(A1, A2)> | |||
| { | |||
| typedef A1 first_argument_type; | |||
| typedef A2 second_argument_type; | |||
| }; | |||
| template <typename R, typename A1, typename A2> | |||
| struct executor_binder_argument_type<R(&)(A1, A2)> | |||
| { | |||
| typedef A1 first_argument_type; | |||
| typedef A2 second_argument_type; | |||
| }; | |||
| // Helper to: | |||
| // - Apply the empty base optimisation to the executor. | |||
| // - Perform uses_executor construction of the target type, if required. | |||
| template <typename T, typename Executor, bool UsesExecutor> | |||
| class executor_binder_base; | |||
| template <typename T, typename Executor> | |||
| class executor_binder_base<T, Executor, true> | |||
| : protected Executor | |||
| { | |||
| protected: | |||
| template <typename E, typename U> | |||
| executor_binder_base(ASIO_MOVE_ARG(E) e, ASIO_MOVE_ARG(U) u) | |||
| : Executor(ASIO_MOVE_CAST(E)(e)), | |||
| target_(executor_arg_t(), static_cast<const Executor&>(*this), | |||
| ASIO_MOVE_CAST(U)(u)) | |||
| { | |||
| } | |||
| T target_; | |||
| }; | |||
| template <typename T, typename Executor> | |||
| class executor_binder_base<T, Executor, false> | |||
| : protected Executor | |||
| { | |||
| protected: | |||
| template <typename E, typename U> | |||
| executor_binder_base(ASIO_MOVE_ARG(E) e, ASIO_MOVE_ARG(U) u) | |||
| : Executor(ASIO_MOVE_CAST(E)(e)), | |||
| target_(ASIO_MOVE_CAST(U)(u)) | |||
| { | |||
| } | |||
| T target_; | |||
| }; | |||
| // Helper to enable SFINAE on zero-argument operator() below. | |||
| template <typename T, typename = void> | |||
| struct executor_binder_result_of0 | |||
| { | |||
| typedef void type; | |||
| }; | |||
| template <typename T> | |||
| struct executor_binder_result_of0<T, | |||
| typename executor_binder_check<typename result_of<T()>::type>::type> | |||
| { | |||
| typedef typename result_of<T()>::type type; | |||
| }; | |||
| } // namespace detail | |||
| /// A call wrapper type to bind an executor of type @c Executor to an object of | |||
| /// type @c T. | |||
| template <typename T, typename Executor> | |||
| class executor_binder | |||
| #if !defined(GENERATING_DOCUMENTATION) | |||
| : public detail::executor_binder_result_type<T>, | |||
| public detail::executor_binder_argument_type<T>, | |||
| public detail::executor_binder_argument_types<T>, | |||
| private detail::executor_binder_base< | |||
| T, Executor, uses_executor<T, Executor>::value> | |||
| #endif // !defined(GENERATING_DOCUMENTATION) | |||
| { | |||
| public: | |||
| /// The type of the target object. | |||
| typedef T target_type; | |||
| /// The type of the associated executor. | |||
| typedef Executor executor_type; | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// The return type if a function. | |||
| /** | |||
| * The type of @c result_type is based on the type @c T of the wrapper's | |||
| * target object: | |||
| * | |||
| * @li if @c T is a pointer to function type, @c result_type is a synonym for | |||
| * the return type of @c T; | |||
| * | |||
| * @li if @c T is a class type with a member type @c result_type, then @c | |||
| * result_type is a synonym for @c T::result_type; | |||
| * | |||
| * @li otherwise @c result_type is not defined. | |||
| */ | |||
| typedef see_below result_type; | |||
| /// The type of the function's argument. | |||
| /** | |||
| * The type of @c argument_type is based on the type @c T of the wrapper's | |||
| * target object: | |||
| * | |||
| * @li if @c T is a pointer to a function type accepting a single argument, | |||
| * @c argument_type is a synonym for the return type of @c T; | |||
| * | |||
| * @li if @c T is a class type with a member type @c argument_type, then @c | |||
| * argument_type is a synonym for @c T::argument_type; | |||
| * | |||
| * @li otherwise @c argument_type is not defined. | |||
| */ | |||
| typedef see_below argument_type; | |||
| /// The type of the function's first argument. | |||
| /** | |||
| * The type of @c first_argument_type is based on the type @c T of the | |||
| * wrapper's target object: | |||
| * | |||
| * @li if @c T is a pointer to a function type accepting two arguments, @c | |||
| * first_argument_type is a synonym for the return type of @c T; | |||
| * | |||
| * @li if @c T is a class type with a member type @c first_argument_type, | |||
| * then @c first_argument_type is a synonym for @c T::first_argument_type; | |||
| * | |||
| * @li otherwise @c first_argument_type is not defined. | |||
| */ | |||
| typedef see_below first_argument_type; | |||
| /// The type of the function's second argument. | |||
| /** | |||
| * The type of @c second_argument_type is based on the type @c T of the | |||
| * wrapper's target object: | |||
| * | |||
| * @li if @c T is a pointer to a function type accepting two arguments, @c | |||
| * second_argument_type is a synonym for the return type of @c T; | |||
| * | |||
| * @li if @c T is a class type with a member type @c first_argument_type, | |||
| * then @c second_argument_type is a synonym for @c T::second_argument_type; | |||
| * | |||
| * @li otherwise @c second_argument_type is not defined. | |||
| */ | |||
| typedef see_below second_argument_type; | |||
| #endif // defined(GENERATING_DOCUMENTATION) | |||
| /// Construct an executor wrapper for the specified object. | |||
| /** | |||
| * This constructor is only valid if the type @c T is constructible from type | |||
| * @c U. | |||
| */ | |||
| template <typename U> | |||
| executor_binder(executor_arg_t, const executor_type& e, | |||
| ASIO_MOVE_ARG(U) u) | |||
| : base_type(e, ASIO_MOVE_CAST(U)(u)) | |||
| { | |||
| } | |||
| /// Copy constructor. | |||
| executor_binder(const executor_binder& other) | |||
| : base_type(other.get_executor(), other.get()) | |||
| { | |||
| } | |||
| /// Construct a copy, but specify a different executor. | |||
| executor_binder(executor_arg_t, const executor_type& e, | |||
| const executor_binder& other) | |||
| : base_type(e, other.get()) | |||
| { | |||
| } | |||
| /// Construct a copy of a different executor wrapper type. | |||
| /** | |||
| * This constructor is only valid if the @c Executor type is constructible | |||
| * from type @c OtherExecutor, and the type @c T is constructible from type | |||
| * @c U. | |||
| */ | |||
| template <typename U, typename OtherExecutor> | |||
| executor_binder(const executor_binder<U, OtherExecutor>& other) | |||
| : base_type(other.get_executor(), other.get()) | |||
| { | |||
| } | |||
| /// Construct a copy of a different executor wrapper type, but specify a | |||
| /// different executor. | |||
| /** | |||
| * This constructor is only valid if the type @c T is constructible from type | |||
| * @c U. | |||
| */ | |||
| template <typename U, typename OtherExecutor> | |||
| executor_binder(executor_arg_t, const executor_type& e, | |||
| const executor_binder<U, OtherExecutor>& other) | |||
| : base_type(e, other.get()) | |||
| { | |||
| } | |||
| #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Move constructor. | |||
| executor_binder(executor_binder&& other) | |||
| : base_type(ASIO_MOVE_CAST(executor_type)(other.get_executor()), | |||
| ASIO_MOVE_CAST(T)(other.get())) | |||
| { | |||
| } | |||
| /// Move construct the target object, but specify a different executor. | |||
| executor_binder(executor_arg_t, const executor_type& e, | |||
| executor_binder&& other) | |||
| : base_type(e, ASIO_MOVE_CAST(T)(other.get())) | |||
| { | |||
| } | |||
| /// Move construct from a different executor wrapper type. | |||
| template <typename U, typename OtherExecutor> | |||
| executor_binder(executor_binder<U, OtherExecutor>&& other) | |||
| : base_type(ASIO_MOVE_CAST(OtherExecutor)(other.get_executor()), | |||
| ASIO_MOVE_CAST(U)(other.get())) | |||
| { | |||
| } | |||
| /// Move construct from a different executor wrapper type, but specify a | |||
| /// different executor. | |||
| template <typename U, typename OtherExecutor> | |||
| executor_binder(executor_arg_t, const executor_type& e, | |||
| executor_binder<U, OtherExecutor>&& other) | |||
| : base_type(e, ASIO_MOVE_CAST(U)(other.get())) | |||
| { | |||
| } | |||
| #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION) | |||
| /// Destructor. | |||
| ~executor_binder() | |||
| { | |||
| } | |||
| /// Obtain a reference to the target object. | |||
| target_type& get() ASIO_NOEXCEPT | |||
| { | |||
| return this->target_; | |||
| } | |||
| /// Obtain a reference to the target object. | |||
| const target_type& get() const ASIO_NOEXCEPT | |||
| { | |||
| return this->target_; | |||
| } | |||
| /// Obtain the associated executor. | |||
| executor_type get_executor() const ASIO_NOEXCEPT | |||
| { | |||
| return static_cast<const Executor&>(*this); | |||
| } | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| template <typename... Args> auto operator()(Args&& ...); | |||
| template <typename... Args> auto operator()(Args&& ...) const; | |||
| #elif defined(ASIO_HAS_VARIADIC_TEMPLATES) | |||
| /// Forwarding function call operator. | |||
| template <typename... Args> | |||
| typename result_of<T(Args...)>::type operator()( | |||
| ASIO_MOVE_ARG(Args)... args) | |||
| { | |||
| return this->target_(ASIO_MOVE_CAST(Args)(args)...); | |||
| } | |||
| /// Forwarding function call operator. | |||
| template <typename... Args> | |||
| typename result_of<T(Args...)>::type operator()( | |||
| ASIO_MOVE_ARG(Args)... args) const | |||
| { | |||
| return this->target_(ASIO_MOVE_CAST(Args)(args)...); | |||
| } | |||
| #elif defined(ASIO_HAS_STD_TYPE_TRAITS) && !defined(_MSC_VER) | |||
| typename detail::executor_binder_result_of0<T>::type operator()() | |||
| { | |||
| return this->target_(); | |||
| } | |||
| typename detail::executor_binder_result_of0<T>::type operator()() const | |||
| { | |||
| return this->target_(); | |||
| } | |||
| #define ASIO_PRIVATE_BIND_EXECUTOR_CALL_DEF(n) \ | |||
| template <ASIO_VARIADIC_TPARAMS(n)> \ | |||
| typename result_of<T(ASIO_VARIADIC_TARGS(n))>::type operator()( \ | |||
| ASIO_VARIADIC_MOVE_PARAMS(n)) \ | |||
| { \ | |||
| return this->target_(ASIO_VARIADIC_MOVE_ARGS(n)); \ | |||
| } \ | |||
| \ | |||
| template <ASIO_VARIADIC_TPARAMS(n)> \ | |||
| typename result_of<T(ASIO_VARIADIC_TARGS(n))>::type operator()( \ | |||
| ASIO_VARIADIC_MOVE_PARAMS(n)) const \ | |||
| { \ | |||
| return this->target_(ASIO_VARIADIC_MOVE_ARGS(n)); \ | |||
| } \ | |||
| /**/ | |||
| ASIO_VARIADIC_GENERATE(ASIO_PRIVATE_BIND_EXECUTOR_CALL_DEF) | |||
| #undef ASIO_PRIVATE_BIND_EXECUTOR_CALL_DEF | |||
| #else // defined(ASIO_HAS_STD_TYPE_TRAITS) && !defined(_MSC_VER) | |||
| typedef typename detail::executor_binder_result_type<T>::result_type_or_void | |||
| result_type_or_void; | |||
| result_type_or_void operator()() | |||
| { | |||
| return this->target_(); | |||
| } | |||
| result_type_or_void operator()() const | |||
| { | |||
| return this->target_(); | |||
| } | |||
| #define ASIO_PRIVATE_BIND_EXECUTOR_CALL_DEF(n) \ | |||
| template <ASIO_VARIADIC_TPARAMS(n)> \ | |||
| result_type_or_void operator()( \ | |||
| ASIO_VARIADIC_MOVE_PARAMS(n)) \ | |||
| { \ | |||
| return this->target_(ASIO_VARIADIC_MOVE_ARGS(n)); \ | |||
| } \ | |||
| \ | |||
| template <ASIO_VARIADIC_TPARAMS(n)> \ | |||
| result_type_or_void operator()( \ | |||
| ASIO_VARIADIC_MOVE_PARAMS(n)) const \ | |||
| { \ | |||
| return this->target_(ASIO_VARIADIC_MOVE_ARGS(n)); \ | |||
| } \ | |||
| /**/ | |||
| ASIO_VARIADIC_GENERATE(ASIO_PRIVATE_BIND_EXECUTOR_CALL_DEF) | |||
| #undef ASIO_PRIVATE_BIND_EXECUTOR_CALL_DEF | |||
| #endif // defined(ASIO_HAS_STD_TYPE_TRAITS) && !defined(_MSC_VER) | |||
| private: | |||
| typedef detail::executor_binder_base<T, Executor, | |||
| uses_executor<T, Executor>::value> base_type; | |||
| }; | |||
| /// Associate an object of type @c T with an executor of type @c Executor. | |||
| template <typename Executor, typename T> | |||
| inline executor_binder<typename decay<T>::type, Executor> | |||
| bind_executor(const Executor& ex, ASIO_MOVE_ARG(T) t, | |||
| typename enable_if<is_executor<Executor>::value>::type* = 0) | |||
| { | |||
| return executor_binder<typename decay<T>::type, Executor>( | |||
| executor_arg_t(), ex, ASIO_MOVE_CAST(T)(t)); | |||
| } | |||
| /// Associate an object of type @c T with an execution context's executor. | |||
| template <typename ExecutionContext, typename T> | |||
| inline executor_binder<typename decay<T>::type, | |||
| typename ExecutionContext::executor_type> | |||
| bind_executor(ExecutionContext& ctx, ASIO_MOVE_ARG(T) t, | |||
| typename enable_if<is_convertible< | |||
| ExecutionContext&, execution_context&>::value>::type* = 0) | |||
| { | |||
| return executor_binder<typename decay<T>::type, | |||
| typename ExecutionContext::executor_type>( | |||
| executor_arg_t(), ctx.get_executor(), ASIO_MOVE_CAST(T)(t)); | |||
| } | |||
| #if !defined(GENERATING_DOCUMENTATION) | |||
| template <typename T, typename Executor> | |||
| struct uses_executor<executor_binder<T, Executor>, Executor> | |||
| : true_type {}; | |||
| template <typename T, typename Executor, typename Signature> | |||
| struct handler_type<executor_binder<T, Executor>, Signature> | |||
| { | |||
| typedef executor_binder< | |||
| typename handler_type<T, Signature>::type, Executor> type; | |||
| }; | |||
| template <typename T, typename Executor> | |||
| class async_result<executor_binder<T, Executor> > | |||
| { | |||
| public: | |||
| typedef typename async_result<T>::type type; | |||
| explicit async_result(executor_binder<T, Executor>& b) | |||
| : target_(b.get()) | |||
| { | |||
| } | |||
| type get() | |||
| { | |||
| return target_.get(); | |||
| } | |||
| private: | |||
| async_result<T> target_; | |||
| }; | |||
| template <typename T, typename Executor, typename Allocator> | |||
| struct associated_allocator<executor_binder<T, Executor>, Allocator> | |||
| { | |||
| typedef typename associated_allocator<T, Allocator>::type type; | |||
| static type get(const executor_binder<T, Executor>& b, | |||
| const Allocator& a = Allocator()) ASIO_NOEXCEPT | |||
| { | |||
| return associated_allocator<T, Allocator>::get(b.get(), a); | |||
| } | |||
| }; | |||
| template <typename T, typename Executor, typename Executor1> | |||
| struct associated_executor<executor_binder<T, Executor>, Executor1> | |||
| { | |||
| typedef Executor type; | |||
| static type get(const executor_binder<T, Executor>& b, | |||
| const Executor1& = Executor1()) ASIO_NOEXCEPT | |||
| { | |||
| return b.get_executor(); | |||
| } | |||
| }; | |||
| #endif // !defined(GENERATING_DOCUMENTATION) | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BIND_EXECUTOR_HPP | |||
| @@ -0,0 +1,262 @@ | |||
| // | |||
| // buffered_read_stream.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BUFFERED_READ_STREAM_HPP | |||
| #define ASIO_BUFFERED_READ_STREAM_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/async_result.hpp" | |||
| #include "asio/buffered_read_stream_fwd.hpp" | |||
| #include "asio/buffer.hpp" | |||
| #include "asio/detail/bind_handler.hpp" | |||
| #include "asio/detail/buffer_resize_guard.hpp" | |||
| #include "asio/detail/buffered_stream_storage.hpp" | |||
| #include "asio/detail/noncopyable.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/io_context.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Adds buffering to the read-related operations of a stream. | |||
| /** | |||
| * The buffered_read_stream class template can be used to add buffering to the | |||
| * synchronous and asynchronous read operations of a stream. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| * | |||
| * @par Concepts: | |||
| * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. | |||
| */ | |||
| template <typename Stream> | |||
| class buffered_read_stream | |||
| : private noncopyable | |||
| { | |||
| public: | |||
| /// The type of the next layer. | |||
| typedef typename remove_reference<Stream>::type next_layer_type; | |||
| /// The type of the lowest layer. | |||
| typedef typename next_layer_type::lowest_layer_type lowest_layer_type; | |||
| /// The type of the executor associated with the object. | |||
| typedef typename lowest_layer_type::executor_type executor_type; | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// The default buffer size. | |||
| static const std::size_t default_buffer_size = implementation_defined; | |||
| #else | |||
| ASIO_STATIC_CONSTANT(std::size_t, default_buffer_size = 1024); | |||
| #endif | |||
| /// Construct, passing the specified argument to initialise the next layer. | |||
| template <typename Arg> | |||
| explicit buffered_read_stream(Arg& a) | |||
| : next_layer_(a), | |||
| storage_(default_buffer_size) | |||
| { | |||
| } | |||
| /// Construct, passing the specified argument to initialise the next layer. | |||
| template <typename Arg> | |||
| buffered_read_stream(Arg& a, std::size_t buffer_size) | |||
| : next_layer_(a), | |||
| storage_(buffer_size) | |||
| { | |||
| } | |||
| /// Get a reference to the next layer. | |||
| next_layer_type& next_layer() | |||
| { | |||
| return next_layer_; | |||
| } | |||
| /// Get a reference to the lowest layer. | |||
| lowest_layer_type& lowest_layer() | |||
| { | |||
| return next_layer_.lowest_layer(); | |||
| } | |||
| /// Get a const reference to the lowest layer. | |||
| const lowest_layer_type& lowest_layer() const | |||
| { | |||
| return next_layer_.lowest_layer(); | |||
| } | |||
| /// Get the executor associated with the object. | |||
| executor_type get_executor() ASIO_NOEXCEPT | |||
| { | |||
| return next_layer_.lowest_layer().get_executor(); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| asio::io_context& get_io_context() | |||
| { | |||
| return next_layer_.get_io_context(); | |||
| } | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| asio::io_context& get_io_service() | |||
| { | |||
| return next_layer_.get_io_service(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// Close the stream. | |||
| void close() | |||
| { | |||
| next_layer_.close(); | |||
| } | |||
| /// Close the stream. | |||
| asio::error_code close(asio::error_code& ec) | |||
| { | |||
| return next_layer_.close(ec); | |||
| } | |||
| /// Write the given data to the stream. Returns the number of bytes written. | |||
| /// Throws an exception on failure. | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers) | |||
| { | |||
| return next_layer_.write_some(buffers); | |||
| } | |||
| /// Write the given data to the stream. Returns the number of bytes written, | |||
| /// or 0 if an error occurred. | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return next_layer_.write_some(buffers, ec); | |||
| } | |||
| /// Start an asynchronous write. The data being written must be valid for the | |||
| /// lifetime of the asynchronous operation. | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_write_some(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| async_completion<WriteHandler, | |||
| void (asio::error_code, std::size_t)> init(handler); | |||
| next_layer_.async_write_some(buffers, | |||
| ASIO_MOVE_CAST(ASIO_HANDLER_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)))(init.handler)); | |||
| return init.result.get(); | |||
| } | |||
| /// Fill the buffer with some data. Returns the number of bytes placed in the | |||
| /// buffer as a result of the operation. Throws an exception on failure. | |||
| std::size_t fill(); | |||
| /// Fill the buffer with some data. Returns the number of bytes placed in the | |||
| /// buffer as a result of the operation, or 0 if an error occurred. | |||
| std::size_t fill(asio::error_code& ec); | |||
| /// Start an asynchronous fill. | |||
| template <typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_fill(ASIO_MOVE_ARG(ReadHandler) handler); | |||
| /// Read some data from the stream. Returns the number of bytes read. Throws | |||
| /// an exception on failure. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers); | |||
| /// Read some data from the stream. Returns the number of bytes read or 0 if | |||
| /// an error occurred. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec); | |||
| /// Start an asynchronous read. The buffer into which the data will be read | |||
| /// must be valid for the lifetime of the asynchronous operation. | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_read_some(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler); | |||
| /// Peek at the incoming data on the stream. Returns the number of bytes read. | |||
| /// Throws an exception on failure. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t peek(const MutableBufferSequence& buffers); | |||
| /// Peek at the incoming data on the stream. Returns the number of bytes read, | |||
| /// or 0 if an error occurred. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t peek(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec); | |||
| /// Determine the amount of data that may be read without blocking. | |||
| std::size_t in_avail() | |||
| { | |||
| return storage_.size(); | |||
| } | |||
| /// Determine the amount of data that may be read without blocking. | |||
| std::size_t in_avail(asio::error_code& ec) | |||
| { | |||
| ec = asio::error_code(); | |||
| return storage_.size(); | |||
| } | |||
| private: | |||
| /// Copy data out of the internal buffer to the specified target buffer. | |||
| /// Returns the number of bytes copied. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t copy(const MutableBufferSequence& buffers) | |||
| { | |||
| std::size_t bytes_copied = asio::buffer_copy( | |||
| buffers, storage_.data(), storage_.size()); | |||
| storage_.consume(bytes_copied); | |||
| return bytes_copied; | |||
| } | |||
| /// Copy data from the internal buffer to the specified target buffer, without | |||
| /// removing the data from the internal buffer. Returns the number of bytes | |||
| /// copied. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t peek_copy(const MutableBufferSequence& buffers) | |||
| { | |||
| return asio::buffer_copy(buffers, storage_.data(), storage_.size()); | |||
| } | |||
| /// The next layer. | |||
| Stream next_layer_; | |||
| // The data in the buffer. | |||
| detail::buffered_stream_storage storage_; | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #include "asio/impl/buffered_read_stream.hpp" | |||
| #endif // ASIO_BUFFERED_READ_STREAM_HPP | |||
| @@ -0,0 +1,25 @@ | |||
| // | |||
| // buffered_read_stream_fwd.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BUFFERED_READ_STREAM_FWD_HPP | |||
| #define ASIO_BUFFERED_READ_STREAM_FWD_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| namespace asio { | |||
| template <typename Stream> | |||
| class buffered_read_stream; | |||
| } // namespace asio | |||
| #endif // ASIO_BUFFERED_READ_STREAM_FWD_HPP | |||
| @@ -0,0 +1,277 @@ | |||
| // | |||
| // buffered_stream.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BUFFERED_STREAM_HPP | |||
| #define ASIO_BUFFERED_STREAM_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/async_result.hpp" | |||
| #include "asio/buffered_read_stream.hpp" | |||
| #include "asio/buffered_write_stream.hpp" | |||
| #include "asio/buffered_stream_fwd.hpp" | |||
| #include "asio/detail/noncopyable.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/io_context.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Adds buffering to the read- and write-related operations of a stream. | |||
| /** | |||
| * The buffered_stream class template can be used to add buffering to the | |||
| * synchronous and asynchronous read and write operations of a stream. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| * | |||
| * @par Concepts: | |||
| * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. | |||
| */ | |||
| template <typename Stream> | |||
| class buffered_stream | |||
| : private noncopyable | |||
| { | |||
| public: | |||
| /// The type of the next layer. | |||
| typedef typename remove_reference<Stream>::type next_layer_type; | |||
| /// The type of the lowest layer. | |||
| typedef typename next_layer_type::lowest_layer_type lowest_layer_type; | |||
| /// The type of the executor associated with the object. | |||
| typedef typename lowest_layer_type::executor_type executor_type; | |||
| /// Construct, passing the specified argument to initialise the next layer. | |||
| template <typename Arg> | |||
| explicit buffered_stream(Arg& a) | |||
| : inner_stream_impl_(a), | |||
| stream_impl_(inner_stream_impl_) | |||
| { | |||
| } | |||
| /// Construct, passing the specified argument to initialise the next layer. | |||
| template <typename Arg> | |||
| explicit buffered_stream(Arg& a, std::size_t read_buffer_size, | |||
| std::size_t write_buffer_size) | |||
| : inner_stream_impl_(a, write_buffer_size), | |||
| stream_impl_(inner_stream_impl_, read_buffer_size) | |||
| { | |||
| } | |||
| /// Get a reference to the next layer. | |||
| next_layer_type& next_layer() | |||
| { | |||
| return stream_impl_.next_layer().next_layer(); | |||
| } | |||
| /// Get a reference to the lowest layer. | |||
| lowest_layer_type& lowest_layer() | |||
| { | |||
| return stream_impl_.lowest_layer(); | |||
| } | |||
| /// Get a const reference to the lowest layer. | |||
| const lowest_layer_type& lowest_layer() const | |||
| { | |||
| return stream_impl_.lowest_layer(); | |||
| } | |||
| /// Get the executor associated with the object. | |||
| executor_type get_executor() ASIO_NOEXCEPT | |||
| { | |||
| return stream_impl_.lowest_layer().get_executor(); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| asio::io_context& get_io_context() | |||
| { | |||
| return stream_impl_.get_io_context(); | |||
| } | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| asio::io_context& get_io_service() | |||
| { | |||
| return stream_impl_.get_io_service(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// Close the stream. | |||
| void close() | |||
| { | |||
| stream_impl_.close(); | |||
| } | |||
| /// Close the stream. | |||
| asio::error_code close(asio::error_code& ec) | |||
| { | |||
| return stream_impl_.close(ec); | |||
| } | |||
| /// Flush all data from the buffer to the next layer. Returns the number of | |||
| /// bytes written to the next layer on the last write operation. Throws an | |||
| /// exception on failure. | |||
| std::size_t flush() | |||
| { | |||
| return stream_impl_.next_layer().flush(); | |||
| } | |||
| /// Flush all data from the buffer to the next layer. Returns the number of | |||
| /// bytes written to the next layer on the last write operation, or 0 if an | |||
| /// error occurred. | |||
| std::size_t flush(asio::error_code& ec) | |||
| { | |||
| return stream_impl_.next_layer().flush(ec); | |||
| } | |||
| /// Start an asynchronous flush. | |||
| template <typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_flush(ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| return stream_impl_.next_layer().async_flush( | |||
| ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Write the given data to the stream. Returns the number of bytes written. | |||
| /// Throws an exception on failure. | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers) | |||
| { | |||
| return stream_impl_.write_some(buffers); | |||
| } | |||
| /// Write the given data to the stream. Returns the number of bytes written, | |||
| /// or 0 if an error occurred. | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return stream_impl_.write_some(buffers, ec); | |||
| } | |||
| /// Start an asynchronous write. The data being written must be valid for the | |||
| /// lifetime of the asynchronous operation. | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_write_some(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler) | |||
| { | |||
| return stream_impl_.async_write_some(buffers, | |||
| ASIO_MOVE_CAST(WriteHandler)(handler)); | |||
| } | |||
| /// Fill the buffer with some data. Returns the number of bytes placed in the | |||
| /// buffer as a result of the operation. Throws an exception on failure. | |||
| std::size_t fill() | |||
| { | |||
| return stream_impl_.fill(); | |||
| } | |||
| /// Fill the buffer with some data. Returns the number of bytes placed in the | |||
| /// buffer as a result of the operation, or 0 if an error occurred. | |||
| std::size_t fill(asio::error_code& ec) | |||
| { | |||
| return stream_impl_.fill(ec); | |||
| } | |||
| /// Start an asynchronous fill. | |||
| template <typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_fill(ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| return stream_impl_.async_fill(ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Read some data from the stream. Returns the number of bytes read. Throws | |||
| /// an exception on failure. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers) | |||
| { | |||
| return stream_impl_.read_some(buffers); | |||
| } | |||
| /// Read some data from the stream. Returns the number of bytes read or 0 if | |||
| /// an error occurred. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return stream_impl_.read_some(buffers, ec); | |||
| } | |||
| /// Start an asynchronous read. The buffer into which the data will be read | |||
| /// must be valid for the lifetime of the asynchronous operation. | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_read_some(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| return stream_impl_.async_read_some(buffers, | |||
| ASIO_MOVE_CAST(ReadHandler)(handler)); | |||
| } | |||
| /// Peek at the incoming data on the stream. Returns the number of bytes read. | |||
| /// Throws an exception on failure. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t peek(const MutableBufferSequence& buffers) | |||
| { | |||
| return stream_impl_.peek(buffers); | |||
| } | |||
| /// Peek at the incoming data on the stream. Returns the number of bytes read, | |||
| /// or 0 if an error occurred. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t peek(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return stream_impl_.peek(buffers, ec); | |||
| } | |||
| /// Determine the amount of data that may be read without blocking. | |||
| std::size_t in_avail() | |||
| { | |||
| return stream_impl_.in_avail(); | |||
| } | |||
| /// Determine the amount of data that may be read without blocking. | |||
| std::size_t in_avail(asio::error_code& ec) | |||
| { | |||
| return stream_impl_.in_avail(ec); | |||
| } | |||
| private: | |||
| // The buffered write stream. | |||
| typedef buffered_write_stream<Stream> write_stream_type; | |||
| write_stream_type inner_stream_impl_; | |||
| // The buffered read stream. | |||
| typedef buffered_read_stream<write_stream_type&> read_stream_type; | |||
| read_stream_type stream_impl_; | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BUFFERED_STREAM_HPP | |||
| @@ -0,0 +1,25 @@ | |||
| // | |||
| // buffered_stream_fwd.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BUFFERED_STREAM_FWD_HPP | |||
| #define ASIO_BUFFERED_STREAM_FWD_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| namespace asio { | |||
| template <typename Stream> | |||
| class buffered_stream; | |||
| } // namespace asio | |||
| #endif // ASIO_BUFFERED_STREAM_FWD_HPP | |||
| @@ -0,0 +1,254 @@ | |||
| // | |||
| // buffered_write_stream.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BUFFERED_WRITE_STREAM_HPP | |||
| #define ASIO_BUFFERED_WRITE_STREAM_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/buffered_write_stream_fwd.hpp" | |||
| #include "asio/buffer.hpp" | |||
| #include "asio/completion_condition.hpp" | |||
| #include "asio/detail/bind_handler.hpp" | |||
| #include "asio/detail/buffered_stream_storage.hpp" | |||
| #include "asio/detail/noncopyable.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/error.hpp" | |||
| #include "asio/io_context.hpp" | |||
| #include "asio/write.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| /// Adds buffering to the write-related operations of a stream. | |||
| /** | |||
| * The buffered_write_stream class template can be used to add buffering to the | |||
| * synchronous and asynchronous write operations of a stream. | |||
| * | |||
| * @par Thread Safety | |||
| * @e Distinct @e objects: Safe.@n | |||
| * @e Shared @e objects: Unsafe. | |||
| * | |||
| * @par Concepts: | |||
| * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. | |||
| */ | |||
| template <typename Stream> | |||
| class buffered_write_stream | |||
| : private noncopyable | |||
| { | |||
| public: | |||
| /// The type of the next layer. | |||
| typedef typename remove_reference<Stream>::type next_layer_type; | |||
| /// The type of the lowest layer. | |||
| typedef typename next_layer_type::lowest_layer_type lowest_layer_type; | |||
| /// The type of the executor associated with the object. | |||
| typedef typename lowest_layer_type::executor_type executor_type; | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// The default buffer size. | |||
| static const std::size_t default_buffer_size = implementation_defined; | |||
| #else | |||
| ASIO_STATIC_CONSTANT(std::size_t, default_buffer_size = 1024); | |||
| #endif | |||
| /// Construct, passing the specified argument to initialise the next layer. | |||
| template <typename Arg> | |||
| explicit buffered_write_stream(Arg& a) | |||
| : next_layer_(a), | |||
| storage_(default_buffer_size) | |||
| { | |||
| } | |||
| /// Construct, passing the specified argument to initialise the next layer. | |||
| template <typename Arg> | |||
| buffered_write_stream(Arg& a, std::size_t buffer_size) | |||
| : next_layer_(a), | |||
| storage_(buffer_size) | |||
| { | |||
| } | |||
| /// Get a reference to the next layer. | |||
| next_layer_type& next_layer() | |||
| { | |||
| return next_layer_; | |||
| } | |||
| /// Get a reference to the lowest layer. | |||
| lowest_layer_type& lowest_layer() | |||
| { | |||
| return next_layer_.lowest_layer(); | |||
| } | |||
| /// Get a const reference to the lowest layer. | |||
| const lowest_layer_type& lowest_layer() const | |||
| { | |||
| return next_layer_.lowest_layer(); | |||
| } | |||
| /// Get the executor associated with the object. | |||
| executor_type get_executor() ASIO_NOEXCEPT | |||
| { | |||
| return next_layer_.lowest_layer().get_executor(); | |||
| } | |||
| #if !defined(ASIO_NO_DEPRECATED) | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| asio::io_context& get_io_context() | |||
| { | |||
| return next_layer_.get_io_context(); | |||
| } | |||
| /// (Deprecated: Use get_executor().) Get the io_context associated with the | |||
| /// object. | |||
| asio::io_context& get_io_service() | |||
| { | |||
| return next_layer_.get_io_service(); | |||
| } | |||
| #endif // !defined(ASIO_NO_DEPRECATED) | |||
| /// Close the stream. | |||
| void close() | |||
| { | |||
| next_layer_.close(); | |||
| } | |||
| /// Close the stream. | |||
| asio::error_code close(asio::error_code& ec) | |||
| { | |||
| return next_layer_.close(ec); | |||
| } | |||
| /// Flush all data from the buffer to the next layer. Returns the number of | |||
| /// bytes written to the next layer on the last write operation. Throws an | |||
| /// exception on failure. | |||
| std::size_t flush(); | |||
| /// Flush all data from the buffer to the next layer. Returns the number of | |||
| /// bytes written to the next layer on the last write operation, or 0 if an | |||
| /// error occurred. | |||
| std::size_t flush(asio::error_code& ec); | |||
| /// Start an asynchronous flush. | |||
| template <typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_flush(ASIO_MOVE_ARG(WriteHandler) handler); | |||
| /// Write the given data to the stream. Returns the number of bytes written. | |||
| /// Throws an exception on failure. | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers); | |||
| /// Write the given data to the stream. Returns the number of bytes written, | |||
| /// or 0 if an error occurred and the error handler did not throw. | |||
| template <typename ConstBufferSequence> | |||
| std::size_t write_some(const ConstBufferSequence& buffers, | |||
| asio::error_code& ec); | |||
| /// Start an asynchronous write. The data being written must be valid for the | |||
| /// lifetime of the asynchronous operation. | |||
| template <typename ConstBufferSequence, typename WriteHandler> | |||
| ASIO_INITFN_RESULT_TYPE(WriteHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_write_some(const ConstBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(WriteHandler) handler); | |||
| /// Read some data from the stream. Returns the number of bytes read. Throws | |||
| /// an exception on failure. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers) | |||
| { | |||
| return next_layer_.read_some(buffers); | |||
| } | |||
| /// Read some data from the stream. Returns the number of bytes read or 0 if | |||
| /// an error occurred. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t read_some(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return next_layer_.read_some(buffers, ec); | |||
| } | |||
| /// Start an asynchronous read. The buffer into which the data will be read | |||
| /// must be valid for the lifetime of the asynchronous operation. | |||
| template <typename MutableBufferSequence, typename ReadHandler> | |||
| ASIO_INITFN_RESULT_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)) | |||
| async_read_some(const MutableBufferSequence& buffers, | |||
| ASIO_MOVE_ARG(ReadHandler) handler) | |||
| { | |||
| async_completion<ReadHandler, | |||
| void (asio::error_code, std::size_t)> init(handler); | |||
| next_layer_.async_read_some(buffers, | |||
| ASIO_MOVE_CAST(ASIO_HANDLER_TYPE(ReadHandler, | |||
| void (asio::error_code, std::size_t)))(init.handler)); | |||
| return init.result.get(); | |||
| } | |||
| /// Peek at the incoming data on the stream. Returns the number of bytes read. | |||
| /// Throws an exception on failure. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t peek(const MutableBufferSequence& buffers) | |||
| { | |||
| return next_layer_.peek(buffers); | |||
| } | |||
| /// Peek at the incoming data on the stream. Returns the number of bytes read, | |||
| /// or 0 if an error occurred. | |||
| template <typename MutableBufferSequence> | |||
| std::size_t peek(const MutableBufferSequence& buffers, | |||
| asio::error_code& ec) | |||
| { | |||
| return next_layer_.peek(buffers, ec); | |||
| } | |||
| /// Determine the amount of data that may be read without blocking. | |||
| std::size_t in_avail() | |||
| { | |||
| return next_layer_.in_avail(); | |||
| } | |||
| /// Determine the amount of data that may be read without blocking. | |||
| std::size_t in_avail(asio::error_code& ec) | |||
| { | |||
| return next_layer_.in_avail(ec); | |||
| } | |||
| private: | |||
| /// Copy data into the internal buffer from the specified source buffer. | |||
| /// Returns the number of bytes copied. | |||
| template <typename ConstBufferSequence> | |||
| std::size_t copy(const ConstBufferSequence& buffers); | |||
| /// The next layer. | |||
| Stream next_layer_; | |||
| // The data in the buffer. | |||
| detail::buffered_stream_storage storage_; | |||
| }; | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #include "asio/impl/buffered_write_stream.hpp" | |||
| #endif // ASIO_BUFFERED_WRITE_STREAM_HPP | |||
| @@ -0,0 +1,25 @@ | |||
| // | |||
| // buffered_write_stream_fwd.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BUFFERED_WRITE_STREAM_FWD_HPP | |||
| #define ASIO_BUFFERED_WRITE_STREAM_FWD_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| namespace asio { | |||
| template <typename Stream> | |||
| class buffered_write_stream; | |||
| } // namespace asio | |||
| #endif // ASIO_BUFFERED_WRITE_STREAM_FWD_HPP | |||
| @@ -0,0 +1,481 @@ | |||
| // | |||
| // buffers_iterator.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_BUFFERS_ITERATOR_HPP | |||
| #define ASIO_BUFFERS_ITERATOR_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include <iterator> | |||
| #include "asio/buffer.hpp" | |||
| #include "asio/detail/assert.hpp" | |||
| #include "asio/detail/type_traits.hpp" | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| namespace detail | |||
| { | |||
| template <bool IsMutable> | |||
| struct buffers_iterator_types_helper; | |||
| template <> | |||
| struct buffers_iterator_types_helper<false> | |||
| { | |||
| typedef const_buffer buffer_type; | |||
| template <typename ByteType> | |||
| struct byte_type | |||
| { | |||
| typedef typename add_const<ByteType>::type type; | |||
| }; | |||
| }; | |||
| template <> | |||
| struct buffers_iterator_types_helper<true> | |||
| { | |||
| typedef mutable_buffer buffer_type; | |||
| template <typename ByteType> | |||
| struct byte_type | |||
| { | |||
| typedef ByteType type; | |||
| }; | |||
| }; | |||
| template <typename BufferSequence, typename ByteType> | |||
| struct buffers_iterator_types | |||
| { | |||
| enum | |||
| { | |||
| is_mutable = is_convertible< | |||
| typename BufferSequence::value_type, | |||
| mutable_buffer>::value | |||
| }; | |||
| typedef buffers_iterator_types_helper<is_mutable> helper; | |||
| typedef typename helper::buffer_type buffer_type; | |||
| typedef typename helper::template byte_type<ByteType>::type byte_type; | |||
| }; | |||
| } | |||
| /// A random access iterator over the bytes in a buffer sequence. | |||
| template <typename BufferSequence, typename ByteType = char> | |||
| class buffers_iterator | |||
| { | |||
| private: | |||
| typedef typename detail::buffers_iterator_types< | |||
| BufferSequence, ByteType>::buffer_type buffer_type; | |||
| public: | |||
| /// The type used for the distance between two iterators. | |||
| typedef std::ptrdiff_t difference_type; | |||
| /// The type of the value pointed to by the iterator. | |||
| typedef ByteType value_type; | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// The type of the result of applying operator->() to the iterator. | |||
| /** | |||
| * If the buffer sequence stores buffer objects that are convertible to | |||
| * mutable_buffer, this is a pointer to a non-const ByteType. Otherwise, a | |||
| * pointer to a const ByteType. | |||
| */ | |||
| typedef const_or_non_const_ByteType* pointer; | |||
| #else // defined(GENERATING_DOCUMENTATION) | |||
| typedef typename detail::buffers_iterator_types< | |||
| BufferSequence, ByteType>::byte_type* pointer; | |||
| #endif // defined(GENERATING_DOCUMENTATION) | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| /// The type of the result of applying operator*() to the iterator. | |||
| /** | |||
| * If the buffer sequence stores buffer objects that are convertible to | |||
| * mutable_buffer, this is a reference to a non-const ByteType. Otherwise, a | |||
| * reference to a const ByteType. | |||
| */ | |||
| typedef const_or_non_const_ByteType& reference; | |||
| #else // defined(GENERATING_DOCUMENTATION) | |||
| typedef typename detail::buffers_iterator_types< | |||
| BufferSequence, ByteType>::byte_type& reference; | |||
| #endif // defined(GENERATING_DOCUMENTATION) | |||
| /// The iterator category. | |||
| typedef std::random_access_iterator_tag iterator_category; | |||
| /// Default constructor. Creates an iterator in an undefined state. | |||
| buffers_iterator() | |||
| : current_buffer_(), | |||
| current_buffer_position_(0), | |||
| begin_(), | |||
| current_(), | |||
| end_(), | |||
| position_(0) | |||
| { | |||
| } | |||
| /// Construct an iterator representing the beginning of the buffers' data. | |||
| static buffers_iterator begin(const BufferSequence& buffers) | |||
| #if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 3) | |||
| __attribute__ ((__noinline__)) | |||
| #endif // defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 3) | |||
| { | |||
| buffers_iterator new_iter; | |||
| new_iter.begin_ = buffers.begin(); | |||
| new_iter.current_ = buffers.begin(); | |||
| new_iter.end_ = buffers.end(); | |||
| while (new_iter.current_ != new_iter.end_) | |||
| { | |||
| new_iter.current_buffer_ = *new_iter.current_; | |||
| if (new_iter.current_buffer_.size() > 0) | |||
| break; | |||
| ++new_iter.current_; | |||
| } | |||
| return new_iter; | |||
| } | |||
| /// Construct an iterator representing the end of the buffers' data. | |||
| static buffers_iterator end(const BufferSequence& buffers) | |||
| #if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 3) | |||
| __attribute__ ((__noinline__)) | |||
| #endif // defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 3) | |||
| { | |||
| buffers_iterator new_iter; | |||
| new_iter.begin_ = buffers.begin(); | |||
| new_iter.current_ = buffers.begin(); | |||
| new_iter.end_ = buffers.end(); | |||
| while (new_iter.current_ != new_iter.end_) | |||
| { | |||
| buffer_type buffer = *new_iter.current_; | |||
| new_iter.position_ += buffer.size(); | |||
| ++new_iter.current_; | |||
| } | |||
| return new_iter; | |||
| } | |||
| /// Dereference an iterator. | |||
| reference operator*() const | |||
| { | |||
| return dereference(); | |||
| } | |||
| /// Dereference an iterator. | |||
| pointer operator->() const | |||
| { | |||
| return &dereference(); | |||
| } | |||
| /// Access an individual element. | |||
| reference operator[](std::ptrdiff_t difference) const | |||
| { | |||
| buffers_iterator tmp(*this); | |||
| tmp.advance(difference); | |||
| return *tmp; | |||
| } | |||
| /// Increment operator (prefix). | |||
| buffers_iterator& operator++() | |||
| { | |||
| increment(); | |||
| return *this; | |||
| } | |||
| /// Increment operator (postfix). | |||
| buffers_iterator operator++(int) | |||
| { | |||
| buffers_iterator tmp(*this); | |||
| ++*this; | |||
| return tmp; | |||
| } | |||
| /// Decrement operator (prefix). | |||
| buffers_iterator& operator--() | |||
| { | |||
| decrement(); | |||
| return *this; | |||
| } | |||
| /// Decrement operator (postfix). | |||
| buffers_iterator operator--(int) | |||
| { | |||
| buffers_iterator tmp(*this); | |||
| --*this; | |||
| return tmp; | |||
| } | |||
| /// Addition operator. | |||
| buffers_iterator& operator+=(std::ptrdiff_t difference) | |||
| { | |||
| advance(difference); | |||
| return *this; | |||
| } | |||
| /// Subtraction operator. | |||
| buffers_iterator& operator-=(std::ptrdiff_t difference) | |||
| { | |||
| advance(-difference); | |||
| return *this; | |||
| } | |||
| /// Addition operator. | |||
| friend buffers_iterator operator+(const buffers_iterator& iter, | |||
| std::ptrdiff_t difference) | |||
| { | |||
| buffers_iterator tmp(iter); | |||
| tmp.advance(difference); | |||
| return tmp; | |||
| } | |||
| /// Addition operator. | |||
| friend buffers_iterator operator+(std::ptrdiff_t difference, | |||
| const buffers_iterator& iter) | |||
| { | |||
| buffers_iterator tmp(iter); | |||
| tmp.advance(difference); | |||
| return tmp; | |||
| } | |||
| /// Subtraction operator. | |||
| friend buffers_iterator operator-(const buffers_iterator& iter, | |||
| std::ptrdiff_t difference) | |||
| { | |||
| buffers_iterator tmp(iter); | |||
| tmp.advance(-difference); | |||
| return tmp; | |||
| } | |||
| /// Subtraction operator. | |||
| friend std::ptrdiff_t operator-(const buffers_iterator& a, | |||
| const buffers_iterator& b) | |||
| { | |||
| return b.distance_to(a); | |||
| } | |||
| /// Test two iterators for equality. | |||
| friend bool operator==(const buffers_iterator& a, const buffers_iterator& b) | |||
| { | |||
| return a.equal(b); | |||
| } | |||
| /// Test two iterators for inequality. | |||
| friend bool operator!=(const buffers_iterator& a, const buffers_iterator& b) | |||
| { | |||
| return !a.equal(b); | |||
| } | |||
| /// Compare two iterators. | |||
| friend bool operator<(const buffers_iterator& a, const buffers_iterator& b) | |||
| { | |||
| return a.distance_to(b) > 0; | |||
| } | |||
| /// Compare two iterators. | |||
| friend bool operator<=(const buffers_iterator& a, const buffers_iterator& b) | |||
| { | |||
| return !(b < a); | |||
| } | |||
| /// Compare two iterators. | |||
| friend bool operator>(const buffers_iterator& a, const buffers_iterator& b) | |||
| { | |||
| return b < a; | |||
| } | |||
| /// Compare two iterators. | |||
| friend bool operator>=(const buffers_iterator& a, const buffers_iterator& b) | |||
| { | |||
| return !(a < b); | |||
| } | |||
| private: | |||
| // Dereference the iterator. | |||
| reference dereference() const | |||
| { | |||
| return static_cast<pointer>( | |||
| current_buffer_.data())[current_buffer_position_]; | |||
| } | |||
| // Compare two iterators for equality. | |||
| bool equal(const buffers_iterator& other) const | |||
| { | |||
| return position_ == other.position_; | |||
| } | |||
| // Increment the iterator. | |||
| void increment() | |||
| { | |||
| ASIO_ASSERT(current_ != end_ && "iterator out of bounds"); | |||
| ++position_; | |||
| // Check if the increment can be satisfied by the current buffer. | |||
| ++current_buffer_position_; | |||
| if (current_buffer_position_ != current_buffer_.size()) | |||
| return; | |||
| // Find the next non-empty buffer. | |||
| ++current_; | |||
| current_buffer_position_ = 0; | |||
| while (current_ != end_) | |||
| { | |||
| current_buffer_ = *current_; | |||
| if (current_buffer_.size() > 0) | |||
| return; | |||
| ++current_; | |||
| } | |||
| } | |||
| // Decrement the iterator. | |||
| void decrement() | |||
| { | |||
| ASIO_ASSERT(position_ > 0 && "iterator out of bounds"); | |||
| --position_; | |||
| // Check if the decrement can be satisfied by the current buffer. | |||
| if (current_buffer_position_ != 0) | |||
| { | |||
| --current_buffer_position_; | |||
| return; | |||
| } | |||
| // Find the previous non-empty buffer. | |||
| typename BufferSequence::const_iterator iter = current_; | |||
| while (iter != begin_) | |||
| { | |||
| --iter; | |||
| buffer_type buffer = *iter; | |||
| std::size_t buffer_size = buffer.size(); | |||
| if (buffer_size > 0) | |||
| { | |||
| current_ = iter; | |||
| current_buffer_ = buffer; | |||
| current_buffer_position_ = buffer_size - 1; | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| // Advance the iterator by the specified distance. | |||
| void advance(std::ptrdiff_t n) | |||
| { | |||
| if (n > 0) | |||
| { | |||
| ASIO_ASSERT(current_ != end_ && "iterator out of bounds"); | |||
| for (;;) | |||
| { | |||
| std::ptrdiff_t current_buffer_balance | |||
| = current_buffer_.size() - current_buffer_position_; | |||
| // Check if the advance can be satisfied by the current buffer. | |||
| if (current_buffer_balance > n) | |||
| { | |||
| position_ += n; | |||
| current_buffer_position_ += n; | |||
| return; | |||
| } | |||
| // Update position. | |||
| n -= current_buffer_balance; | |||
| position_ += current_buffer_balance; | |||
| // Move to next buffer. If it is empty then it will be skipped on the | |||
| // next iteration of this loop. | |||
| if (++current_ == end_) | |||
| { | |||
| ASIO_ASSERT(n == 0 && "iterator out of bounds"); | |||
| current_buffer_ = buffer_type(); | |||
| current_buffer_position_ = 0; | |||
| return; | |||
| } | |||
| current_buffer_ = *current_; | |||
| current_buffer_position_ = 0; | |||
| } | |||
| } | |||
| else if (n < 0) | |||
| { | |||
| std::size_t abs_n = -n; | |||
| ASIO_ASSERT(position_ >= abs_n && "iterator out of bounds"); | |||
| for (;;) | |||
| { | |||
| // Check if the advance can be satisfied by the current buffer. | |||
| if (current_buffer_position_ >= abs_n) | |||
| { | |||
| position_ -= abs_n; | |||
| current_buffer_position_ -= abs_n; | |||
| return; | |||
| } | |||
| // Update position. | |||
| abs_n -= current_buffer_position_; | |||
| position_ -= current_buffer_position_; | |||
| // Check if we've reached the beginning of the buffers. | |||
| if (current_ == begin_) | |||
| { | |||
| ASIO_ASSERT(abs_n == 0 && "iterator out of bounds"); | |||
| current_buffer_position_ = 0; | |||
| return; | |||
| } | |||
| // Find the previous non-empty buffer. | |||
| typename BufferSequence::const_iterator iter = current_; | |||
| while (iter != begin_) | |||
| { | |||
| --iter; | |||
| buffer_type buffer = *iter; | |||
| std::size_t buffer_size = buffer.size(); | |||
| if (buffer_size > 0) | |||
| { | |||
| current_ = iter; | |||
| current_buffer_ = buffer; | |||
| current_buffer_position_ = buffer_size; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // Determine the distance between two iterators. | |||
| std::ptrdiff_t distance_to(const buffers_iterator& other) const | |||
| { | |||
| return other.position_ - position_; | |||
| } | |||
| buffer_type current_buffer_; | |||
| std::size_t current_buffer_position_; | |||
| typename BufferSequence::const_iterator begin_; | |||
| typename BufferSequence::const_iterator current_; | |||
| typename BufferSequence::const_iterator end_; | |||
| std::size_t position_; | |||
| }; | |||
| /// Construct an iterator representing the beginning of the buffers' data. | |||
| template <typename BufferSequence> | |||
| inline buffers_iterator<BufferSequence> buffers_begin( | |||
| const BufferSequence& buffers) | |||
| { | |||
| return buffers_iterator<BufferSequence>::begin(buffers); | |||
| } | |||
| /// Construct an iterator representing the end of the buffers' data. | |||
| template <typename BufferSequence> | |||
| inline buffers_iterator<BufferSequence> buffers_end( | |||
| const BufferSequence& buffers) | |||
| { | |||
| return buffers_iterator<BufferSequence>::end(buffers); | |||
| } | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_BUFFERS_ITERATOR_HPP | |||
| @@ -0,0 +1,218 @@ | |||
| // | |||
| // completion_condition.hpp | |||
| // ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| // | |||
| // Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |||
| // | |||
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| // | |||
| #ifndef ASIO_COMPLETION_CONDITION_HPP | |||
| #define ASIO_COMPLETION_CONDITION_HPP | |||
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| # pragma once | |||
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |||
| #include "asio/detail/config.hpp" | |||
| #include <cstddef> | |||
| #include "asio/detail/push_options.hpp" | |||
| namespace asio { | |||
| namespace detail { | |||
| // The default maximum number of bytes to transfer in a single operation. | |||
| enum default_max_transfer_size_t { default_max_transfer_size = 65536 }; | |||
| // Adapt result of old-style completion conditions (which had a bool result | |||
| // where true indicated that the operation was complete). | |||
| inline std::size_t adapt_completion_condition_result(bool result) | |||
| { | |||
| return result ? 0 : default_max_transfer_size; | |||
| } | |||
| // Adapt result of current completion conditions (which have a size_t result | |||
| // where 0 means the operation is complete, and otherwise the result is the | |||
| // maximum number of bytes to transfer on the next underlying operation). | |||
| inline std::size_t adapt_completion_condition_result(std::size_t result) | |||
| { | |||
| return result; | |||
| } | |||
| class transfer_all_t | |||
| { | |||
| public: | |||
| typedef std::size_t result_type; | |||
| template <typename Error> | |||
| std::size_t operator()(const Error& err, std::size_t) | |||
| { | |||
| return !!err ? 0 : default_max_transfer_size; | |||
| } | |||
| }; | |||
| class transfer_at_least_t | |||
| { | |||
| public: | |||
| typedef std::size_t result_type; | |||
| explicit transfer_at_least_t(std::size_t minimum) | |||
| : minimum_(minimum) | |||
| { | |||
| } | |||
| template <typename Error> | |||
| std::size_t operator()(const Error& err, std::size_t bytes_transferred) | |||
| { | |||
| return (!!err || bytes_transferred >= minimum_) | |||
| ? 0 : default_max_transfer_size; | |||
| } | |||
| private: | |||
| std::size_t minimum_; | |||
| }; | |||
| class transfer_exactly_t | |||
| { | |||
| public: | |||
| typedef std::size_t result_type; | |||
| explicit transfer_exactly_t(std::size_t size) | |||
| : size_(size) | |||
| { | |||
| } | |||
| template <typename Error> | |||
| std::size_t operator()(const Error& err, std::size_t bytes_transferred) | |||
| { | |||
| return (!!err || bytes_transferred >= size_) ? 0 : | |||
| (size_ - bytes_transferred < default_max_transfer_size | |||
| ? size_ - bytes_transferred : std::size_t(default_max_transfer_size)); | |||
| } | |||
| private: | |||
| std::size_t size_; | |||
| }; | |||
| } // namespace detail | |||
| /** | |||
| * @defgroup completion_condition Completion Condition Function Objects | |||
| * | |||
| * Function objects used for determining when a read or write operation should | |||
| * complete. | |||
| */ | |||
| /*@{*/ | |||
| /// Return a completion condition function object that indicates that a read or | |||
| /// write operation should continue until all of the data has been transferred, | |||
| /// or until an error occurs. | |||
| /** | |||
| * This function is used to create an object, of unspecified type, that meets | |||
| * CompletionCondition requirements. | |||
| * | |||
| * @par Example | |||
| * Reading until a buffer is full: | |||
| * @code | |||
| * boost::array<char, 128> buf; | |||
| * asio::error_code ec; | |||
| * std::size_t n = asio::read( | |||
| * sock, asio::buffer(buf), | |||
| * asio::transfer_all(), ec); | |||
| * if (ec) | |||
| * { | |||
| * // An error occurred. | |||
| * } | |||
| * else | |||
| * { | |||
| * // n == 128 | |||
| * } | |||
| * @endcode | |||
| */ | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| unspecified transfer_all(); | |||
| #else | |||
| inline detail::transfer_all_t transfer_all() | |||
| { | |||
| return detail::transfer_all_t(); | |||
| } | |||
| #endif | |||
| /// Return a completion condition function object that indicates that a read or | |||
| /// write operation should continue until a minimum number of bytes has been | |||
| /// transferred, or until an error occurs. | |||
| /** | |||
| * This function is used to create an object, of unspecified type, that meets | |||
| * CompletionCondition requirements. | |||
| * | |||
| * @par Example | |||
| * Reading until a buffer is full or contains at least 64 bytes: | |||
| * @code | |||
| * boost::array<char, 128> buf; | |||
| * asio::error_code ec; | |||
| * std::size_t n = asio::read( | |||
| * sock, asio::buffer(buf), | |||
| * asio::transfer_at_least(64), ec); | |||
| * if (ec) | |||
| * { | |||
| * // An error occurred. | |||
| * } | |||
| * else | |||
| * { | |||
| * // n >= 64 && n <= 128 | |||
| * } | |||
| * @endcode | |||
| */ | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| unspecified transfer_at_least(std::size_t minimum); | |||
| #else | |||
| inline detail::transfer_at_least_t transfer_at_least(std::size_t minimum) | |||
| { | |||
| return detail::transfer_at_least_t(minimum); | |||
| } | |||
| #endif | |||
| /// Return a completion condition function object that indicates that a read or | |||
| /// write operation should continue until an exact number of bytes has been | |||
| /// transferred, or until an error occurs. | |||
| /** | |||
| * This function is used to create an object, of unspecified type, that meets | |||
| * CompletionCondition requirements. | |||
| * | |||
| * @par Example | |||
| * Reading until a buffer is full or contains exactly 64 bytes: | |||
| * @code | |||
| * boost::array<char, 128> buf; | |||
| * asio::error_code ec; | |||
| * std::size_t n = asio::read( | |||
| * sock, asio::buffer(buf), | |||
| * asio::transfer_exactly(64), ec); | |||
| * if (ec) | |||
| * { | |||
| * // An error occurred. | |||
| * } | |||
| * else | |||
| * { | |||
| * // n == 64 | |||
| * } | |||
| * @endcode | |||
| */ | |||
| #if defined(GENERATING_DOCUMENTATION) | |||
| unspecified transfer_exactly(std::size_t size); | |||
| #else | |||
| inline detail::transfer_exactly_t transfer_exactly(std::size_t size) | |||
| { | |||
| return detail::transfer_exactly_t(size); | |||
| } | |||
| #endif | |||
| /*@}*/ | |||
| } // namespace asio | |||
| #include "asio/detail/pop_options.hpp" | |||
| #endif // ASIO_COMPLETION_CONDITION_HPP | |||