SAM for beginngers blog
This commit is contained in:
605
i2p2www/blog/2019/06/23/sam-library-basics.rst
Normal file
605
i2p2www/blog/2019/06/23/sam-library-basics.rst
Normal file
@ -0,0 +1,605 @@
|
|||||||
|
=============================================================
|
||||||
|
{% trans -%}So You Want To Write A SAM Library{%- endtrans %}
|
||||||
|
=============================================================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:author: idk
|
||||||
|
:date: 2019-06-23
|
||||||
|
:excerpt: {% trans %}Beginners guide to writing a SAM library!{% endtrans %}
|
||||||
|
|
||||||
|
*Or, talking to*\ `i2p <https://geti2p.net>`__\ *for people who aren't really used to reading specs*
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
One of the best features of I2P, in my opinion, is it's SAM API, which can be
|
||||||
|
used to build a bridge between I2P and your application or language of choice.
|
||||||
|
Currently, dozens of SAM libraries exist for a variety of languages, including:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
- `i2psam, for c++ <https://github.com/i2p/i2psam>`__
|
||||||
|
- `libsam3, for C <https://github.com/i2p/libsam3>`__
|
||||||
|
- `txi2p for Python <https://github.com/str4d/txi2p>`__
|
||||||
|
- `i2plib for Python <https://github.com/l-n-s/i2plib>`__
|
||||||
|
- `i2p.socket for Python <https://github.com/majestrate/i2p.socket>`__
|
||||||
|
- `leaflet for Python <https://github.com/MuxZeroNet/leaflet>`__
|
||||||
|
- `gosam, for Go <https://github.com/eyedeekay/gosam>`__
|
||||||
|
- `sam3 for Go <https://github.com/eyedeekay/sam3>`__
|
||||||
|
- `node-i2p for nodejs <https://github.com/redhog/node-i2p>`__
|
||||||
|
- `haskell-network-anonymous-i2p <https://github.com/solatis/haskell-network-anonymous-i2p>`__
|
||||||
|
- `i2pdotnet for .Net languages <https://github.com/SamuelFisher/i2pdotnet>`__
|
||||||
|
- `rust-i2p <https://github.com/stallmanifold/rust-i2p>`__
|
||||||
|
- `and i2p.rb for ruby <https://github.com/dryruby/i2p.rb>`__
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
If you're using any of these languages, you may be able to port your application
|
||||||
|
to I2P already, using an existing library. That's not what this tutorial is
|
||||||
|
about, though. This tutorial is about what to do if you want to create a SAM
|
||||||
|
library in a new language. In this tutorial, I will implement a new SAM library
|
||||||
|
in Java. I chose Java because there isn't a Java library that connects you to
|
||||||
|
SAM yet, because of Java's use in Android, and because it's a language almost
|
||||||
|
everybody has at least a *little* experience with, so hopefully you can
|
||||||
|
translate it into a language of your choice.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
{% trans -%}Creating your library{%- endtrans %}
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
How you set up your own library will vary depending on the language you wish
|
||||||
|
to use. For this example library, we'll be using java so we can create a library
|
||||||
|
like this:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: sh
|
||||||
|
|
||||||
|
mkdir jsam
|
||||||
|
cd jsam
|
||||||
|
gradle init --type java-library
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Or, if you are using gradle 5 or greater:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: sh
|
||||||
|
|
||||||
|
gradle init --type java-library --project-name jsam
|
||||||
|
|
||||||
|
{% trans -%}Setting up the Library{%- endtrans %}
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
There are a few pieces of data that almost any SAM library should probably
|
||||||
|
manage. It will at least need to store the address of the SAM Bridge you intend
|
||||||
|
to use and the signature type you wish to use.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
{% trans -%}Storing the SAM address{%- endtrans %}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
I prefer to store the SAM address as a String and an Integer, and re-combine
|
||||||
|
them in a function at runtime.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public String SAMHost = "127.0.0.1";
|
||||||
|
public int SAMPort = 7656;
|
||||||
|
public String SAMAddress(){
|
||||||
|
return SAMHost + ":" + SAMPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}Storing the Signature Type{%- endtrans %}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
The valid signature types for an I2P Tunnel are DSA_SHA1, ECDSA_SHA256_P256,
|
||||||
|
ECDSA_SHA384_P384, ECDSA_SHA512_P521, EdDSA_SHA512_Ed25519, but it is
|
||||||
|
strongly recommended that you use EdDSA_SHA512_Ed25519 as a default if you
|
||||||
|
implement at least SAM 3.1. In java, the 'enum' datastructure lends itself to
|
||||||
|
this task, as it is intended to contain a group of constants. Add the enum, and
|
||||||
|
an instance of the enum, to your java class definition.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
enum SIGNATURE_TYPE {
|
||||||
|
DSA_SHA1,
|
||||||
|
ECDSA_SHA256_P256,
|
||||||
|
ECDSA_SHA384_P384,
|
||||||
|
ECDSA_SHA512_P521,
|
||||||
|
EdDSA_SHA512_Ed25519;
|
||||||
|
}
|
||||||
|
public SIGNATURE_TYPE SigType = SIGNATURE_TYPE.EdDSA_SHA512_Ed25519;
|
||||||
|
|
||||||
|
{% trans -%}Retrieving the signature type:{%- endtrans %}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
That takes care of reliably storing the signature type in use by the SAM
|
||||||
|
connection, but you've still got to retrieve it as a string to communicate it
|
||||||
|
to the bridge.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public String SignatureType() {
|
||||||
|
switch (SigType) {
|
||||||
|
case DSA_SHA1:
|
||||||
|
return "SIGNATURE_TYPE=DSA_SHA1";
|
||||||
|
case ECDSA_SHA256_P256:
|
||||||
|
return "SIGNATURE_TYPE=ECDSA_SHA256_P256";
|
||||||
|
case ECDSA_SHA384_P384:
|
||||||
|
return "SIGNATURE_TYPE=ECDSA_SHA384_P384";
|
||||||
|
case ECDSA_SHA512_P521:
|
||||||
|
return "SIGNATURE_TYPE=ECDSA_SHA512_P521";
|
||||||
|
case EdDSA_SHA512_Ed25519:
|
||||||
|
return "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
It's important to test things, so let's write some tests:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
@Test public void testValidDefaultSAMAddress() {
|
||||||
|
Jsam classUnderTest = new Jsam();
|
||||||
|
assertEquals("127.0.0.1:7656", classUnderTest.SAMAddress());
|
||||||
|
}
|
||||||
|
@Test public void testValidDefaultSignatureType() {
|
||||||
|
Jsam classUnderTest = new Jsam();
|
||||||
|
assertEquals("EdDSA_SHA512_Ed25519", classUnderTest.SignatureType());
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Once that's done, begin creating your constructor. Note that we've given our
|
||||||
|
library defaults which will be useful in default situations on all existing I2P
|
||||||
|
routers so far.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public Jsam(String host, int port, SIGNATURE_TYPE sig) {
|
||||||
|
SAMHost = host;
|
||||||
|
SAMPort = port;
|
||||||
|
SigType = sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}Establishing a SAM Connection{%- endtrans %}
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Finally, the good part. Interaction with the SAM bridge is done by sending a
|
||||||
|
"command" to the address of the SAM bridge, and you can parse the result of the
|
||||||
|
command as a set of string-based key-value pairs. So bearing that in mind, let's
|
||||||
|
estabish a read-write connection to the SAM Address we defined before, then
|
||||||
|
write a "CommandSAM" Function and a reply parser.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
{% trans -%}Connecting to the SAM Port{%- endtrans %}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
We're communicating with SAM via a Socket, so in order to connect to, read from,
|
||||||
|
and write to the socket, you'll need to create the following private variables
|
||||||
|
in the Jsam class:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
private PrintWriter writer;
|
||||||
|
private BufferedReader reader;
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
You will also want to instantiate those variables in your Constructors by
|
||||||
|
creating a function to do so.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public Jsam(String host, int port, SIGNATURE_TYPE sig) {
|
||||||
|
SAMHost = host;
|
||||||
|
SAMPort = port;
|
||||||
|
SigType = sig;
|
||||||
|
startConnection();
|
||||||
|
}
|
||||||
|
public void startConnection() {
|
||||||
|
try {
|
||||||
|
socket = new Socket(SAMHost, SAMPort);
|
||||||
|
writer = new PrintWriter(socket.getOutputStream(), true);
|
||||||
|
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
//omitted for brevity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}Sending a Command to SAM{%- endtrans %}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Now you're all set up to finally start talking to SAM. In order to keep things
|
||||||
|
nicely organized, let's create a function which sends a single command to SAM,
|
||||||
|
terminated by a newline, and which returns a Reply object, which we will create
|
||||||
|
in the next step:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public Reply CommandSAM(String args) {
|
||||||
|
writer.println(args + "\n");
|
||||||
|
try {
|
||||||
|
String repl = reader.readLine();
|
||||||
|
return new Reply(repl);
|
||||||
|
} catch (Exception e) {
|
||||||
|
//omitted for brevity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Note that we are using the writer and reader we created from the socket in the
|
||||||
|
previous step as our inputs and outputs to the socket. When we get a reply from
|
||||||
|
the reader, we pass the string to the Reply constructor, which parses it and
|
||||||
|
returns the Reply object.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. _parsing-a-reply-and-creating-a-reply-object:
|
||||||
|
|
||||||
|
{% trans -%}Parsing a reply and creating a Reply object.{%- endtrans %}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
In order to more easily handle replies, we'll use a Reply object to
|
||||||
|
automatically parse the results we get from the SAM bridge. A reply has at least
|
||||||
|
a topic, a type, and a result, as well as an arbitrary number of key-value
|
||||||
|
pairs.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public class Reply {
|
||||||
|
String topic;
|
||||||
|
String type;
|
||||||
|
REPLY_TYPES result;
|
||||||
|
Map<String, String> replyMap = new HashMap<String, String>();
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
As you can see, we will be storing the "result" as an enum, REPLY_TYPES. This
|
||||||
|
enum contains all the possible reply results which the SAM bridge might respond
|
||||||
|
with.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
enum REPLY_TYPES {
|
||||||
|
OK,
|
||||||
|
CANT_REACH_PEER,
|
||||||
|
DUPLICATED_ID,
|
||||||
|
DUPLICATED_DEST,
|
||||||
|
I2P_ERROR,
|
||||||
|
INVALID_KEY,
|
||||||
|
KEY_NOT_FOUND,
|
||||||
|
PEER_NOT_FOUND,
|
||||||
|
TIMEOUT;
|
||||||
|
public static REPLY_TYPES set(String type) {
|
||||||
|
String temp = type.trim();
|
||||||
|
switch (temp) {
|
||||||
|
case "RESULT=OK":
|
||||||
|
return OK;
|
||||||
|
case "RESULT=CANT_REACH_PEER":
|
||||||
|
return CANT_REACH_PEER;
|
||||||
|
case "RESULT=DUPLICATED_ID":
|
||||||
|
return DUPLICATED_ID;
|
||||||
|
case "RESULT=DUPLICATED_DEST":
|
||||||
|
return DUPLICATED_DEST;
|
||||||
|
case "RESULT=I2P_ERROR":
|
||||||
|
return I2P_ERROR;
|
||||||
|
case "RESULT=INVALID_KEY":
|
||||||
|
return INVALID_KEY;
|
||||||
|
case "RESULT=KEY_NOT_FOUND":
|
||||||
|
return KEY_NOT_FOUND;
|
||||||
|
case "RESULT=PEER_NOT_FOUND":
|
||||||
|
return PEER_NOT_FOUND;
|
||||||
|
case "RESULT=TIMEOUT":
|
||||||
|
return TIMEOUT;
|
||||||
|
}
|
||||||
|
return I2P_ERROR;
|
||||||
|
}
|
||||||
|
public static String get(REPLY_TYPES type) {
|
||||||
|
switch (type) {
|
||||||
|
case OK:
|
||||||
|
return "RESULT=OK";
|
||||||
|
case CANT_REACH_PEER:
|
||||||
|
return "RESULT=CANT_REACH_PEER";
|
||||||
|
case DUPLICATED_ID:
|
||||||
|
return "RESULT=DUPLICATED_ID";
|
||||||
|
case DUPLICATED_DEST:
|
||||||
|
return "RESULT=DUPLICATED_DEST";
|
||||||
|
case I2P_ERROR:
|
||||||
|
return "RESULT=I2P_ERROR";
|
||||||
|
case INVALID_KEY:
|
||||||
|
return "RESULT=INVALID_KEY";
|
||||||
|
case KEY_NOT_FOUND:
|
||||||
|
return "RESULT=KEY_NOT_FOUND";
|
||||||
|
case PEER_NOT_FOUND:
|
||||||
|
return "RESULT=PEER_NOT_FOUND";
|
||||||
|
case TIMEOUT:
|
||||||
|
return "RESULT=TIMEOUT";
|
||||||
|
}
|
||||||
|
return "RESULT=I2P_ERROR";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Now let's create our constructor, which takes the reply string recieved from the
|
||||||
|
socket as a parameter, parses it, and uses the information to set up the reply
|
||||||
|
object. The reply is space-delimited, with key-value pairs joined by an equal
|
||||||
|
sign and terminated by a newline.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public Reply(String reply) {
|
||||||
|
String trimmed = reply.trim();
|
||||||
|
String[] replyvalues = reply.split(" ");
|
||||||
|
if (replyvalues.length < 2) {
|
||||||
|
//omitted for brevity
|
||||||
|
}
|
||||||
|
topic = replyvalues[0];
|
||||||
|
type = replyvalues[1];
|
||||||
|
result = REPLY_TYPES.set(replyvalues[2]);
|
||||||
|
|
||||||
|
String[] replyLast = Arrays.copyOfRange(replyvalues, 2, replyvalues.length);
|
||||||
|
for (int x = 0; x < replyLast.length; x++) {
|
||||||
|
String[] kv = replyLast[x].split("=", 2);
|
||||||
|
if (kv.length != 2) {
|
||||||
|
|
||||||
|
}
|
||||||
|
replyMap.put(kv[0], kv[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Lastly, for the sake of convenience, let's give the reply object a toString()
|
||||||
|
function which returns a string representation of the Reply object.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return topic + " " + type + " " + REPLY_TYPES.get(result) + " " + replyMap.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}Saying "HELLO" to SAM{%- endtrans %}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Now we're ready to establish communication with SAM by sending a "Hello"
|
||||||
|
message. If you're writing a new SAM library, you should probably target at
|
||||||
|
least SAM 3.1, since it's available in both I2P and i2pd and introduces support
|
||||||
|
for the SIGNATURE_TYPE parameter.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public boolean HelloSAM() {
|
||||||
|
Reply repl = CommandSAM("HELLO VERSION MIN=3.0 MAX=3.1 \n");
|
||||||
|
if (repl.result == Reply.REPLY_TYPES.OK) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
System.out.println(repl.String());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
As you can see, we use the CommandSAM function we created before to send the
|
||||||
|
newline-terminated command ``HELLO VERSION MIN=3.0 MAX=3.1 \n``. This tells
|
||||||
|
SAM that you want to start communicating with the API, and that you know how
|
||||||
|
to speak SAM version 3.0 and 3.1. The router, in turn, will respond with
|
||||||
|
like ``HELLO REPLY RESULT=OK VERSION=3.1`` which is a string you can pass to
|
||||||
|
the Reply constructor to get a valid Reply object. From now on, we can use our
|
||||||
|
CommandSAM function and Reply object to deal with all our communication across
|
||||||
|
the SAM bridge.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Finally, let's add a test for our "HelloSAM" function.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
@Test public void testHelloSAM() {
|
||||||
|
Jsam classUnderTest = new Jsam();
|
||||||
|
assertTrue("HelloSAM should return 'true' in the presence of an alive SAM bridge", classUnderTest.HelloSAM());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Creating a "Session" for your application
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Now that you've negotiated your connection to SAM and agreed on a SAM version
|
||||||
|
you both speak, you can set up peer-to-peer connections for your application
|
||||||
|
to connect to other i2p applications. You do this by sending a "SESSION CREATE"
|
||||||
|
command to the SAM Bridge. To do that, we'll use a CreateSession function that
|
||||||
|
accepts a session ID and a destination type parameter.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public String CreateSession(String id, String destination ) {
|
||||||
|
if (destination == "") {
|
||||||
|
destination = "TRANSIENT";
|
||||||
|
}
|
||||||
|
Reply repl = CommandSAM("SESSION CREATE STYLE=STREAM ID=" + ID + " DESTINATION=" + destination);
|
||||||
|
if (repl.result == Reply.REPLY_TYPES.OK) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
That was easy, right? All we had to do was adapt the pattern we used in our
|
||||||
|
HelloSAM function to the ``SESSION CREATE`` command. A good reply from the
|
||||||
|
bridge will still return OK, and in that case we return the ID of the newly
|
||||||
|
created SAM connection. Otherwise, we return an empty string because that's an
|
||||||
|
invalid ID anyway and it failed, so it's easy to check. Let's see if this
|
||||||
|
function works by writing a test for it:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
@Test public void testCreateSession() {
|
||||||
|
Jsam classUnderTest = new Jsam();
|
||||||
|
assertTrue("HelloSAM should return 'true' in the presence of an alive SAM bridge", classUnderTest.HelloSAM());
|
||||||
|
assertEquals("test", classUnderTest.CreateSession("test", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Note that in this test, we *must* call HelloSAM first to establish communication
|
||||||
|
with SAM before starting our session. If not, the bridge will reply with an
|
||||||
|
error and the test will fail.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. _looking-up-hosts-by-name-or-b32:
|
||||||
|
|
||||||
|
Looking up Hosts by name or .b32
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Now you have your session established and your local destination, and need to
|
||||||
|
decide what you want to do with them. Your session can now be commanded to
|
||||||
|
connect to a remote service over I2P, or to wait for incoming connections to
|
||||||
|
respond to. However, before you can connect to a remote destination, you may
|
||||||
|
need to obtain the base64 of the destination, which is what the API expects. In
|
||||||
|
order to do this, we'll create a LookupName function, which will return the
|
||||||
|
base64 in a usable form.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public String LookupName(String name) {
|
||||||
|
String cmd = "NAMING LOOKUP NAME=" + name + "\n";
|
||||||
|
Reply repl = CommandSAM(cmd);
|
||||||
|
if (repl.result == Reply.REPLY_TYPES.OK) {
|
||||||
|
System.out.println(repl.replyMap.get("VALUE"));
|
||||||
|
return repl.replyMap.get("VALUE");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Again, this is almost the same as our HelloSAM and CreateSession functions,
|
||||||
|
with one difference. Since we're looking for the VALUE specifically and the NAME
|
||||||
|
field will be the same as the ``name`` argument, it simply returns the base64
|
||||||
|
string of the destination requested.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Now that we have our LookupName function, let's test it:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
@Test public void testLookupName() {
|
||||||
|
Jsam classUnderTest = new Jsam();
|
||||||
|
assertTrue("HelloSAM should return 'true' in the presence of an alive SAM bridge", classUnderTest.HelloSAM());
|
||||||
|
assertEquals("8ZAW~KzGFMUEj0pdchy6GQOOZbuzbqpWtiApEj8LHy2~O~58XKxRrA43cA23a9oDpNZDqWhRWEtehSnX5NoCwJcXWWdO1ksKEUim6cQLP-VpQyuZTIIqwSADwgoe6ikxZG0NGvy5FijgxF4EW9zg39nhUNKRejYNHhOBZKIX38qYyXoB8XCVJybKg89aMMPsCT884F0CLBKbHeYhpYGmhE4YW~aV21c5pebivvxeJPWuTBAOmYxAIgJE3fFU-fucQn9YyGUFa8F3t-0Vco-9qVNSEWfgrdXOdKT6orr3sfssiKo3ybRWdTpxycZ6wB4qHWgTSU5A-gOA3ACTCMZBsASN3W5cz6GRZCspQ0HNu~R~nJ8V06Mmw~iVYOu5lDvipmG6-dJky6XRxCedczxMM1GWFoieQ8Ysfuxq-j8keEtaYmyUQme6TcviCEvQsxyVirr~dTC-F8aZ~y2AlG5IJz5KD02nO6TRkI2fgjHhv9OZ9nskh-I2jxAzFP6Is1kyAAAA", classUnderTest.LookupName("i2p-projekt.i2p"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Sending and Recieving Information
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
At last, we are going to establish a connection to another service with our new
|
||||||
|
library. This part confused me a bit at first, but the most astute Java
|
||||||
|
developers were probably wondering why we didn't extend the socket class
|
||||||
|
instead of creating a Socket variable inside of the Jsam class. That's because
|
||||||
|
until now, we've been communicating with the "Control Socket" and we need to
|
||||||
|
create a new socket to do the actual communication. So we've waited to extend
|
||||||
|
the the Socket class with the Jsam class until now:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public class Jsam extends Socket {
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
Also, let's alter our startConnection function so that we can use it to switch
|
||||||
|
over from the control socket to the socket we'll be using in our application. It
|
||||||
|
will now take a Socket argument.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public void startConnection(Socket socket) {
|
||||||
|
try {
|
||||||
|
socket.connect(new InetSocketAddress(SAMHost, SAMPort), 600 );
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
writer = new PrintWriter(socket.getOutputStream(), true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
This allows us to quickly and easily open a new socket to communicate over,
|
||||||
|
perform the "Hello SAM" handshake over again, and connect the stream.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public String ConnectSession(String id, String destination) {
|
||||||
|
startConnection(this);
|
||||||
|
HelloSAM();
|
||||||
|
if (destination.endsWith(".i2p")) {
|
||||||
|
destination = LookupName(destination);
|
||||||
|
}
|
||||||
|
String cmd = "STREAM CONNECT ID=" + id + " DESTINATION=" + destination + " SILENT=false";
|
||||||
|
Reply repl = CommandSAM(cmd);
|
||||||
|
if (repl.result == Reply.REPLY_TYPES.OK) {
|
||||||
|
System.out.println(repl.String());
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
System.out.println(repl.String());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
And now you have a new Socket for communicating over SAM! Let's do the same
|
||||||
|
thing for Accepting remote connections:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
.. code:: java
|
||||||
|
|
||||||
|
public String AcceptSession(String id) {
|
||||||
|
startConnection(this);
|
||||||
|
HelloSAM();
|
||||||
|
String cmd = "STREAM ACCEPT ID=" + id + " SILENT=false";
|
||||||
|
Reply repl = CommandSAM(cmd);
|
||||||
|
if (repl.result == Reply.REPLY_TYPES.OK) {
|
||||||
|
System.out.println(repl.String());
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
System.out.println(repl.String());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
There you have it. That's how you build a SAM library, step-by-step. In the
|
||||||
|
future, I will cross-reference this with the working version of the library,
|
||||||
|
Jsam, and the SAM v3 specification but for now I've got to get some other stuff
|
||||||
|
done.
|
||||||
|
{%- endtrans %}
|
||||||
|
|
Reference in New Issue
Block a user