about summary refs log tree commit diff
path: root/doc/libwpactrl/index.html
blob: 89b3db212f8379bd8274c2d48b71af00dc162e9c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="Content-Language" content="en" />
    <title>bcnm: the wpactrl library interface</title
    <meta name="Description" content="bcnm: the wpactrl library interface" />
    <meta name="Keywords" content="bcnm library wpactrl libwpactrl wpactrl.h wpa_supplicant command" />
    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
  </head>
<body>

<p>
<a href="../index.html">bcnm</a><br />
<a href="//skarnet.org/software/">Software</a><br />
<a href="//skarnet.org/">skarnet.org</a>
</p>

<h1> The <tt>wpactrl</tt> library interface </h1>

<p>
<tt>wpactrl</tt> is a library designed to interface a client
with the <a href="https://w1.fi/wpa_supplicant/">wpa_supplicant</a>
program, in a smaller, cleaner, more user-friendly and more
packager-friendly way than the <tt>wpa_ctrl</tt> interface that
comes with the <a href="https://w1.fi/wpa_supplicant/">wpa_supplicant</a>
distribution.
</p>

<h2> Compiling </h2>

<ul>
 <li> Use <tt>#include &lt;bcnm/wpactrl.h&gt;</tt> </li>
</ul>

<h2> Linking </h2>

<ul>
 <li> Make sure the bcnm libraries, as well as the skalibs
libraries, are visible in your library search path. </li>
 <li> Link against <tt>-lwpactrl</tt> and <tt>-lskarnet</tt>.
Also add <tt>`cat $sysdeps/socket.lib $sysdeps/tainnow.lib`</tt>
to the end of your command line invoking the linker, in order
to be portable to libcs that put socket or time functions into
separate libraries.
(<tt>$sysdeps</tt> stands for your skalibs sysdeps directory.) </li>
</ul>


<h2> Programming </h2>

<p>
 The <tt>bcnm/wpactrl.h</tt> header is the reference for the exact
function prototypes.
</p>

<h3> General usage </h3>

<p>
 <tt>libwpactrl</tt> stores its information in a <tt>wpactrl_t</tt> structure.
Such a structure must be allocated (can be declared in the stack) and
initialized to WPACTRL_ZERO before use. The address of that <tt>wpactrl_t</tt>
structure is then used as a handle and given as an argument to all the
<tt>wpactrl_*</tt> function calls.
</p>

<h3> Connections </h3>

<p>
 A <tt>wpactrl_t</tt> instance represents two connections to a <tt>wpa_supplicant</tt>
program: an <em>attached</em> one and a <em>detached</em> one. For proper operation,
it is important to regularly read the data from the <em>attached</em> connection. This is
achieved by calling the <tt>wpactrl_update()</tt> function whenever data is available
on the <em>attached</em> connection; this is notified by the connection's fd becoming
readable. The attached connection's fd can be obtained via the <tt>wpactrl_fd()</tt>
function. So, proper usage of a <tt>wpactrl_t</tt> instance involves an asynchronous
event loop.
</p>

<h3> Synchronous functions </h3>

<p>
 The bulk of <tt>libwpactrl</tt> functions takes an extra <em>stamp</em> argument
at the end, of type
<a href="//skarnet.org/software/skalibs/libstddjb/tai.html">tain_t</a>. This means
they are synchronous function calls, and the extra argument is there to ensure
those calls do not block forever.
</p>

<p>
<em>stamp</em> must be first initialized to an
accurate enough approximation of the current time, for instance via skalibs'
<tt>tain_now()</tt> function; it will then be automatically updated by the
libwpactrl function calls to always contain (an accurate enough approximation
of) the current time.
</p>

<p>
 <a href="//skarnet.org/software/skalibs/">skalibs</a> can keep track of the
timestamp for you, in the global <tt>STAMP</tt> variable. All <tt>libwpactrl</tt>
functions taking a <em>stamp</em> argument also have a
version with a name ending in <tt>_g</tt>, that does not take it and instead
assumes the <tt>STAMP</tt> variable always contains (an accurate
enough approximation of) the current time.
<p>

<p>
 Those synchronous function calls normally return almost instantly: there should
be no blocking code path between the function call and its return. Nevertheless,
since they involve communication with a complex <tt>wpa_supplicant</tt> process,
it's impossible to guarantee that they will never block, so the use of the
<em>stamp</em> argument, plus a timeout given at <tt>wpactrl_start</tt> time,
ensures there is a cap on the amount of time they block.
</p>


<h3> Starting and stopping a session </h3>

<p>
<code> int wpactrl_start (wpactrl_t *a, char const *path, unsigned int timeout, tain_t *stamp) </code> <br />
Starts a session with a <tt>wpa_supplicant</tt> instance listening on a Unix socket
at <em>path</em>. <em>a</em> is a handle that must be initialized to
WPACTRL_ZERO before the call to <tt>wpactrl_start</tt>, and that must then
be passed to every <tt>wpactrl_*</tt> call in the session.
The function returns 1 if it succeeds, or 0 (and sets errno) if
it fails. The <em>timeout</em> argument is interpreted as milliseconds:
it sets the number of milliseconds for which every subsequent synchronous call
to wpa_supplicant in the current session will be willing to wait. If a call
to wpa_supplicant takes longer than <em>timeout</em> milliseconds, the call
will immediately be aborted.
</p>

<p>
<code> int wpactrl_end (wpactrl_t *a) </code> <br />
Ends the session, freeing all used resources.
</p>

<h3> Low-level command sending </h3>

<p>
<code> ssize_t wpactrl_query (wpactrl_t *a, char const *q, char *ans, size_t anslen, tain_t *stamp) </code> <br />
Sends the query <em>q</em> to the connected instance
of wpa_supplicant, and reads its answer into the buffer pointed to by
<em>ans</em>. Returns -1 in case of failure, or the number of bytes of
the answer in case of success. Returns -1 with errno set to EMSGSIZE if
the answer is bigger than <em>anslen</em> bytes.
</p>

<p>
<code> int wpactrl_querysa (wpactrl_t *a, char const *q, stralloc *sa, tain_t *stamp) </code> <br />
Sends the query <em>q</em> to the connected instance
of wpa_supplicant, and reads its answer into the
<a href="//skarnet.org/software/skalibs/libstddjb/stralloc.html">stralloc</a>
pointed to by <em>sa</em>. Returns 1 if it succeeds and 0 if it fails.
</p>

<p>
<code> wparesponse_t wpactrl_command (wpactrl_t *a, char const *q, tain_t *stamp) </code> <br />
Sends the command <em>q</em> to the connected instance
of wpa_supplicant, and returns its answer under the form of a
<tt>wparesponse_t</tt>, which is an enumeration defined in the
<tt>bcnm/wpactrl.h</tt> header. This function is meant to be used
with commands returning a well-known value, such as <tt>RECONFIGURE</tt>
(returning <tt>OK</tt> or <tt>FAIL</tt>) or <tt>PING</tt>
(returning <tt>PONG</tt>). The <tt>wparesponse_t</tt> enumeration
type lists all the possible values for the function's return code.
</p>

<h3> Reading from the attached (asynchronous) connection </h3>

<p>
<code> int wpactrl_update (wpactrl_t *a) </code> <br />
Reads unsolicited messages from wpa_supplicant. If the messages
are whitelisted, it keeps them, otherwise it discards them.
The function returns the number of messages that have been read,
or -1 in case of failure. A positive number does not mean that
all pending messages have been read: there is a cap on the
number of messages that can be consecutively read, to prevent
a spamming wpa_supplicant from monopolizing your program.
</p>

<p>
<code> char *wpactrl_msg (wpactrl_t *a) </code> <br />
Returns a pointer to the first unsolicited message from
wpa_supplicant that has been read by <tt>wpactrl_update()</tt> but
has not been acknowledged yet. If there's no such message,
returns NULL.
</p>

<p>
<code> void wpactrl_ackmsg (wpactrl_t *a) </code> <br />
Acknowledges reading of one unsolicited message from wpa_supplicant.
The next invocation of <tt>wpactrl_msg()</tt> will point to the next
one.
</p>

<p>
<code> int wpactrl_filter_add (wpactrl_t *a, char const *prefix) </code> <br />
Adds <em>prefix</em> to the whitelist. Unsolicited messages from
wpa_supplicant will be stored and made available to the application
if they start with <tt>&lt;</tt><em>priority</em><tt>&gt;</tt><em>prefix</em>,
<em>priority</em> being a single nonzero digit. If the filter is
activated (which is the default), then only messages matching prefixes
registered via <tt>wpactrl_filter_add()</tt> will be stored, and all
other messages will be discarded. The function returns
1 if it succeeds and 0 if it fails.
</p>

<p>
<code> void wpactrl_filter_remove (wpactrl_t *a, char const *prefix) </code> <br />
Removes <em>prefix</em> from the whitelist.
</p>

<p>
<code> void wpactrl_filter_activate (wpactrl_t *a) </code> <br />
Activates the message filter. Unsolicited messages from
wpa_supplicant will be discarded unless they are explicitly
whitelisted by a call to <tt>wpactrl_filter_add()</tt>. This
is the default.
</p>

<p>
<code> void wpactrl_filter_deactivate (wpactrl_t *a) </code> <br />
Dectivates the message filter. All the unsolicited messages from
wpa_supplicant will be stored and made available to the
application.
</p>

<p>
<code> int wpactrl_filter_match (wpactrl_t const *a, char const *s, size_t len) </code> <br />
Returns 1 if the string <em>s</em> of size <em>len</em> matches one of the
registered filters, and 0 otherwise.
</p>


<h3> Helper functions for parsing answers from wpa_supplicant </h3>

<p>
<code> size_t wpactrl_bssid_scan (char const *s, char *bssid) </code> <br />
Parses a BSSID of the form <em>a:b:c:d:e:f</em> in string <em>s</em>
and writes it as an array of 6 bytes pointed to by <em>bssid</em>.
The string "any" is specifically recognized and yields a <em>bssid</em>
of 6 zero bytes. The function returns the number of characters eaten
in <em>s</em>, or 0 if it fails to recognize a BSSID.
</p>

<p>
<code> size_t wpactrl_flags_scan (char const *s, stralloc *sa) </code> <br />
Parses a wpa_supplicant "flags" field in the string <em>s</em>
and appends them to the
<a href="//skarnet.org/software/skalibs/libstddjb/stralloc.html">stralloc</a>
pointed to by <em>sa</em>. The flags are written without their
surrounding square brackets, and every flag is terminated by a null
byte.
</p>

<p>
<code> unsigned int wpactrl_env_parse (char *s, size_t len) </code> <br />
Replaces newlines with null bytes in the string <em>s</em> of length <em>len</em>.
Returns the number of replaced newlines.
</p>

<p>
<code> int wpactrl_scan_parse (char const *s, size_t len, genalloc *ga, stralloc *storage) </code> <br />
Parses the string <em>s</em> of length <em>len</em>, expecting it to be
wpa_supplicant's response to a SCAN_RESULTS command. The result is a series of
<tt>wpactrl_scanres_t</tt> structures, appended to the
<a href="//skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
pointed to by <em>ga</em>, and variable length data is appended to the
<a href="//skarnet.org/software/skalibs/libstddjb/stralloc.html">stralloc</a>
pointed to by <em>storage</em>.
 The <tt>ssid_start</tt> and <tt>flags_start</tt> fields of a
<tt>wpactrl_scanres_t</tt> are indices pointing into the <em>storage&rarr;s</em>
string.
</p>

<p>
<code> int wpactrl_networks_parse (char const *s, size_t len, genalloc *ga, stralloc *storage) </code> <br />
Parses the string <em>s</em> of length <em>len</em>, expecting it to be
wpa_supplicant's response to a LIST_NETWORKS command. The result is a series of
<tt>wpactrl_networks_t</tt> structures, appended to the
<a href="//skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
pointed to by <em>ga</em>, and variable length data is appended to the
<a href="//skarnet.org/software/skalibs/libstddjb/stralloc.html">stralloc</a>
pointed to by <em>storage</em>.
 The <tt>ssid_start</tt> and <tt>flags_start</tt> fields of a
<tt>wpactrl_networks_t</tt> are indices pointing into the <em>storage&rarr;s</em>
string.
</p>

<p>
<code> void wpactrl_xchg_cbres_free (wpactrl_xchg_cbres_t *res) </code> <br />
Frees the heap memory used by the object pointed to by <em>res</em>.
</p>

<h3> User functions for common calls to wpa_supplicant </h3>

<p>
<code> int wpactrl_addnetwork (wpactrl_t *a, uint32_t *id, tain_t *stamp) </code> </br>
Tells wpa_supplicant to create a new network. If it fails, returns 0. If it
succeeds, stores the new network id in <em>*id</em> and returns 1.
</p>

<p>
<code> wparesponse_t wpactrl_removenetwork (wpactrl_t *a, uint32_t id, tain_t *stamp) </code> </br>
Tells wpa_supplicant to remove the network with id <em>id</em>. Returns the
response code of wpa_supplicant: WPA_OK on success, WPA_FAIL or something
else on failure.
</p>

<p>
<code> int wpactrl_findnetwork (wpactrl_t *a, char const *ssid, uint32_t *id, tain_t *stamp) </code> </br>
Finds the network id (as seen by wpa_supplicant) of the network with ssid <em>ssid</em>.
Stores it into <em>*id</em> if found, and returns 1. Returns 0 if not found;
returns -1 (and sets errno) if an error occurs.
</p>

<p>
<code> wparesponse_t wpactrl_setnetworkoption (wpactrl_t *a, uint32_t id, char const *var, char const *val, tain_t *stamp) </code> </br>
Sets parameter <em>var</em> to value <em>val</em> for network <em>id</em>.
Returns the response code of wpa_supplicant, most likely WPA_OK or WPA_FAIL.
</p>

<p>
<code> wparesponse_t wpactrl_selectnetwork (wpactrl_t *a, uint32_t id, tain_t *stamp) </code> </br>
Selects network <em>id</em> to associate with.
Returns the response code of wpa_supplicant, most likely WPA_OK or WPA_FAIL.
</p>

<p>
<code> int wpactrl_associate (wpactrl_t *, char const *ssid, char const *psk, tain_t *stamp) </code> </br>
Tells wpa_supplicant to associate with the wifi network having the ssid <em>ssid</em>,
creating it if it's not already known by wpa_supplicant. If <em>psk</em> is NULL,
the network will be assumed open and authentication will use a NONE protocol.
If <em>psk</em> is not NULL, the network authentication will be assumed using
WPA-PSK or WPA2-PSK, and <em>psk</em> will be sent as pre-shared key.
The function returns 1 on success, or 0 if something went wrong.
</p>

<p>
<code> int wpactrl_startscan (wpactrl_t *a, wpactrl_xchgitem_t *item, wpactrl_xchg_cbres_t *res, tain_t const *limit, tain_t *stamp) </code> </br>
Asks wpa_supplicant to start a scan. Sets up the <tt>wpactrl_xchgitem_t</tt>
structure pointed to by <em>item</em> so it can be used in an asynchronous
event loop to check for availability of the scan results (see below).
<em>limit</em> is an absolute deadline after which the scan should be
considered failed: if the current time goes over <em>limit</em>, then
<tt>wpactrl_xchg_timeout()</tt> will report a timeout on <em>item</em>.
But if <tt>wpactrl_xchg_event()</tt> reports that an event occurs on
<em>item</em>, instead, the results will be available in the
<tt>wpactrl_xchg_cbres_t</tt> structure pointed to by <em>res</em>:
<em>res&rarr;parsed</em> will be a
<a href="//skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
made of <tt>wpactrl_scanres_t</tt> objects, constructed by the
<tt>wpactrl_scan_parse()</tt> function, and <em>res&rarr;storage</em> will
be the associated storage.
<tt>wpactrl_startscan()</tt> returns 0 (and sets errno) if an error
occurs, and 1 if the scan is properly started.
</p>

<h3> Functions to use within an asynchronous event loop </h3>

<p>
<code> int wpactrl_xchg_init (wpactrl_xchg_t *dt, wpactrl_xchgitem_t const *tab, unsigned int tablen, tain_t const *limit, void *aux) </code> </br>
Initializes the <tt>wpactrl_xchg_t</tt> structure pointed to by <em>dt</em>.
Returns 0 on failure and 1 on success.
</p>

<p>
A <tt>wpactrl_xchg_t</tt> contains the state for an asynchronous call to
wpa_supplicant (i.e. a command has been sent and we're now waiting on
reception of an event on the attached interface). It is initialized with
the <em>tab</em>, <em>n</em> and <em>aux</em> values.
</p>

<p>
<em>aux</em> is a user-provided pointer used to pass external data
to the function callbacks defined in <em>tab</em>.
</p>

<p>
<em>tab</em> points to <em>tablen</em> caller-provided objects of type <tt>wpactrl_xchgitem_t</tt>.
This type is a struct containing the following members:
</p>

<ul>
 <li> <tt>char const *filter</tt>&nbsp;: a string to watch for in unsolicited messages
sent by wpa_supplicant to the attached interface. When this string is received,
it means the call can proceed. For instance, when a scan has been requested,
the string to watch is <tt>CTRL-EVENT-SCAN-RESULTS</tt>. </li>
 <li> <tt>int *cb (wpactrl_t *a, char const *msg, size_t msglen, void *aux, tain_t *stamp)</tt>&nbsp;:
A pointer to a function that will be run as a callback when a message matching the <em>filter</em>
field is received. It will be called with the following arguments:
 <ul>
  <li> <em>a</em>&nbsp;: the connection handle </li>
  <li> <em>msg</em>&nbsp;: the message from wpa_supplicant </li>
  <li> <em>msglen</em>&nbsp;: the size of the message </li>
  <li> <em>aux</em>&nbsp;: the <em>aux</em> pointer provided to this <tt>wpactrl_xchg_init()</tt> call </li>
  <li> <em>stamp</em>&nbsp;: a pointer to the current time (at the time of the callback) </li>
 </ul> </li>
</ul>

<p>
The <em>*cb</em> function must return 0 (and set errno) if it fails, or a
positive integer if it succeeds. The objects in <em>tab</em> will be used
sequentially: first a message with <em>dt&rarr;tab[0].filter</em> will
be waited for, then <em>*dt&rarr;tab[0].cb</em> will be run; if it
succeeds, a message with <em>dt&rarr;tab[1].filter</em> will be waited for,
and so on. The last function, <em>*dt&rarr;tab[tablen-1].cb</em>, should
write the final result of the whole to a place accessible by the
user; this is one of the uses for the <em>aux</em> pointer.
</p>

<p>
<em>limit</em> is a deadline: an absolute date after which the whole series of
exchanges with wpa_supplicant will stop and be considered failed, i.e.
<a href="//skarnet.org/software/skalibs/libstddjb/iopause.html">iopause</a>
will report a timeout and <tt>wpactrl_xchg_timeout()</tt> called on
<em>dt</em> will return 1.
</p>

<p>
<code> int wpactrl_xchg_start (wpactrl_t *a, wpactrl_xchg_t *dt) </code> <br />
Starts the exchange defined in the object pointed to by <em>dt</em>, with the
wpa_supplicant instance defined by the handle <em>a</em>. Returns 1 if it
succeeds and 0 if it fails.
</p>

<p>
<code> void wpactrl_xchg_computedeadline (wpactrl_xchg_t const *dt, tain_t *deadline) </code> <br />
Updates the deadline pointed to by <em>deadline</em>, destined to be used in the next
<a href="//skarnet.org/software/skalibs/libstddjb/iopause.html">iopause</a> invocation,
with the one contained in <em>*dt</em>. Namely: if the deadline defined by <em>*dt</em>
is earlier than <em>*deadline</em>, replaces the latter with the former.
</p>

<p>
<code> int wpactrl_xchg_timeout (wpactrl_t *a, wpactrl_xchg_t *dt, tain_t const *stamp) </code> <br />
To be called after an <tt>iopause</tt> invocation that returned 0.
Tests whether the exchange
defined by <em>dt</em> has timed out. Returns 1 (and cleans up the relevant
filters in <em>a</em> if it is the case, and 0 otherwise. <em>stamp</em> must
point to the current time.
</p>

<p>
<code> int wpactrl_xchg_event (wpactrl_t *a, wpactrl_xchg_t *dt, tain_t *stamp) </code> <br />
To be called after an <tt>iopause</tt> invocation that returned a positive number, and
after a <tt>wpactrl_update(<em>a</em>)</tt> invocation.
Advances the exchange described in <em>*dt</em>, if applicable: if a message arrived
that matches the current filter set up by <em>*dt</em>, executes the corresponding
callback, then sets up the next filter. <em>stamp</em> must point to the current
time.
</p>

<p>
 The function returns a negative number if an error occurred and the exchange needs
to be cancelled and freed; 0 if the exchange isn't over yet; and a positive number
if the exchange completed successfully. Namely:
</p>

<ul>
 <li> -2: a callback was run and returned an error. </li>
 <li> -1: an error occurred during execution of <tt>wpactrl_xchg_event()</tt>.
 <li> 0: either the message from wpa_supplicant that <em>*dt</em> is expecting
has not arrived yet, or it has arrived, the relevant callback has been run and
has succeeded, and it was not the last part of the exchange - <em>*dt</em> is
now waiting for another message. </li>
 <li> 1: the exchange completed successfully. The last callback should have
written the results to the auxiliary pointer. <em>dt</em> can now be ignored. </li>
 <li> 2: the exchange already completed in a previous invocation of
<tt>wpactrl_xchg_event()</tt>. It's still a success, but likely signals a programming
error. </li>
</ul>

<h2> A working example </h2>

<p>
 The provided
 <a href="bcnm-wpactrl-scan.c.txt">bcnm-wpactrl-scan.c</a>
file is an example on how to program with
<a href="//skarnet.org/software/skalibs/">skalibs</a> and libwpactrl. It connects to
a wpa_supplicant instance (it takes the path to the Unix socket to wpa_supplicant as
an argument), requests a scan, waits for the scan results with a timeout of 10
seconds, and prints the results as is on its standard output.
</p>

</body>
</html>