• Gönderilme: 2019-06-23
  • Yazar: idk

Or, talking toi2pfor people who aren't really used to reading specs

Bence I2P ile ilgili en iyi özelliklerden biri, I2P ağı ile uygulamanız veya seçtiğiniz programlama dili arasında bir köprü oluşturmak için kullanılabilecek SAM API uygulamasıdır. Şu anda, aşağıdakilerle birlikte çeşitli diller için düzinelerce SAM kitaplığı bulunmaktadır:

Bu dillerden herhangi birini kullanıyorsanız, uygulamanızı var olan bir kitaplığı kullanarak I2P üzerine taşıyabilirsiniz. Yine de bu eğitimin konusu bu değil. Bu eğitimde, yeni bir dilde bir SAM kitaplığı oluşturmak istiyorsanız ne yapacağınızı anlatacağım. Bu derste Java ile yeni bir SAM kitaplığı oluşturacağım. Android üzerinde kullanıldığından ve neredeyse herkesin az çok deneyimi olduğundan Java dilini seçtim çünkü henüz sizi SAM üzerine bağlayan bir Java kitaplığı yok. Umarım bu örneği seçeceğiniz başka bir dile çevirebilirsiniz.

Kitaplığınızı oluşturmak

Kendi kitaplığınızı nasıl kuracağınız, kullanmak istediğiniz dile bağlı olarak değişir. Bu örnek kitaplık için java kullanacağız, böylece şöyle bir kitaplık oluşturabiliriz:

mkdir jsam
cd jsam
gradle init --type java-library

Ya da gradle 5 veya daha üzerini kullanıyorsanız:

gradle init --type java-library --project-name jsam

Kitaplığı kurmak

Neredeyse tüm SAM kitaplıklarının yönetmesi gereken birkaç veri parçası vardır. En azından kullanmayı düşündüğünüz SAM köprüsünün adresini ve kullanmak istediğiniz imza türünü kaydetmesi gerekecektir.

SAM adresini kaydetmek

SAM adresini bir dizge ve tamsayı olarak saklamayı ve bunları runtime içinde bir işlevde yeniden birleştirmeyi yeğliyorum.

public String SAMHost = "127.0.0.1";
public int SAMPort = 7656;
public String SAMAddress(){
    return SAMHost + ":" + SAMPort;
}

İmza türünü kaydetmek

Bir I2P tüneli için geçerli imza türleri DSA_SHA1, ECDSA_SHA256_P256, ECDSA_SHA384_P384, ECDSA_SHA512_P521, EdDSA_SHA512_Ed25519 şeklindedir. Ancak en azından SAM uygularsanız, varsayılan olarak EdDSA_SHA512_Ed25519 kullanmanız şiddetle önerilir. Java üzerinde 'enum' veri yapısı, bir sabitler grubu içermesi amaçlandığından bu göreve uygundur. Java sınıfı tanımınıza enum ve bir enum kopyasını ekleyin.

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;

İmza türünü almak:

SAM bağlantısı tarafından kullanılan imza türünü güvenilir bir şekilde saklamayı sağlar. Ancak yine de onu köprüye iletmek için bir dizge olarak almanız gerekir.

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 "";
}

Bir şeyleri denemek önemlidir. Bu yüzden bazı deneme kodları yazalım:

@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());
}

Bu yapıldıktan sonra, kurucunuzu oluşturmaya başlayın. Şu ana kadar var olan tüm I2P yönelticilerinde varsayılan durumlarda faydalı olacak kitaplık varsayılanlarımızı verdiğimizi unutmayın.

public Jsam(String host, int port, SIGNATURE_TYPE sig) {
    SAMHost = host;
    SAMPort = port;
    SigType = sig;
}

SAM bağlantısı kurmak

Son olarak, iyi bölüm. SAM köprüsüyle etkileşim, SAM köprüsünün adresine bir "komut" gönderilerek yapılır ve komutun sonucunu bir dizi dizge tabanlı anahtar/değer çifti olarak ayrıştırabilirsiniz. Bunu akılda tutarak, daha önce tanımladığımız SAM adresine bir okuma-yazma bağlantısı kuralım. Ardından bir "CommandSAM" işlevi ve bir yanıt işleyici yazalım.

SAM bağlantı noktasına bağlanmak

SAM ile bir Soket aracılığıyla iletişim kuruyoruz. Bu nedenle sokete bağlanmak, soketten okumak ve sokete yazmak için Jsam sınıfında aşağıdaki özel değişkenleri oluşturmanız gerekir:

private Socket socket;
private PrintWriter writer;
private BufferedReader reader;

Ayrıca, bunu yapmak için bir işlev oluşturarak Yapıcılarınızda bu değişkenleri başlatmak isteyeceksiniz.

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
    }
}

SAM üzerine bir komut göndermek

Artık SAM ile konuşmaya başlamaya hazırsınız. İşleri güzel bir şekilde düzenlemek için, SAM üzerine tek bir komut gönderen, bir satır sonu ile tamalanan ve bir sonraki adımda oluşturacağımız bir yanıt nesnesi döndüren bir işlev oluşturalım:

public Reply CommandSAM(String args) {
    writer.println(args + "\n");
    try {
        String repl = reader.readLine();
        return new Reply(repl);
    } catch (Exception e) {
        //omitted for brevity
    }
}

Bir önceki adımda soketten oluşturduğumuz yazıcı ve okuyucuyu, sokete giriş ve çıkışlarımız olarak kullandığımızı unutmayın. Okuyucudan bir yanıt aldığımızda, dizgeyi, onu ayrıştıran ve yanıt nesnesini döndüren yanıt yapıcısına iletiriz.

Bir yanıtı işlemek ve bir yanıt nesnesi oluşturmak.

Yalnızca sistem taraından veya tüm kullanıcılara gönderilen iletilerin yönetilebileceğini unutmayın. Gönderildikten sonra bu iletiyi buradan yönetemezsiniz.

public class Reply {
    String topic;
    String type;
    REPLY_TYPES result;
    Map<String, String> replyMap = new HashMap<String, String>();

Gördüğünüz gibi, "sonucu" REPLY_TYPES enum olarak saklayacağız. Bu enum, SAM köprüsünün verebileceği tüm olası yanıt sonuçlarını içerir.

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";
    }
};

Şimdi, soketten alınan yanıt dizisini parametre olarak alan, onu işleyen ve yanıt nesnesini kurmak için bilgiyi kullanan yapıcımızı oluşturalım. Yanıt boşlukla sınırlandırılır, anahtar/değer çiftleri eşittir işaretiyle birleştirilir ve yeni satır karakteriyle sonlandırılır.

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]);
    }
}

Son olarak, kolaylık olması açısından, yanıt nesnesine, yenıt nesnesinin dizge gösterimini döndüren bir toString() işlevi verelim.

    public String toString() {
        return topic + " " + type + " " + REPLY_TYPES.get(result) + " " + replyMap.toString();
    }
}

SAM programına "Merhaba" demek

Artık "Merhaba" iletisi göndererek SAM ile iletişim kurmaya hazırız. Yeni bir SAM kitaplığı yazıyorsanız, hem I2P hem de i2pd üzerinde var olduğundan ve SIGNATURE_TYPE parametresi için destek sunduğundan, büyük olasılıkla en azından SAM 3.1 sürümünü hedeflemelisiniz.

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;
}

Gördüğünüz gibi, yeni satır karakteriyle biten HELLO VERSION MIN=3.0 MAX=3.1 \n komutunu göndermek için daha önce oluşturduğumuz CommandSAM işlevini kullanıyoruz. Bu, SAM yazılımına API ile iletişim kurmaya başlamak istediğinizi ve SAM sürüm 3.0 ve 3.1 ile nasıl konuşacağınızı bildiğinizi söyler. Yöneltici sırayla HELLO REPLY RESULT=OK VERSION=3.1 gibi yanıt verir; bu, geçerli bir yanıt nesnesi almak için yanıt oluşturucuya iletebileceğiniz bir dizgedir. Şu andan başlayarak, SAM köprüsü üzerindeki tüm iletişimimizle ilgilenmek için CommandSAM işlevimizi ve yanıt nesnemizi kullanabiliriz.

Son olarak "HelloSAM" işlevimiz için bir deneme ekleyelim.

@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

Artık SAM ile bağlantı kurmak için anlaştığınıza ve ikinizin de konuştuğu bir SAM sürümü seçtiğinize göre, uygulamanızın diğer i2p uygulamaları ile bağlantı kurması için eşler arası bağlantılar kurabilirsiniz. Bunu, SAM köprüsüne bir "SESSION CREATE" komutu göndererek yaparsınız. Bunu yapmak için, bir oturum kimliği ve bir hedef türü parametresi kabul eden bir CreateSession işlevi kullanacağız.

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 "";
}

Oldukça kolay değil mi? Tek yapmamız gereken HelloSAM işlevinde kullandığımız kalıbı SESSION CREATE komutuna uyarlamaktı. Köprüden gelen iyi bir yanıt yine de OK değerini döndürür ve bu durumda yeni oluşturulan SAM bağlantısının kimliğini döndürürüz. Aksi takdirde boş bir dizge döndürürüz çünkü bu zaten geçersiz bir kimliktir ve başarısız olur, bu nedenle denetlenmesi kolaydır. Bir deneme kodu yazarak bu işlevin çalışıp çalışmadığını görelim:

@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", ""));
}

Bu denemede, oturumumuza başlamadan önce SAM ile iletişim kurmak için ilk olarak HelloSAM işlevini çağırmamız gerektiğini unutmayın. Yoksa, köprü bir hatayla yanıt verir ve deneme başarısız olur.

Looking up Hosts by name or .b32

Artık oturumunuzu ve yerel hedefinizi belirlediniz ve bunlarla ne yapmak istediğinize karar vermeniz gerekiyor. Oturumunuza I2P üzerinden uzak bir hizmete bağlanma veya geliş bağlantılarına yanıt vermesini bekleme komutu verilebilir. Ancak, uzak bir hedefe bağlanmadan önce, API yazılımının beklediği hedefin base64 değerini edinmeniz gerekebilir. Bunu yapmak için base64 değerini kullanılabilir bir biçimde döndürecek bir LookupName işlevi oluşturacağız.

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 "";
}

Yine, bu durum, bir farkla, HelloSAM ve CreateSession işlevlerimizle hemen hemen aynıdır. Özellikle DEĞER aradığımız için ve NAME alanı "name" argümanıyla aynı olacağından, istenen hedefin base64 dizgesi döndürülür.

Artık LookupName işlevimiz olduğuna göre deneyebiliriz:

@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

Son olarak yeni kitaplığımız ile başka bir hizmete bağlanacağız. Bu bölüm başta biraz kafamı karıştırdı, ancak en zeki Java geliştiricileri büyük olasılıkla Jsam sınıfının içinde bir soket değişkeni oluşturmak yerine neden soket sınıfını genişletmediğimizi merak ediyorlardı. Çünkü şimdiye kadar "Denetim soketi" üzerinden haberleşiyorduk ve asıl iletişimi yapmak için yeni bir soket oluşturmamız gerekiyor. Bu yüzden şimdiye kadar soket sınıfını Jsam sınıfıyla genişletmeyi bekledik:

public class Jsam extends Socket {

Ayrıca startConnection işlevimizi, uygulamamızda kullanacağımız denetim soketinden sokete geçmek için kullanabileceğimiz şekilde değiştirelim. Şimdi bir soket argümanı alacak.

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);
    }
}

Böylece, iletişim kurmak için hızlı ve kolay bir şekilde yeni bir soket açabilir, "Merhaba SAM" el sıkışmasını yeniden gerçekleştirebilir ve akış bağlantısı kurabiliriz.

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 "";
}
Ve artık SAM üzerinden iletişim kurmak için yeni bir soketiniz var! Uzak bağlantıları
kabul etmek için de aynı şeyi yapalım:
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 "";
}

Tamamdır. Adım adım bir SAM kitaplığı bu şekilde oluşturursunuz. Gelecekte, bunu kitaplığın çalışan sürümü, Jsam ve SAM v3 teknik özellikleri ile çapraz referans şeklinde yapacağım ama şimdi başka şeyler yapmam gerekiyor.