diff options
Diffstat (limited to 'utils/hwstub/include/hwstub_net.hpp')
-rw-r--r-- | utils/hwstub/include/hwstub_net.hpp | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/utils/hwstub/include/hwstub_net.hpp b/utils/hwstub/include/hwstub_net.hpp new file mode 100644 index 0000000000..2cf6e07ccb --- /dev/null +++ b/utils/hwstub/include/hwstub_net.hpp | |||
@@ -0,0 +1,334 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2016 by Amaury Pouly | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #ifndef __HWSTUB_NET_HPP__ | ||
22 | #define __HWSTUB_NET_HPP__ | ||
23 | |||
24 | #include "hwstub.hpp" | ||
25 | #include <map> | ||
26 | #include <thread> | ||
27 | #include <future> | ||
28 | #include <list> | ||
29 | |||
30 | namespace hwstub { | ||
31 | namespace net { | ||
32 | |||
33 | /** Net context | ||
34 | * | ||
35 | * A socket context provides access to another context through a network. This | ||
36 | * is particularly useful to have another program create a USB context and provide | ||
37 | * access to it via some network. The two most useful types of network are TCP | ||
38 | * and Unix domains */ | ||
39 | class context : public hwstub::context | ||
40 | { | ||
41 | friend class device; | ||
42 | friend class handle; | ||
43 | protected: | ||
44 | context(); | ||
45 | public: | ||
46 | virtual ~context(); | ||
47 | /** Create a socket context with an existing file descriptor. Note that the | ||
48 | * file descriptor will be closed when the context will be destroyed. */ | ||
49 | static std::shared_ptr<context> create_socket(int socket_fd); | ||
50 | /** Create a TCP socket context with a domain name and a port. If port is empty, | ||
51 | * a default port is used. */ | ||
52 | static std::shared_ptr<context> create_tcp(const std::string& domain, | ||
53 | const std::string& port, std::string *error = nullptr); | ||
54 | /** Create a UNIX socket context with a file system path (see man for details) */ | ||
55 | static std::shared_ptr<context> create_unix(const std::string& path, | ||
56 | std::string *error = nullptr); | ||
57 | /** Create a UNIX socket context with an abstract name (see man for details) */ | ||
58 | static std::shared_ptr<context> create_unix_abstract(const std::string& path, | ||
59 | std::string *error = nullptr); | ||
60 | /** Useful functions for network byte order conversion */ | ||
61 | uint32_t to_net_order(uint32_t u); | ||
62 | uint32_t from_net_order(uint32_t u); | ||
63 | |||
64 | /** Default parameters */ | ||
65 | static std::string default_unix_path(); | ||
66 | static std::string default_tcp_domain(); | ||
67 | static std::string default_tcp_port(); | ||
68 | |||
69 | protected: | ||
70 | /** Send a message to the server. Context will always serialize calls to send() | ||
71 | * so there is no need to worry about concurrency issues. */ | ||
72 | virtual error send(void *buffer, size_t& sz) = 0; | ||
73 | /** Receive a message from the server, sz is updated with the received size. | ||
74 | * Context will always serialize calls to recv() so there is no need to | ||
75 | * worry about concurrency issues. */ | ||
76 | virtual error recv(void *buffer, size_t& sz) = 0; | ||
77 | /** Perform a standard command: send a header with optional data and wait for | ||
78 | * an answer. In case of an underlying network error, the corresponding error | ||
79 | * code will be reported. If the server responds correctly, the argument array | ||
80 | * is overwritten with the servers's response. If the requests has been NACK'ed | ||
81 | * the error code will be parsed and returned as a standard error code (see details below) | ||
82 | * (note that the original error code can still be found in args[0]). No data | ||
83 | * is transmitted in case of NACK. | ||
84 | * If the server ACKs the request, this function will also perform reception of | ||
85 | * the data. In recv_data is not NULL, the receive data will be put there and the | ||
86 | * size will be written in in_size. There are two cases: either *recv_data is NULL | ||
87 | * and the function will allocate the memory based on much data is sent by the server. | ||
88 | * Or *recv_data is not NULL, in which case the function NOT allocate memory | ||
89 | * and put the data at *recv_data; in this case, *recv_size should be the set | ||
90 | * to the size of the buffer and will be updated to the received size. If the | ||
91 | * server sents more data than the buffer size, OVERFLOW will be returned. | ||
92 | * If no data was received but recv_data is not null, *recv_size will be set to | ||
93 | * zero. It is the caller's responsability to delete *recv_data. Note that if | ||
94 | * server sends data but recv_data is null, the data will still be received and | ||
95 | * thrown away. | ||
96 | * This function takes care of network byte order for cmd and arguments | ||
97 | * but not for data. */ | ||
98 | error send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *send_data, | ||
99 | size_t send_size, uint8_t **recv_data, size_t *recv_size); | ||
100 | /** Ask the context to stop any communication with the server and do a clean | ||
101 | * shutdown if possible. This is a blocking call. When this function returns, | ||
102 | * there will no more calls to the underlying communication functions. | ||
103 | * This function should be called in the destructor to prevent the context from | ||
104 | * calling children functions after the object has been deconstructed. */ | ||
105 | void stop_context(); | ||
106 | |||
107 | /** Perform delayed init (aka HELLO stage), do nothing is not needed */ | ||
108 | void delayed_init(); | ||
109 | /* NOTE ctx_dev_t = uint32_t (device id) */ | ||
110 | uint32_t from_ctx_dev(ctx_dev_t dev); | ||
111 | ctx_dev_t to_ctx_dev(uint32_t dev); | ||
112 | virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr); | ||
113 | virtual void destroy_device_list(void *ptr); | ||
114 | virtual error create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev); | ||
115 | virtual bool match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev); | ||
116 | |||
117 | enum class state | ||
118 | { | ||
119 | HELLO, /* client is initialising, server has not been contacted yet */ | ||
120 | IDLE, /* not doing anything */ | ||
121 | DEAD, /* died on unrecoverable error */ | ||
122 | }; | ||
123 | |||
124 | state m_state; /* client state */ | ||
125 | error m_error; /* error state for DEAD */ | ||
126 | }; | ||
127 | |||
128 | /** Socket based net context | ||
129 | * | ||
130 | * Don't use this class directly, use context::create_* calls. This class | ||
131 | * provides send()/recv() for any socket based network. */ | ||
132 | class socket_context : public context | ||
133 | { | ||
134 | friend class context; | ||
135 | protected: | ||
136 | socket_context(int socket_fd); | ||
137 | public: | ||
138 | virtual ~socket_context(); | ||
139 | /** set operation timeout */ | ||
140 | void set_timeout(std::chrono::milliseconds ms); | ||
141 | |||
142 | protected: | ||
143 | virtual error send(void *buffer, size_t& sz); | ||
144 | virtual error recv(void *buffer, size_t& sz); | ||
145 | |||
146 | int m_socketfd; /* socket file descriptor */ | ||
147 | }; | ||
148 | |||
149 | |||
150 | /** Net device | ||
151 | * | ||
152 | * Device accessed through a network */ | ||
153 | class device : public hwstub::device | ||
154 | { | ||
155 | friend class context; /* for ctor */ | ||
156 | protected: | ||
157 | device(std::shared_ptr<hwstub::context> ctx, uint32_t devid); | ||
158 | public: | ||
159 | virtual ~device(); | ||
160 | |||
161 | protected: | ||
162 | /** Return device ID */ | ||
163 | uint32_t device_id(); | ||
164 | virtual error open_dev(std::shared_ptr<hwstub::handle>& handle); | ||
165 | virtual bool has_multiple_open() const; | ||
166 | |||
167 | int32_t m_device_id; /* device id */ | ||
168 | }; | ||
169 | |||
170 | /** Net handle | ||
171 | * | ||
172 | * Handle used to talk to a distant device. */ | ||
173 | class handle : public hwstub::handle | ||
174 | { | ||
175 | friend class device; | ||
176 | protected: | ||
177 | handle(std::shared_ptr<hwstub::device> dev, uint32_t hid); | ||
178 | public: | ||
179 | virtual ~handle(); | ||
180 | |||
181 | protected: | ||
182 | virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic); | ||
183 | virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic); | ||
184 | virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz); | ||
185 | virtual error get_dev_log(void *buf, size_t& buf_sz); | ||
186 | virtual error exec_dev(uint32_t addr, uint16_t flags); | ||
187 | virtual error status() const; | ||
188 | virtual size_t get_buffer_size(); | ||
189 | |||
190 | uint32_t m_handle_id; /* handle id */ | ||
191 | }; | ||
192 | |||
193 | /** Net server | ||
194 | * | ||
195 | * A server that forwards requests from net clients to a context */ | ||
196 | class server | ||
197 | { | ||
198 | protected: | ||
199 | server(std::shared_ptr<hwstub::context> contex); | ||
200 | public: | ||
201 | virtual ~server(); | ||
202 | |||
203 | /** Create a socket server with an existing file descriptor. Note that the | ||
204 | * file descriptor will be closed when the context will be destroyed. */ | ||
205 | static std::shared_ptr<server> create_socket(std::shared_ptr<hwstub::context> contex, | ||
206 | int socket_fd); | ||
207 | /** Create a TCP socket server with a domain name and a port. If port is empty, | ||
208 | * a default port is used. */ | ||
209 | static std::shared_ptr<server> create_tcp(std::shared_ptr<hwstub::context> contex, | ||
210 | const std::string& domain, const std::string& port, std::string *error = nullptr); | ||
211 | /** Create a UNIX socket server with a file system path (see man for details) */ | ||
212 | static std::shared_ptr<server> create_unix(std::shared_ptr<hwstub::context> contex, | ||
213 | const std::string& path, std::string *error = nullptr); | ||
214 | /** Create a UNIX socket server with an abstract name (see man for details) */ | ||
215 | static std::shared_ptr<server> create_unix_abstract( | ||
216 | std::shared_ptr<hwstub::context> contex, const std::string& path, | ||
217 | std::string *error = nullptr); | ||
218 | /** Useful functions for network byte order conversion */ | ||
219 | uint32_t to_net_order(uint32_t u); | ||
220 | uint32_t from_net_order(uint32_t u); | ||
221 | |||
222 | /** Set/clear debug output for this context */ | ||
223 | void set_debug(std::ostream& os); | ||
224 | inline void clear_debug() { set_debug(cnull); } | ||
225 | /** Get debug output for this context */ | ||
226 | std::ostream& debug(); | ||
227 | protected: | ||
228 | struct client_state; | ||
229 | /** Opaque client type */ | ||
230 | typedef void* srv_client_t; | ||
231 | /** The client discovery implementation must call this function when a new | ||
232 | * client wants to talk to the server. If the server is unhappy with the | ||
233 | * request, it will immediately call terminate_client() */ | ||
234 | void client_arrived(srv_client_t client); | ||
235 | /** The client discovery implementation can notify asychronously about a client | ||
236 | * that left. Note that the implementation does not need to provide a mechanism, | ||
237 | * but should in this case return CLIENT_DISCONNECTED when the server performs | ||
238 | * a send() or recv() on a disconnected client. The server will always call | ||
239 | * after receiving client_left() but since this call is asychronous, the | ||
240 | * implementation must be prepared to deal with extra send()/recv() in the mean | ||
241 | * time. */ | ||
242 | void client_left(srv_client_t client); | ||
243 | /** The client discovery implementation can ask the server to stop all client | ||
244 | * threads. This is a blocking call. When this function returns, there will no | ||
245 | * more calls to the underlying communication functions. Note that the server | ||
246 | * will normally call terminate_client() on each active client at this point. | ||
247 | * This function should be called in the destructor to prevent the server from | ||
248 | * calling children functions after the object has been deconstructed. */ | ||
249 | void stop_server(); | ||
250 | /** Notify that the connection to a client is now finished. After this call, no | ||
251 | * more send()/recv() will be made to the client and the associated data will | ||
252 | * be freed. After this call, the implementation is not allowed to call client_left() | ||
253 | * for this client (assuming it did not previously). The implementation should close | ||
254 | * the communication channel at this point and free any associated data. */ | ||
255 | virtual void terminate_client(srv_client_t client) = 0; | ||
256 | /** Send a message to the client. Server will always serialize calls to send() | ||
257 | * for a given client so there is no need to worry about concurrency issues. */ | ||
258 | virtual error send(srv_client_t client, void *buffer, size_t& sz) = 0; | ||
259 | /** Receive a message from the client, sz is updated with the received size. | ||
260 | * Server will always serialize calls to recv() for a given client so there | ||
261 | * is no need to worry about concurrency issues. See comment about client_left(). */ | ||
262 | virtual error recv(srv_client_t client, void *buffer, size_t& sz) = 0; | ||
263 | /** handle command: cmd and arguments are in host order, the function should | ||
264 | * either return an error (command will be NACKed) or must fill the arguments | ||
265 | * and data for the answer. Note that the data is still in network byte order. | ||
266 | * If the funtion wants to send data back, it must set *send_data to a valid | ||
267 | * pointer, this pointer will be freed after the data is sent back. */ | ||
268 | error handle_cmd(client_state *state, uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], | ||
269 | uint8_t *recv_data, size_t recv_size, uint8_t*& send_data, size_t& send_size); | ||
270 | |||
271 | /* complete state of a client */ | ||
272 | struct client_state | ||
273 | { | ||
274 | client_state(srv_client_t cl, std::future<void>&& f); | ||
275 | srv_client_t client; /* client */ | ||
276 | std::future<void> future; /* thread (see .cpp for explaination) */ | ||
277 | volatile bool exit; /* exit flag */ | ||
278 | uint32_t next_dev_id; /* next device ID */ | ||
279 | uint32_t next_handle_id; /* next handle ID */ | ||
280 | /* dev ID <-> hwstub dev map */ | ||
281 | std::map<uint32_t, std::shared_ptr<hwstub::device>> dev_map; | ||
282 | /* handle ID -> hwstub handle map */ | ||
283 | std::map<uint32_t, std::shared_ptr<hwstub::handle>> handle_map; | ||
284 | }; | ||
285 | |||
286 | /** Client thread */ | ||
287 | static void client_thread2(server *s, client_state *cs); | ||
288 | void client_thread(client_state *cs); | ||
289 | |||
290 | std::shared_ptr<hwstub::context> m_context; /* context to perform operation */ | ||
291 | std::list<client_state> m_client; /* client list */ | ||
292 | std::recursive_mutex m_mutex; /* server mutex */ | ||
293 | std::ostream *m_debug; /* debug stream */ | ||
294 | }; | ||
295 | |||
296 | /** Socket based net server | ||
297 | * | ||
298 | */ | ||
299 | class socket_server : public server | ||
300 | { | ||
301 | protected: | ||
302 | socket_server(std::shared_ptr<hwstub::context> contex, int socket_fd); | ||
303 | public: | ||
304 | virtual ~socket_server(); | ||
305 | /** create a server */ | ||
306 | static std::shared_ptr<server> create(std::shared_ptr<hwstub::context> contex, | ||
307 | int socket_fd); | ||
308 | /** set operation timeout */ | ||
309 | void set_timeout(std::chrono::milliseconds ms); | ||
310 | |||
311 | protected: | ||
312 | virtual void terminate_client(srv_client_t client); | ||
313 | virtual error send(srv_client_t client, void *buffer, size_t& sz); | ||
314 | virtual error recv(srv_client_t, void *buffer, size_t& sz); | ||
315 | |||
316 | /* NOTE srv_client_t = int (client file descriptor) */ | ||
317 | int from_srv_client(srv_client_t cli); | ||
318 | srv_client_t to_srv_client(int fd); | ||
319 | |||
320 | /** Discovery thread */ | ||
321 | static void discovery_thread1(socket_server *s); | ||
322 | void discovery_thread(); | ||
323 | |||
324 | static const int LISTEN_QUEUE_SIZE = 5; | ||
325 | struct timeval m_timeout; /* operations timeout */ | ||
326 | int m_socketfd; /* socket file descriptor */ | ||
327 | std::thread m_discovery_thread; /* thread handling client discovery */ | ||
328 | volatile bool m_discovery_exit; /* exit flag */ | ||
329 | }; | ||
330 | |||
331 | } // namespace net | ||
332 | } // namespace hwstub | ||
333 | |||
334 | #endif /* __HWSTUB_NET_HPP__ */ | ||