Freenet Client Protocol v1.2
Abstract
The FreenetClientProtocol (FCP) is designed to abstract the basics of Freenet so that client developers do not have to track the main Freenet protocol. FCP should be the bare bones of Freenet - metadata handling is not included in FCP though an extension to FCP may come about at a later date to avoid writing metadata handling libraries in many languages.
Note
This protocol is never meant to go across a network - only via the loopback. Nodes should not accept FCP connections from hosts other than localhost by default.
Basics
By default FCP is port 8481, but any client that uses FCP should leave this configurable, because this may be changed in the node's configuration file or by some future FCP revision.
FCP follows the FNP setup for session and presentation.
In the following, numbers are always hex-encoded and fields in square-brackets are optional.
FCP allows one transaction per connection, after which the connection is torn down. At the beginning of each connection, the client must send these 4 bytes:
00 00 00 02
These are the 2-byte session identifier and the 2-byte presentation identifier. In the future, different identifiers may be used to allow alternate syntaxes or encrypted FCP connections from remote hosts, for example.
After sending the session and presentation identifiers, the client sends a message to initiate the transaction, then waits for one or more messages from the node until the transaction is complete. Messages are a series of lines terminated by LF or CRLF, in this form:
Header [Field1=Value1] . . [FieldN=ValueN] EndMessage
Message Summary
This is the complete set of client to node messages, with the possible node to client responses (only the headers are listed).
ClientHelloNodeHello
ClientInfoNodeInfo
ClientGetURIErrorRestartedDataNotFoundRouteNotFoundDataFoundDataChunk
ClientPutURIErrorRestartedRouteNotFoundKeyCollisionPendingSuccess
GenerateCHKSuccess
GenerateSVKPairSuccess
ClientDeleteSuccess
InvertPrivateKeySuccess
GetSizeSuccess
Additionally, the node may respond to any client message with
a FormatError, meaning the command was not understood, and the
node may respond at any time with a Failed, indicating a fault
in the node itself:
(Node -> Client) FormatError [Reason=<descriptive string>] EndMessageFailed
(Node -> Client) Failed [Reason=<descriptive string>] EndMessage
Failed and FormatError will not be discussed
in the remainder of this document. Clients should be prepared to handle a Failed
at any time, and a FormatError as the response to any client message.
Either of these messages terminates the transaction and the connection.
Handshaking
This is totally optional for the client. Note that this counts as a transaction and thus the connection is torn down afterwards.
ClientHello(Client -> Node) ClientHello EndMessage
In response the node sends the following message:
NodeHello(Node -> Client) NodeHello Protocol=<number: protocol version number. Currently 1.2> Node=<string: Description of the node> [HighestSeenBuild=<number: Highest build seen in datastore>] MaxFileSize=<hex number: largest data size node will allow> EndMessage
The optional HighestSeenBuild will only be present
if a build higher than the node's current build is seen in the datastore. Client
implementors are advised, in this circumstance, to notify the user that they
should upgrade to the latest build of Freenet. The user should have the ability
to turn off this warning.
Querying the Node's Status
ClientInfo(Client -> Node) ClientInfo EndMessage
In response the node sends the following message:
NodeInfo(Node -> Client) NodeInfo Architecture=<string> OperatingSystem=<string: free form> OperatingSystemVersion=<string> Processors=<hex number> JavaVendor=<string: free form> JavaName=<string: free form> JavaVersion=<string> AllocatedMemory=<number> FreeMemory=<hex number> DatastoreMax=<hex number> DatastoreUsed=<hex number> DatastoreFree=<hex number> MaxFileSize=<hex number> MostRecentTimestamp=<hex number> LeastRecentTimestamp=<hex number> RoutingTime=<hex number> (0) AvailableThreads=<hex number> ActiveJobs=<hex number> NodePort=<hex number> NodeAddress=<string> EstimatedLoad=<hex number> EstimatedRateLimitingLoad=<hex number> IsTransient=<true|false> EndMessage
Note: This list was verified by running a client and seeing the fields and their values; older versions of this document may have listed different fields.
Requesting
ClientGet(Client -> Node) ClientGet RemoveLocalKey=<true|false> URI=<string: fully specified URI, such as freenet:=KSK@sample.txt> HopsToLive=<number: hops to live> EndMessage
The client is now in the waiting state. The node may return one of the following messages:
DataFound: The data has been found.DataNotFound: The transaction is terminated due to not being able to find data.RouteNotFound: The transaction is terminated due to not being able to find a route.Restarted: The client should continue waiting.URIError: Invalid Freenet URL. The transaction is terminated.
(Node -> Client) DataFound DataLength=<number: number of bytes of metadata + data> [MetadataLength=<number: default = 0, number of bytes of metadata>] [Timeout=<number: number of seconds by which the node will have a response>] EndMessage
After a DataFound message the data itself is sent in chunks:
(Node -> Client) DataChunk Length=<number: number of bytes in trailing field> Data <@Length bytes of data>
At any time when the full payload of data has not been
sent (even before the DataFound message), a
Restarted message may be sent. This means that the data
failed to verify and the transfer will be restarted. The client
should disregard all data it has recieved since it made its initial
request and return to waiting for a DataFound message
to begin the data transfer again. Otherwise, when the final
DataChunk is received, the transaction is complete and
the connection dies.
Be aware that there may be a DataChunk that contains both meta and key data.
In this case, care is needed to extract the metadata first, then the keydata before
retrieving another DataChunk or the operation will appear to hang.
(Node -> Client) RouteNotFound Unreachable=<number: default = 0, nodes> Restarted=<number: default = 0, nodes> Rejected=<number: default = 0, nodes> BackedOff=<number: default = 0, nodes> EndMessage
RouteNotFound carries some extra fields as above, the
other errors (DataNotFound and URIError)
simply terminate the connection. In a successful Request, a
DataFound message is returned:
Inserting
ClientPut(Client -> Node) ClientPut RemoveLocalKey=<true|false> HopsToLive=<number: hops to live> URI=<string: fully specified URI, such as freenet:=KSK@sample.txt> DataLength=<number: number of bytes of metadata + data> [MetadataLength=<number: default = 0, number of bytes of metadata>] Data <@DataLength number of bytes>
If the client is inserting a CHK, the URI may be abbreviated as just CHK@. In this case, the node will calculate the CHK. The node must get all of the trailing field before it can start the insert into Freenet. The node may reply with one of the following messages:
URIError: Invalid Freenet URL. The transaction is terminated.Restarted: The client should continue waiting.RouteNotFound: The transaction is terminated due to not being able to find a route.KeyCollision: The transaction is terminated due to a document with the same key already existing in Freenet. This message contains a URI field with the Freenet URI of the document.SizeError: The transaction is terminated due to the data being too large for the key type; all non-CHK keys have a limit of 32 kB of data.
During an insertion, multiple Pending messages may be returned.
These messages signal that the data is being successfully inserted, but insertion
is not complete, and the node has not received a StoreData message
yet:
(Node -> Client) Pending URI=<string: fully specified URI, such as freenet:=KSK@sample.txt> Timeout=<number: in milliseconds> [PublicKey=<string: public key>] [PrivateKey=<string: private key>] EndMessage
When the node receives a StoreData message (and thus
insertion is complete), a Success message is returned with the
Freenet URI of the new document and possibly a private/public keypair, if the
inserted document was an SVK. See the section on key generation about this.
(Node -> Client) Success URI=<string: fully specified URI, such as freenet:=KSK@sample.txt> [PublicKey=<string: public key>] [PrivateKey=<string: private key>] EndMessage
Key Operations
These messages allow a client to generate keys. This does not affect Freenet at all - the calculations are carried out at the node.
Key generation requests are done via a
GenerateKey message. Either a CHK or an SVK keypair can
be generated:
(Client -> Node) GenerateCHK DataLength=number: number of bytes of data + metadata> [MetadataLength=<number: default = 0, number of bytes of metadata>] Data <@DataLength number of bytes>
The node calculates the CHK as it would do if inserting, but instead returns it. This completes the transaction:
Success(Node -> Client) Success URI=<string: fully specified URI, such as freenet:=KSK@sample.txt> EndMessageGenerateSVKPair
The format for generating SVKs is very similar but generates a pair of keys (public and private) which are independent of any data. This is generally used for setting up SSKs:
(Client -> Node) GenerateSVKPair EndMessage
The node generates a key pair and returns:
Success(Node -> Client) Success PublicKey=<string: public Freenet key> PrivateKey=<string: private Freenet key> CryptoKey=<string: entropy for encryption> EndMessage
The public and private keys are returned as Freenet-base64 encoded strings. These can be used to construct URIs for inserting SSKs:
(insert) freenet:SSK@<PrivateKey>,<CryptoKey>/<name>
Note: the public key is not sufficient to request SSKs as the private key is for inserting. Attempting to use the public key as follows:
(request) freenet:SSK@<PublicKey>,<CryptoKey>/<name>
will result in an invalid key error; there are bits added to the
end of the public key that are necessary for generating a proper
request. The only way to be sure that your request key is correct is
to use the return value of GenerateCHK (for CHKs) or the
Pending/Success response on insert.
The use of CryptoKey in URIs is optional, but it will protect your data from being decrypted by a dictionary attack against the name part of the key.
InvertPrivateKey(Client -> Node) InvertPrivateKey Private=<PrivateKey> EndMessageSuccess
(Node -> Client) Success Public=<PublicKey> EndMessage
Private can be either an insert URI (must start with
freenet:SSK@) or a raw private key (ie the private value you get back
from GenerateSVKPair).
A Success message is sent on success with
the Public field set to the public key.
(Client -> Node) GetSize URI=<string: fully specified URI, such as freenet:=KSK@sample.txt> EndMessageSuccess
(Node -> Client) Success Length=<number: size of document rounded up to the nearest power of two.< EndMessage
This command is useful for working with splitfiles, so that one can pre-allocate a buffer for the entire file and know where each part of the splitfile goes inside the buffer, knowing their lengths.
One can implement an equivalent function to this for just CHKs by base64 decoding the CHK routing key (the part before the ',') and inspecting the 3rd to last byte; it's the log2 size of the entire document. Be careful with your base64 decoder, freenet uses a different table than the standard one.


