drop package-*.html now that we have real javadocs
This commit is contained in:
@ -16,7 +16,7 @@ for some tunnels to be used.
|
||||
|
||||
<p>
|
||||
The protocol itself has only been implemented in Java, to provide the
|
||||
<a href="package-client.html">Client SDK</a>.
|
||||
<a href="http://docs.i2p2.de/javadoc/net/i2p/client/package-summary.html">Client SDK</a>.
|
||||
This SDK is exposed in the i2p.jar package, which implements the client-side of I2CP.
|
||||
Clients should never need to access the router.jar package, which contains the
|
||||
router itself and the router-side of I2CP.
|
||||
|
@ -1,37 +0,0 @@
|
||||
{% extends "_layout.html" %}
|
||||
{% block title %}Client Package{% endblock %}
|
||||
{% block content %}
|
||||
<p>Implements the base I2P SDK for developing applications that communicate
|
||||
through I2P.</p>
|
||||
|
||||
<p>When a client application wants to communicate over I2P, the first thing it
|
||||
needs to do is get a {@link net.i2p.client.I2PClient} from the
|
||||
{@link net.i2p.client.I2PClientFactory}. If it does not already have a {@link
|
||||
net.i2p.data.Destination}, it must generate one with the {@link
|
||||
net.i2p.client.I2PClient#createDestination} before proceeding. Once it has
|
||||
one, it then creates an {@link net.i2p.client.I2PSession} which serves as the
|
||||
bridge to the I2P network, allowing it to send messages (via
|
||||
{@link net.i2p.client.I2PSession#sendMessage}) and receive messages (via
|
||||
{@link net.i2p.client.I2PSession#receiveMessage}). In addition, the client
|
||||
receives asynchronous notification of network activity by providing an implementation
|
||||
of {@link net.i2p.client.I2PSessionListener}. </p>
|
||||
|
||||
<p>A simple example of how these base client classes can be used is the
|
||||
{@link net.i2p.client.ATalk} application. It isn't really useful, but it is
|
||||
heavily documented code.</p>
|
||||
|
||||
<p>This client package provides the basic necessity for communicating over I2P,
|
||||
but there are three other subpackages that are helpful. Specifically:<ul>
|
||||
<li>{@link net.i2p.client.datagram} - for applications that want their messages
|
||||
to be both authenticated and repliable</li>
|
||||
<li>{@link net.i2p.client.naming} - for applications that want to resolve
|
||||
readable names into {@link net.i2p.data.Destination}s</li>
|
||||
<li>{@link net.i2p.client.streaming} - for applications that want to use
|
||||
a streaming API to provide reliable in order message delivery (<b>note</b>:
|
||||
the streaming library is packaged separate from the main SDK - in the
|
||||
mstreaming.jar and streaming.jar)</li>
|
||||
</ul></p>
|
||||
|
||||
<p>The {@link net.i2p.client.I2PSession} implementation itself communicates with
|
||||
the I2P router by the I2CP (the client protocol).</p>
|
||||
{% endblock %}
|
@ -1,39 +0,0 @@
|
||||
{% extends "_layout_de.html" %}
|
||||
{% block title %}Klient Packet{% endblock %}
|
||||
{% block content %}
|
||||
<p>Implementiert das Basis I2P SDK zum Entwickeln von Anwendungen, die
|
||||
über I2P kommunizieren.</p>
|
||||
|
||||
<p>Sobald eine Klientanwendung über I2P kommunizieren will, braucht
|
||||
sie zuerst ein {@link net.i2p.client.I2PClient} von der
|
||||
{@link net.i2p.client.I2PClientFactory}. Falls es nicht schon eine
|
||||
{@link net.i2p.data.Destination} hat, muss es mit dem
|
||||
{@link net.i2p.client.I2PClient#createDestination} eine erstellen bevor
|
||||
sie weiter machen kann. Sobald es eine hat erstellt sie eine
|
||||
{@link net.i2p.client.I2PSession} die als Brücke ins I2P Netzwerk
|
||||
arbeitet. Dieses erlaubt das Senden von Nachrichten (via
|
||||
{@link net.i2p.client.I2PSession#sendMessage}) und das Empfangen von Nachrichten
|
||||
(via {@link net.i2p.client.I2PSession#receiveMessage}). Zusätzlich
|
||||
erhält der Klient asynchrone Informationen zur Netzaktivität
|
||||
aus der Implementation des {@link net.i2p.client.I2PSessionListener}. </p>
|
||||
|
||||
<p>Ein einfaches Beispiel zur Nutzung dieser Basisklassen kann in der
|
||||
{@link net.i2p.client.ATalk} Anwendung gefunden werden. Sie ist nicht
|
||||
wirklich nützlich, ist aber ein sehr stark dokumentierter Quelltext.</p>
|
||||
|
||||
<p>Dieses Klient Packet hält die Basis Anforderungen zur Kommunikation
|
||||
mit I2P bereit, hinzu kommen drei weitere, hilfreiche Unterpackete.
|
||||
Diese sind:<ul>
|
||||
<li>{@link net.i2p.client.datagram} - für Anwendungen, die ihre Nachrichten
|
||||
sowohl authentifiziert als auch beantwortbar brauchen</li>
|
||||
<li>{@link net.i2p.client.naming} - für Anwendungen, die lesbare Namen
|
||||
in {@link net.i2p.data.Destination}s auflösen wollen</li>
|
||||
<li>{@link net.i2p.client.streaming} - für Anwendungen, die eine
|
||||
Streaming API nutzen, um die Reihenfolge der Nachrichten sicherstellen
|
||||
zu können (<b>Hinweis</b>: die Streaming Bibliothek ist seperat
|
||||
vom Haupt SDK vorgehalten - in den mstreaming.jar und streaming.jar)</li>
|
||||
</ul></p>
|
||||
|
||||
<p>Die {@link net.i2p.client.I2PSession} Implementation alleine kommuniziert
|
||||
mit dem I2P Router mittels des I2CP (das Klientenprotokoll).</p>
|
||||
{% endblock %}
|
@ -1,17 +0,0 @@
|
||||
{% extends "_layout.html" %}
|
||||
{% block title %}Datagram Package{% endblock %}
|
||||
{% block content %}
|
||||
<p>Provides a standard way for reading and writing messages transferred over I2P
|
||||
so that the recipient has an authenticated mechanism to reply to it. This is
|
||||
necessary because the base I2P message sent through {@link net.i2p.client.I2PSession#sendMessage}
|
||||
has no "from" address, and simply providing a raw "from" address would be
|
||||
insecure as it could be spoofed. An application that needs to know for certain
|
||||
who sent a message to them should use the {@link net.i2p.client.datagram.I2PDatagramDissector}
|
||||
to parse the raw message received from the {@link net.i2p.client.I2PSession}, and
|
||||
in turn, use the {@link net.i2p.client.datagram.I2PDatagramMaker} to build a
|
||||
message that can be parsed. </p>
|
||||
|
||||
<p>The datagram format implemented here includes
|
||||
the sender's {@link net.i2p.data.Destination}, the payload, and a hash of the
|
||||
payload (signed by the sender's {@link net.i2p.data.SigningPrivateKey}).</p>
|
||||
{% endblock %}
|
@ -1,20 +0,0 @@
|
||||
{% extends "_layout_de.html" %}
|
||||
{% block title %}Datagram Package{% endblock %}
|
||||
{% block content %}
|
||||
<p>Hält einen Standardweg zum Lesen und Schreiben von über I2P
|
||||
transportierten Nachrichten bereit, damit der Empfänger einen
|
||||
authentifizierten Mechanismus zum Beantworten derselben hat. Dieses
|
||||
ist notwendig, da die Standard I2P Nachrichten, die über
|
||||
{@link net.i2p.client.I2PSession#sendMessage} gesendet werden, keine
|
||||
"von" Adresse haben. Und das hinzufügen einer Klartext "von" Adresse
|
||||
ist unsicher, da dieses manipuliert werden kann. Eine Anwendung, die
|
||||
sicher wissen muss, von wem eine Nachricht stammt, sollte die
|
||||
{@link net.i2p.client.datagram.I2PDatagramDissector} nutzen um die von
|
||||
{@link net.i2p.client.I2PSession} empfangenen Klartext Nachricht zu
|
||||
bearbeiten und mit der Ausgabe die {@link net.i2p.client.datagram.I2PDatagramMaker}
|
||||
nutzen zum bauen einer Nachricht, die geparst werden kann. </p>
|
||||
|
||||
<p>Das hier implementierte Datagramm Format inkludiert die {@link net.i2p.data.Destination}
|
||||
des Senders, die Nutzdaten und einen Hash der Nutzdaten (signiert mit dem
|
||||
{@link net.i2p.data.SigningPrivateKey} des Senders).</p>
|
||||
{% endblock %}
|
@ -1,9 +0,0 @@
|
||||
{% extends "_layout.html" %}
|
||||
{% block title %}Naming Package{% endblock %}
|
||||
{% block content %}
|
||||
<p>Provides a standard way for querying the local naming service to resolve a
|
||||
name into a {@link net.i2p.data.Destination} (without the complexity of JNDI).
|
||||
The default implementation is a simple hosts.txt driven system, though that can
|
||||
be overridden by specifying the "i2p.naming.impl" environment property to point
|
||||
at the requested classname.</p>
|
||||
{% endblock %}
|
@ -1,10 +0,0 @@
|
||||
{% extends "_layout_de.html" %}
|
||||
{% block title %}Namensgebung der Packete{% endblock %}
|
||||
{% block content %}
|
||||
<p>Hält einen Standardweg zum Abfragen des lokalen Namensgebungsservices
|
||||
bereit, um einen Namen in eine {@link net.i2p.data.Destination} aufzulöen
|
||||
(ohne die Komplexität von JNDI).
|
||||
Die Standardimplementation ist ein einfaches System aus einer hosts.txt Datei,
|
||||
wobei dieses übergangen werden kann, in dem man die Umgebungsvariable
|
||||
"i2p.naming.impl" auf den entsprechenden Klassennamen setzt.</p>
|
||||
{% endblock %}
|
@ -1,26 +0,0 @@
|
||||
{% extends "_layout.html" %}
|
||||
{% block title %}Streaming Package{% endblock %}
|
||||
{% block content %}
|
||||
<p>Implements a TCP-like (reliable, authenticated, in order) set of sockets for
|
||||
communicating over the IP-like (unreliable, unauthenticated, unordered) I2P
|
||||
messages.</p>
|
||||
|
||||
<p>When an application wants to use streams, it must fetch an {@link
|
||||
net.i2p.client.streaming.I2PSocketManager} from the {@link
|
||||
net.i2p.client.streaming.I2PSocketManagerFactory}, which in turn builds its own
|
||||
{@link net.i2p.client.I2PSession} internally. All communication over that
|
||||
{@link net.i2p.client.I2PSession} is handled by the {@link
|
||||
net.i2p.client.streaming.I2PSocketManager}, as it imposes its own formatting on
|
||||
the raw messages sent and received. If an application wants to receive streams
|
||||
from other clients on the network, it should access the blocking {@link
|
||||
net.i2p.client.streaming.I2PServerSocket#accept} method, which will provide an
|
||||
{@link net.i2p.client.streaming.I2PSocket} when a new one is available. If an
|
||||
application wants to create a new stream to a peer, it should do so with the
|
||||
appropriate {@link net.i2p.client.streaming.I2PSocketManager#connect} call.</p>
|
||||
|
||||
<p>There is a simple pair of demo applications available as well - {@link
|
||||
net.i2p.client.streaming.StreamSinkServer} listens to a destination and dumps
|
||||
the data from all sockets it accepts to individual files, while {@link
|
||||
net.i2p.client.streaming.StreamSinkClient} connects to a particular destination
|
||||
and sends a specific amount of random data then disconnects.</p>
|
||||
{% endblock %}
|
@ -1,30 +0,0 @@
|
||||
{% extends "_layout_de.html" %}
|
||||
{% block title %}Streaming Packet{% endblock %}
|
||||
{% block content %}
|
||||
<p>Implementiert einen TCP-ähnliche (zuverlässlich, authentifiziert,
|
||||
in Reihenfolge) Satz an Sockets zum Kommunizieren über die IP-ähnlichen
|
||||
(unzuverlässlich, unauthentifiziert, unsortiert) I2P Nachrichten.</p>
|
||||
|
||||
<p>Falls eine Anwendugn Streams nutzen möchte, muss es einen
|
||||
{@link net.i2p.client.streaming.I2PSocketManager} von der{@link
|
||||
net.i2p.client.streaming.I2PSocketManagerFactory} anfordern, welche
|
||||
als Antwort intern eine eigene {@link net.i2p.client.I2PSession} aufbaut.
|
||||
Jede Kommunikation über diese {@link net.i2p.client.I2PSession}
|
||||
wird vom {@link net.i2p.client.streaming.I2PSocketManager} verwaltet,
|
||||
da dieser eine eigene Art an Formatierung der gesendeten und empfangenen
|
||||
RAW Nachrichten beinhaltet. Falls eine Anwendung Streams von anderen
|
||||
Klienten aus dem Netzwerk empfangen will, sollte es die blockierenden
|
||||
{@link net.i2p.client.streaming.I2PServerSocket#accept} Methoden nutzen,
|
||||
welche einen {@link net.i2p.client.streaming.I2PSocket} zur Verfügung
|
||||
stellen sobald ein neuer Socket verfügbar ist. Falls eine Anwendung
|
||||
einen neuen Stream zu einem Knoten erstellen will, sollte sie dieses mit dem
|
||||
passendem {@link net.i2p.client.streaming.I2PSocketManager#connect} Aufruf
|
||||
erledigen.</p>
|
||||
|
||||
<p>Es exisitieren auch hier einige einfache Demoanwendungen - {@link
|
||||
net.i2p.client.streaming.StreamSinkServer} lauscht zu einer Destination
|
||||
und schreibt die Daten aller Sockets, die es akzeptiert, in individuelle
|
||||
Dateien, während {@link net.i2p.client.streaming.StreamSinkClient}
|
||||
zu einer bestimmten Destination verbindet und eine bestimmte Anzahl an
|
||||
Zufallsdaten sendet und sich dann disconnected.</p>
|
||||
{% endblock %}
|
@ -1,143 +0,0 @@
|
||||
{% extends "_layout.html" %}
|
||||
{% block title %}Old TCP Package{% endblock %}
|
||||
{% block content %}
|
||||
<p>Implements the transport for communicating with other routers via TCP/IP.</p>
|
||||
|
||||
<h1>Connection protocol</h1>
|
||||
|
||||
<p>The protocol used to establish the connection between the peers is
|
||||
implemented in the {@link net.i2p.router.transport.tcp.ConnectionBuilder}
|
||||
for "Alice", the initiator, and in
|
||||
{@link net.i2p.router.transport.tcp.ConnectionHandler} for "Bob", the
|
||||
receiving peer. <i>(+ implies concatenation)</i></p>
|
||||
|
||||
<h2>Common case:</h2>
|
||||
<p><b>1) </b> <i>Alice to Bob</i>: <br />
|
||||
<code>#bytesFollowing + #versions + v1 [+ v2 [etc]] + tag? + tagData + properties</code></p>
|
||||
<p><b>2) </b> <i>Bob to Alice</i>: <br />
|
||||
<code>#bytesFollowing + versionOk + #bytesIP + IP + tagOk? + nonce + properties</code></p>
|
||||
|
||||
<ul>
|
||||
<li><code>#bytesFollowing</code> is a 2 byte unsigned integer specifying how many
|
||||
bytes there are (after the current pair) in the line sent. 0xFFFF is reserved</li>
|
||||
<li><code>#versions</code> is a 1 byte unsigned integer specifying how many
|
||||
acceptable 1 byte version numbers follow (preferred value first).</li>
|
||||
<li><code>v1</code> (etc) is a 1 byte unsigned integer specifying a protocol
|
||||
version. The value 0x0 is not allowed.</li>
|
||||
<li><code>tag?</code> is a 1 byte value specifying whether a tag follows - 0x0 means
|
||||
no tag follows, 0x1 means a 32 byte tag follows.</li>
|
||||
<li><code>tagData</code> is a 32 byte tag, if necessary</li>
|
||||
<li><code>properties</code> is a name=value mapping, formatted as the other I2P
|
||||
mappings (via {@link net.i2p.data.DataHelper#readProperties})</li>
|
||||
<li><code>versionOk</code> is a 1 byte value specifying the protocol version
|
||||
that is agreed upon, or 0x0 if no compatible protocol versions are available.</li>
|
||||
<li><code>#bytesIP</code> is a 2 byte unsigned integer specifying how many bytes
|
||||
following make up the IP address</li>
|
||||
<li><code>IP</code> is made up of <code>#bytesIP</code> bytes formatting the
|
||||
peer who established the connection's IP address as a string (e.g. "192.168.1.1")</li>
|
||||
<li><code>tagOk?</code> is a 1 byte value specifying whether the tag provided
|
||||
is available for use - 0x0 means no, 0x1 means yes.</li>
|
||||
<li><code>nonce</code> is a 4 byte random value</li>
|
||||
</ul>
|
||||
|
||||
<p>Whether or not the <code>tagData</code> is specified by Alice and is accepted
|
||||
by Bob determines which of the scenarios below are used. In addition, the IP
|
||||
address provided by Bob gives Alice the opportunity to fire up a socket listener
|
||||
on that interface and include it in her list of reachable addresses. The
|
||||
<code>properties</code> mappings are left for future expansion.</p>
|
||||
|
||||
<h2>Connection establishment with a valid tag:</h2>
|
||||
<p>With a valid <code>tag</code> and <code>nonce</code> received, both Alice and
|
||||
Bob load up the previously negotiated <code>sessionKey</code> and set the
|
||||
<code>iv</code> to the first 16 bytes of <code>H(tag + nonce)</code>. The
|
||||
remainder of the communication is AES256 encrypted per
|
||||
{@link net.i2p.crypto.AESInputStream} and {@link net.i2p.crypto.AESOutputStream}</p>
|
||||
|
||||
<p><b>3) </b> <i>Alice to Bob</i>: <br />
|
||||
<code>H(nonce)</code></p>
|
||||
<p><b>4) </b> <i>Bob to Alice</i>: <br />
|
||||
<code>H(tag)</code></p>
|
||||
<p><b>5) </b> If the hashes are not correct, disconnect immediately and do not
|
||||
consume the tag</p>
|
||||
<p><b>6) </b> <i>Alice to Bob</i>: <br />
|
||||
<code>routerInfo + currentTime + H(routerInfo + currentTime + nonce + tag)</code></p>
|
||||
<p><b>7) </b> Bob should now verify that he can establish a connection to her through one of the
|
||||
routerAddresses specified in her RouterInfo. The testing process is described below.</p>
|
||||
<p><b>8) </b> <i>Bob to Alice</i>: <br />
|
||||
<code>routerInfo + status + properties + H(routerInfo + status + properties + nonce + tag)</code></p>
|
||||
<p><b>9) </b> If the <code>status</code> is okay, both Alice and Bob consume the
|
||||
<code>tagData</code>, updating the next tag to be <code>H(E(nonce + tag, sessionKey))</code>
|
||||
(with nonce+tag padded with 12 bytes of 0x0 at the end).
|
||||
Otherwise, both sides disconnect and do not consume the tag. In addition, on error the
|
||||
<code>properties</code> mapping has a more detailed reason under the key "MESSAGE".</p>
|
||||
|
||||
<ul>
|
||||
<li><code>H(x)</code> is the SHA256 hash of x, formatted per {@link net.i2p.data.Hash#writeBytes}.</li>
|
||||
<li><code>routerInfo</code> is the serialization of the local router's info
|
||||
per {@link net.i2p.data.RouterInfo#writeBytes}.</li>
|
||||
<li><code>currentTime</code> is what the local router thinks the current network time
|
||||
is, formatted per {@link net.i2p.data.DataHelper#writeDate}.</li>
|
||||
<li><code>status</code> is a 1 byte value:<ul>
|
||||
<li><b>0x0</b> means OK</li>
|
||||
<li><b>0x1</b> means Alice was not reachable</li>
|
||||
<li><b>0x2</b> means the clock was skewed (Bob's current time may be available
|
||||
in the properties mapping under "SKEW", formatted as "yyyyMMddhhmmssSSS",
|
||||
per {@link java.text.SimpleDateFormat}).</li>
|
||||
<li><b>0x3</b> means the signature is invalid (only used by steps 9 and 11 below)</li>
|
||||
<li>Other values are currently undefined (yet fatal) errors</li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
|
||||
<h2>Connection establishment without a valid tag:</h2>
|
||||
|
||||
<p><b>3) </b> <i>Alice to Bob</i> <br />
|
||||
X</p>
|
||||
<p><b>4) </b> <i>Bob to Alice</i> <br />
|
||||
Y</p>
|
||||
<p><b>5) </b> Both sides complete the Diffie-Hellman exchange, setting the
|
||||
<code>sessionKey</code> to the first 32 bytes of the result (e.g. (X^y mod p)),
|
||||
<code>iv</code> to the next 16 bytes, and the <code>nextTag</code> to the 32
|
||||
bytes after that. The rest of the data is AES256 encrypted with those settings per
|
||||
{@link net.i2p.crypto.AESInputStream} and {@link net.i2p.crypto.AESOutputStream}</p>
|
||||
<p><b>6) </b> <i>Alice to Bob</i> <br />
|
||||
<code>H(nonce)</code></p>
|
||||
<p><b>7) </b> <i>Bob to Alice</i> <br />
|
||||
<code>H(nextTag)</code></p>
|
||||
<p><b>8) </b> If they disagree, disconnect immediately and do not persist the tags or keys</p>
|
||||
<p><b>9) </b> <i>Alice to Bob</i> <br />
|
||||
<code>routerInfo + currentTime
|
||||
+ S(routerInfo + currentTime + nonce + nextTag, routerIdent.signingKey)</code></p>
|
||||
<p><b>10) </b> Bob should now verify that he can establish a connection to her through one of the
|
||||
routerAddresses specified in her RouterInfo. The testing process is described below.</p>
|
||||
<p><b>11) </b> <i>Bob to Alice</i> <br />
|
||||
<code>routerInfo + status + properties
|
||||
+ S(routerInfo + status + properties + nonce + nextTag, routerIdent.signingKey)</code></p>
|
||||
<p><b>12) </b> If the signature matches on both sides and <code>status</code> is okay, both sides
|
||||
save the <code>sessionKey</code> negotiated as well as the <code>nextTag</code>.
|
||||
Otherwise, the keys and tags are discarded and both sides drop the connection.</p>
|
||||
|
||||
<ul>
|
||||
<li><code>X</code> is a 256 byte unsigned integer in 2s complement, representing
|
||||
</code>g^x mod p</code> (where <code>g</code> and <code>p</code> are defined
|
||||
in {@link net.i2p.crypto.CryptoConstants} and x is a randomly chosen value</li>
|
||||
<li><code>Y</code> is a 256 byte unsigned integer in 2s complement, representing
|
||||
</code>g^y mod p</code> (where <code>g</code> and <code>p</code> are defined
|
||||
in {@link net.i2p.crypto.CryptoConstants} and y is a randomly chosen value</li>
|
||||
<li><code>S(val, key)</code> is the DSA signature of the <code>val</code> using the
|
||||
given signing <code>key</code> (in this case, the router's signing keys to provide
|
||||
authentication that they are who they say they are). The signature is formatted
|
||||
per {@link net.i2p.data.Signature}.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Peer testing</h2>
|
||||
<p>As mentioned in steps 7 and 10 above, Bob should verify that Alice is reachable
|
||||
to prevent a restricted route from being formed (he may decide not to do this once
|
||||
I2P supports restricted routes)</p>
|
||||
|
||||
<p><b>1) </b> <i>Bob to Alice</i> <br />
|
||||
<code>0xFFFF + #versions + v1 [+ v2 [etc]] + properties</p>
|
||||
<p><b>2) </b> <i>Alice to Bob</i> <br />
|
||||
<code>0xFFFF + versionOk + #bytesIP + IP + currentTime + properties</code></p>
|
||||
<p><b>3) </b> Both sides close the socket</p>
|
||||
|
||||
{% endblock %}
|
@ -379,7 +379,7 @@
|
||||
is the one who should receive a given message. The particulars of how routers
|
||||
communicate with other routers aren't critical - three separate protocols
|
||||
have been used at different points to provide those bare necessities. </p>
|
||||
<p> I2P started with a <a href="package-tcp.html">TCP-based protocol</a> which
|
||||
<p> I2P started with a TCP-based protocol which
|
||||
has since been disabled. Then, to accommodate the need for high degree communication
|
||||
(as a number of routers will end up speaking with many others), I2P moved
|
||||
from a TCP based transport to a <a href="udp.html">UDP-based one</a> - "Secure
|
||||
|
Reference in New Issue
Block a user