diff options
Diffstat (limited to 'utils/hwstub/lib/hwstub_uri.cpp')
-rw-r--r-- | utils/hwstub/lib/hwstub_uri.cpp | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/utils/hwstub/lib/hwstub_uri.cpp b/utils/hwstub/lib/hwstub_uri.cpp new file mode 100644 index 0000000000..e2f252f3dc --- /dev/null +++ b/utils/hwstub/lib/hwstub_uri.cpp | |||
@@ -0,0 +1,332 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2015 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 | #include "hwstub_uri.hpp" | ||
22 | #include "hwstub_usb.hpp" | ||
23 | #include "hwstub_virtual.hpp" | ||
24 | #include "hwstub_net.hpp" | ||
25 | #include <cctype> | ||
26 | |||
27 | namespace hwstub { | ||
28 | namespace uri { | ||
29 | |||
30 | void print_usage(FILE *f, bool client, bool server) | ||
31 | { | ||
32 | if(client && server) | ||
33 | fprintf(f, "A context/server"); | ||
34 | else if(client) | ||
35 | fprintf(f, "A context"); | ||
36 | else | ||
37 | fprintf(f, "A server"); | ||
38 | fprintf(f, " URI has the following format:\n"); | ||
39 | fprintf(f, " scheme:[//domain[:port]][/]\n"); | ||
40 | fprintf(f, "The scheme is mandatory and specifies the type of context to create.\n"); | ||
41 | fprintf(f, "The following scheme are recognized:\n"); | ||
42 | if(client) | ||
43 | fprintf(f, " usb USB context (using libusb)\n"); | ||
44 | fprintf(f, " tcp TCP context\n"); | ||
45 | fprintf(f, " unix Local unix domain context\n"); | ||
46 | if(client) | ||
47 | fprintf(f, " virt A virtual context (testing and debugging mostly)\n"); | ||
48 | fprintf(f, " default Default choice made by the library\n"); | ||
49 | if(client) | ||
50 | { | ||
51 | fprintf(f, "When creating a USB context, the domain and port must be empty:\n"); | ||
52 | fprintf(f, " usb:\n"); | ||
53 | } | ||
54 | fprintf(f, "When creating a TCP context, the domain and port are the usual TCP parameters:\n"); | ||
55 | fprintf(f, " tcp://localhost\n"); | ||
56 | fprintf(f, " tcp://localhost:6666\n"); | ||
57 | fprintf(f, "The port is optional, in which the default port %s is used.\n", hwstub::net::context::default_tcp_port().c_str()); | ||
58 | fprintf(f, "When creating a unix domain context, the port must be empty and there are two type of unix domains.\n"); | ||
59 | fprintf(f, "Normal domains are specified by filesystem paths. Abstract domains (Linux-only) can be arbitrary strings,\n"); | ||
60 | fprintf(f, "in which case the domain must start with a '#':\n"); | ||
61 | fprintf(f, " unix:///path/to/socket\n"); | ||
62 | fprintf(f, " unix://#hwstub\n"); | ||
63 | if(client) | ||
64 | { | ||
65 | fprintf(f, "When creating a virtual context, the domain must contain a specification of the devices.\n"); | ||
66 | fprintf(f, "The device list is of the form type(param);type(param);... where the only supported type\n"); | ||
67 | fprintf(f, "at the moment is 'dummy' with a single parameter which is the device name:\n"); | ||
68 | fprintf(f, " virt://dummy(Device A);dummy(Device B);dummy(Super device C)\n"); | ||
69 | } | ||
70 | if(server && client) | ||
71 | fprintf(f, "Note that usb and virt schemes are not supported for server URIs.\n"); | ||
72 | } | ||
73 | |||
74 | uri::uri(const std::string& uri) | ||
75 | :m_uri(uri), m_valid(false) | ||
76 | { | ||
77 | parse(); | ||
78 | } | ||
79 | |||
80 | bool uri::validate_scheme() | ||
81 | { | ||
82 | /* scheme must be nonempty */ | ||
83 | if(m_scheme.empty()) | ||
84 | { | ||
85 | m_error = "empty scheme"; | ||
86 | return false; | ||
87 | } | ||
88 | /* and contain alphanumeric characters or '+', '-' or '.' */ | ||
89 | for(auto c : m_scheme) | ||
90 | if(!isalnum(c) && c != '+' && c != '-' && c != '.') | ||
91 | { | ||
92 | m_error = std::string("invalid character '") + c + "' in scheme"; | ||
93 | return false; | ||
94 | } | ||
95 | return true; | ||
96 | } | ||
97 | |||
98 | bool uri::validate_domain() | ||
99 | { | ||
100 | return true; | ||
101 | } | ||
102 | |||
103 | bool uri::validate_port() | ||
104 | { | ||
105 | return true; | ||
106 | } | ||
107 | |||
108 | void uri::parse() | ||
109 | { | ||
110 | std::string str = m_uri; | ||
111 | /* try to find the first ':' */ | ||
112 | size_t scheme_colon = str.find(':'); | ||
113 | if(scheme_colon == std::string::npos) | ||
114 | { | ||
115 | m_error = "URI contains no scheme"; | ||
116 | return; | ||
117 | } | ||
118 | /* found scheme */ | ||
119 | m_scheme = str.substr(0, scheme_colon); | ||
120 | /* validate scheme */ | ||
121 | if(!validate_scheme()) | ||
122 | return; | ||
123 | /* remove scheme: */ | ||
124 | str = str.substr(scheme_colon + 1); | ||
125 | /* check if it begins with // */ | ||
126 | if(str.size() >= 2 && str[0] == '/' && str[1] == '/') | ||
127 | { | ||
128 | /* remove it */ | ||
129 | str = str.substr(2); | ||
130 | /* find next '/' */ | ||
131 | std::string path; | ||
132 | size_t next_slash = str.find('/'); | ||
133 | /* if none, we can assume the path is empty, otherwise split path */ | ||
134 | if(next_slash != std::string::npos) | ||
135 | { | ||
136 | path = str.substr(next_slash); | ||
137 | str = str.substr(0, next_slash); | ||
138 | } | ||
139 | /* find last ':' if any, indicating a port */ | ||
140 | size_t port_colon = str.rfind(':'); | ||
141 | if(port_colon != std::string::npos) | ||
142 | { | ||
143 | m_domain = str.substr(0, port_colon); | ||
144 | m_port = str.substr(port_colon + 1); | ||
145 | } | ||
146 | else | ||
147 | m_domain = str; | ||
148 | if(!validate_domain() || !validate_port()) | ||
149 | return; | ||
150 | /* pursue with path */ | ||
151 | str = path; | ||
152 | } | ||
153 | /* next is path */ | ||
154 | m_path = str; | ||
155 | m_valid = true; | ||
156 | } | ||
157 | |||
158 | bool uri::valid() const | ||
159 | { | ||
160 | return m_valid; | ||
161 | } | ||
162 | |||
163 | std::string uri::error() const | ||
164 | { | ||
165 | return m_error; | ||
166 | } | ||
167 | |||
168 | std::string uri::full_uri() const | ||
169 | { | ||
170 | return m_uri; | ||
171 | } | ||
172 | |||
173 | std::string uri::scheme() const | ||
174 | { | ||
175 | return m_scheme; | ||
176 | } | ||
177 | |||
178 | std::string uri::domain() const | ||
179 | { | ||
180 | return m_domain; | ||
181 | } | ||
182 | |||
183 | std::string uri::port() const | ||
184 | { | ||
185 | return m_port; | ||
186 | } | ||
187 | |||
188 | std::string uri::path() const | ||
189 | { | ||
190 | return m_path; | ||
191 | } | ||
192 | |||
193 | /** Context creator */ | ||
194 | |||
195 | std::shared_ptr<context> create_default_context(std::string *error) | ||
196 | { | ||
197 | /* first try to create a unix context with abstract name 'hwstub' */ | ||
198 | std::shared_ptr<context> ctx = hwstub::net::context::create_unix_abstract( | ||
199 | hwstub::net::context::default_unix_path(), error); | ||
200 | if(ctx) | ||
201 | return ctx; | ||
202 | /* otherwise try default tcp */ | ||
203 | ctx = hwstub::net::context::create_tcp(hwstub::net::context::default_tcp_domain(), | ||
204 | hwstub::net::context::default_tcp_port(), error); | ||
205 | if(ctx) | ||
206 | return ctx; | ||
207 | /* otherwise default to usb */ | ||
208 | return hwstub::usb::context::create(nullptr, false, error); | ||
209 | } | ||
210 | |||
211 | std::shared_ptr<context> create_context(const uri& uri, std::string *error) | ||
212 | { | ||
213 | /* check URI is valid */ | ||
214 | if(!uri.valid()) | ||
215 | { | ||
216 | if(error) | ||
217 | *error = "invalid URI: " + uri.error(); | ||
218 | return std::shared_ptr<context>(); | ||
219 | } | ||
220 | /* handle different types of contexts */ | ||
221 | if(uri.scheme() == "usb") | ||
222 | { | ||
223 | /* domain and port must be empty */ | ||
224 | if(!uri.domain().empty() || !uri.port().empty()) | ||
225 | { | ||
226 | if(error) | ||
227 | *error = "USB URI cannot contain a domain or a port"; | ||
228 | return std::shared_ptr<context>(); | ||
229 | } | ||
230 | /* in doubt, create a new libusb context and let the context destroy it */ | ||
231 | libusb_context *ctx; | ||
232 | libusb_init(&ctx); | ||
233 | return hwstub::usb::context::create(ctx, true); | ||
234 | } | ||
235 | else if(uri.scheme() == "virt") | ||
236 | { | ||
237 | /* port must be empty */ | ||
238 | if(!uri.port().empty()) | ||
239 | { | ||
240 | if(error) | ||
241 | *error = "virt URI cannot contain a port"; | ||
242 | return std::shared_ptr<context>(); | ||
243 | } | ||
244 | return hwstub::virt::context::create_spec(uri.domain(), error); | ||
245 | } | ||
246 | else if(uri.scheme() == "tcp") | ||
247 | { | ||
248 | return hwstub::net::context::create_tcp(uri.domain(), uri.port(), error); | ||
249 | } | ||
250 | else if(uri.scheme() == "unix") | ||
251 | { | ||
252 | /* port must be empty */ | ||
253 | if(!uri.port().empty()) | ||
254 | { | ||
255 | if(error) | ||
256 | *error = "unix URI cannot contain a port"; | ||
257 | return std::shared_ptr<context>(); | ||
258 | } | ||
259 | if(!uri.domain().empty() && uri.domain()[0] == '#') | ||
260 | return hwstub::net::context::create_unix_abstract(uri.domain().substr(1), error); | ||
261 | else | ||
262 | return hwstub::net::context::create_unix(uri.domain(), error); | ||
263 | } | ||
264 | else if(uri.scheme() == "default") | ||
265 | { | ||
266 | /* domain and port must be empty */ | ||
267 | if(!uri.domain().empty() || !uri.port().empty()) | ||
268 | { | ||
269 | if(error) | ||
270 | *error = "Default URI cannot contain a domain or a port"; | ||
271 | return std::shared_ptr<context>(); | ||
272 | } | ||
273 | return create_default_context(error); | ||
274 | } | ||
275 | else | ||
276 | { | ||
277 | if(error) | ||
278 | *error = "unknown scheme '" + uri.scheme() + "'"; | ||
279 | return std::shared_ptr<context>(); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | std::shared_ptr<net::server> create_server(std::shared_ptr<context> ctx, | ||
284 | const uri& uri, std::string *error) | ||
285 | { | ||
286 | /* check URI is valid */ | ||
287 | if(!uri.valid()) | ||
288 | { | ||
289 | if(error) | ||
290 | *error = "invalid URI: " + uri.error(); | ||
291 | return std::shared_ptr<net::server>(); | ||
292 | } | ||
293 | /* handle different types of contexts */ | ||
294 | if(uri.scheme() == "unix") | ||
295 | { | ||
296 | /* port must be empty */ | ||
297 | if(!uri.port().empty()) | ||
298 | { | ||
299 | if(error) | ||
300 | *error = "unix URI cannot contain a port"; | ||
301 | return std::shared_ptr<net::server>(); | ||
302 | } | ||
303 | if(!uri.domain().empty() && uri.domain()[0] == '#') | ||
304 | return hwstub::net::server::create_unix_abstract(ctx, uri.domain().substr(1), error); | ||
305 | else | ||
306 | return hwstub::net::server::create_unix(ctx, uri.domain(), error); | ||
307 | } | ||
308 | else if(uri.scheme() == "tcp") | ||
309 | { | ||
310 | return hwstub::net::server::create_tcp(ctx, uri.domain(), uri.port(), error); | ||
311 | } | ||
312 | else | ||
313 | { | ||
314 | if(error) | ||
315 | *error = "unknown scheme '" + uri.scheme() + "'"; | ||
316 | return std::shared_ptr<net::server>(); | ||
317 | } | ||
318 | } | ||
319 | |||
320 | uri default_uri() | ||
321 | { | ||
322 | return uri("default:"); | ||
323 | } | ||
324 | |||
325 | uri default_server_uri() | ||
326 | { | ||
327 | return uri("unix://#" + hwstub::net::context::default_unix_path()); | ||
328 | } | ||
329 | |||
330 | } // namespace uri | ||
331 | } // namespace hwstub | ||
332 | |||