1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 2023, The LegacyClonk Team and contributors
5 *
6 * Distributed under the terms of the ISC license; see accompanying file
7 * "COPYING" for details.
8 *
9 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10 * See accompanying file "TRADEMARK" for details.
11 *
12 * To redistribute this file separately, substitute the full license texts
13 * for the above references.
14 */
15
16#pragma once
17
18#include "C4Attributes.h"
19#include "C4Coroutine.h"
20#include "C4NetIO.h"
21#include "StdSync.h"
22
23#include <expected>
24#include <memory>
25#include <variant>
26
27#include <curl/curlver.h>
28
29#if CURL_AT_LEAST_VERSION(8, 11, 0)
30using CURLM = void;
31using CURL = void;
32#else
33using CURLM = struct Curl_multi;
34using CURL = struct Curl_easy;
35#endif
36
37using curl_socket_t = SOCKET;
38using CURLU = struct Curl_URL;
39
40template<>
41struct std::hash<std::pair<CURL *, SOCKET>>
42{
43 std::size_t operator()(const std::pair<CURL *, SOCKET> pair) const
44 {
45 return std::hash<CURL *>{}(pair.first) ^ std::hash<SOCKET>{}(pair.second);
46 }
47};
48
49class C4CurlSystem
50{
51private:
52 struct CURLMultiDeleter
53 {
54 void operator()(CURLM *multi);
55 };
56
57 struct CURLEasyDeleter
58 {
59 void operator()(CURL *easy);
60 };
61
62 using TaskType = C4Task::Task<void, C4Task::TaskTraitsHotWaitOnDestruction, C4Task::PromiseTraitsTerminateOnException>;
63
64#ifdef _WIN32
65 using WaitReturnType = bool;
66#else
67 using WaitReturnType = std::vector<pollfd>;
68#endif
69
70public:
71 using MultiHandle = std::unique_ptr<CURLM, CURLMultiDeleter>;
72 using EasyHandle = std::unique_ptr<CURL, CURLEasyDeleter>;
73
74 class AddedEasyHandle
75 {
76 public:
77 AddedEasyHandle(C4CurlSystem &system, EasyHandle &&easyHandle);
78 ~AddedEasyHandle();
79
80 AddedEasyHandle(const AddedEasyHandle &) = delete;
81 AddedEasyHandle &operator=(const AddedEasyHandle &) = delete;
82
83 AddedEasyHandle(AddedEasyHandle &&) = default;
84 AddedEasyHandle &operator=(AddedEasyHandle &&) = default;
85
86 public:
87 auto get() const { return easyHandle.get(); }
88
89 private:
90 std::reference_wrapper<C4CurlSystem> system;
91 EasyHandle easyHandle;
92 };
93
94 class Exception : public std::runtime_error
95 {
96 public:
97 using runtime_error::runtime_error;
98 };
99
100private:
101 class GlobalInit
102 {
103 public:
104 GlobalInit();
105 ~GlobalInit();
106 };
107
108 class MultiHandleWithCallbacks
109 {
110 public:
111 using SocketFunction = int(CURL *, curl_socket_t, int, void *) noexcept;
112 using TimerFunction = int(CURLM *, long, void *) noexcept;
113
114 public:
115 MultiHandleWithCallbacks(MultiHandle multiHandle, C4CurlSystem &system, SocketFunction *socketFunction, TimerFunction *timerFunction);
116 ~MultiHandleWithCallbacks();
117
118 public:
119 auto get() const { return multiHandle.get(); }
120 explicit operator bool() const noexcept { return !!multiHandle; }
121
122 private:
123 MultiHandle multiHandle;
124 };
125
126 class Awaiter : public C4Task::CancellableAwaiter<Awaiter>
127 {
128 public:
129 Awaiter(C4CurlSystem &system, EasyHandle &&easyHandle);
130 ~Awaiter() = default;
131
132 public:
133 void SetResult(C4NetIO::addr_t &&result);
134 void SetErrorMessage(const char *message);
135
136 constexpr bool await_ready() const noexcept { return false; }
137
138 template<typename T>
139 void await_suspend(const std::coroutine_handle<T> handle)
140 {
141 coroutineHandle.store(handle, std::memory_order_release);
142
143 SetCancellablePromise(handle);
144
145 easyHandle.emplace<1>(args: system.AddHandle(easyHandle: std::move(std::get<0>(v&: easyHandle))));
146 }
147
148 C4NetIO::addr_t await_resume();
149
150 void SetupCancellation(C4Task::CancellablePromise *promise);
151 void Resume();
152
153 private:
154 C4CurlSystem &system;
155 std::variant<EasyHandle, AddedEasyHandle> easyHandle;
156 std::atomic<std::coroutine_handle<>> coroutineHandle;
157 std::mutex resultMutex;
158 std::expected<C4NetIO::addr_t, std::string> result;
159 std::atomic_bool cancelled{false};
160 };
161
162public:
163 C4CurlSystem();
164 ~C4CurlSystem() = default;
165
166 C4CurlSystem(const C4CurlSystem &) = delete;
167 C4CurlSystem &operator=(const C4CurlSystem &) = delete;
168
169 C4CurlSystem(C4CurlSystem &&) = delete;
170 C4CurlSystem &operator=(C4CurlSystem &&) = delete;
171
172public:
173 Awaiter WaitForEasyAsync(EasyHandle &&easyHandle)
174 {
175 return {*this, std::move(easyHandle)};
176 }
177
178 AddedEasyHandle AddHandle(EasyHandle &&easyHandle);
179 void RemoveHandle(CURL *const handle);
180
181private:
182 TaskType Execute();
183 C4Task::Cold<WaitReturnType> Wait();
184 void ProcessMessages();
185 void CancelWait();
186
187 std::unordered_map<std::pair<CURL *, SOCKET>, int> GetSocketMapCopy()
188 {
189 const std::lock_guard lock{socketMapMutex};
190 return sockets;
191 }
192
193 static int SocketFunction(CURL *curl, curl_socket_t s, int what, void *userData) noexcept;
194 static int TimerFunction(CURLM *, long timeout, void *userData) noexcept;
195
196private:
197 std::atomic_uint32_t timeout{StdSync::Infinite};
198 [[NO_UNIQUE_ADDRESS]] GlobalInit globalInit;
199
200 MultiHandleWithCallbacks multiHandle;
201
202#ifdef _WIN32
203 CStdEvent event;
204#endif
205
206 TaskType multiTask;
207
208 std::atomic<C4Task::CancellablePromise *> wait{nullptr};
209
210 std::mutex socketMapMutex;
211 std::unordered_map<std::pair<CURL *, SOCKET>, int> sockets;
212};
213