i2pd-sys 0.0.1

Raw FFI bindings to a minimal C shim over libi2pd (PurpleI2P/i2pd).
Documentation
/* Implementation of shim.h — see that file for the boundary contract.
 *
 * Every entry point catches (...) so a C++ exception (i2pd's own code does throw in places,
 * e.g. std::runtime_error on crypto/config failures) can never unwind across the extern "C"
 * boundary, which would be undefined behavior.
 */
#include "shim.h"

#include <cstdlib>
#include <cstring>
#include <memory>
#include <thread>
#include <chrono>

#include "api.h"
#include "Destination.h"
#include "Streaming.h"
#include "Identity.h"

struct I2pdDestination {
    std::shared_ptr<i2p::client::ClientDestination> ptr;
};

struct I2pdStream {
    std::shared_ptr<i2p::stream::Stream> ptr;
};

extern "C" {

void i2pd_init(const char *app_name) {
    if (!app_name)
        return;
    try {
        char name[256];
        std::strncpy(name, app_name, sizeof(name) - 1);
        name[sizeof(name) - 1] = '\0';
        char *argv[] = {name};
        i2p::api::InitI2P(1, argv, app_name);
    } catch (...) {
    }
}

void i2pd_start(void) {
    try {
        i2p::api::StartI2P();
    } catch (...) {
    }
}

void i2pd_stop(void) {
    try {
        i2p::api::StopI2P();
    } catch (...) {
    }
}

void i2pd_terminate(void) {
    try {
        i2p::api::TerminateI2P();
    } catch (...) {
    }
}

I2pdDestination *i2pd_create_transient_destination(void) {
    try {
        auto dest = i2p::api::CreateLocalDestination(/*isPublic=*/true);
        if (!dest)
            return nullptr;
        return new I2pdDestination{dest};
    } catch (...) {
        return nullptr;
    }
}

int i2pd_generate_keys(int sig_type, int crypto_type, unsigned char **out_buf, size_t *out_len) {
    if (!out_buf || !out_len)
        return 0;
    try {
        auto keys = i2p::data::PrivateKeys::CreateRandomKeys(
            static_cast<i2p::data::SigningKeyType>(sig_type),
            static_cast<i2p::data::CryptoKeyType>(crypto_type),
            /*isDestination=*/true);
        size_t len = keys.GetFullLen();
        auto *buf = static_cast<unsigned char *>(std::malloc(len));
        if (!buf)
            return 0;
        keys.ToBuffer(buf, len);
        *out_buf = buf;
        *out_len = len;
        return 1;
    } catch (...) {
        return 0;
    }
}

I2pdDestination *i2pd_create_persistent_destination(const unsigned char *keys_buf, size_t keys_len, int is_public) {
    if (!keys_buf || !keys_len)
        return nullptr;
    try {
        i2p::data::PrivateKeys keys;
        if (keys.FromBuffer(keys_buf, keys_len) == 0)
            return nullptr;
        auto dest = i2p::api::CreateLocalDestination(keys, is_public != 0);
        if (!dest)
            return nullptr;
        return new I2pdDestination{dest};
    } catch (...) {
        return nullptr;
    }
}

void i2pd_free_buffer(unsigned char *ptr) {
    std::free(ptr);
}

void i2pd_destroy_destination(I2pdDestination *dest) {
    if (!dest)
        return;
    try {
        i2p::api::DestroyLocalDestination(dest->ptr);
    } catch (...) {
    }
    delete dest;
}

char *i2pd_destination_b32_address(I2pdDestination *dest) {
    if (!dest || !dest->ptr)
        return nullptr;
    try {
        std::string addr = dest->ptr->GetIdentHash().ToBase32() + ".b32.i2p";
        char *out = static_cast<char *>(std::malloc(addr.size() + 1));
        if (!out)
            return nullptr;
        std::memcpy(out, addr.c_str(), addr.size() + 1);
        return out;
    } catch (...) {
        return nullptr;
    }
}

int i2pd_destination_ident_hash(I2pdDestination *dest, unsigned char *out) {
    if (!dest || !dest->ptr || !out)
        return 0;
    try {
        std::memcpy(out, dest->ptr->GetIdentHash().data(), 32);
        return 1;
    } catch (...) {
        return 0;
    }
}

void i2pd_free_string(char *ptr) {
    std::free(ptr);
}

void i2pd_accept_stream(I2pdDestination *dest, I2pdAcceptCallback cb, void *ctx) {
    if (!dest || !dest->ptr || !cb)
        return;
    try {
        i2p::api::AcceptStream(dest->ptr, [cb, ctx](std::shared_ptr<i2p::stream::Stream> stream) {
            if (!stream)
                return;
            auto *s = new I2pdStream{stream};
            cb(ctx, s);
        });
    } catch (...) {
    }
}

I2pdStream *i2pd_create_stream(I2pdDestination *dest, const unsigned char *remote_ident_hash, int timeout_seconds) {
    if (!dest || !dest->ptr || !remote_ident_hash)
        return nullptr;
    try {
        i2p::data::IdentHash hash(remote_ident_hash);
        auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout_seconds);
        while (true) {
            auto stream = i2p::api::CreateStream(dest->ptr, hash);
            if (stream)
                return new I2pdStream{stream};
            if (std::chrono::steady_clock::now() >= deadline)
                return nullptr;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    } catch (...) {
        return nullptr;
    }
}

long i2pd_stream_send(I2pdStream *stream, const unsigned char *buf, size_t len) {
    if (!stream || !stream->ptr)
        return -1;
    try {
        return static_cast<long>(stream->ptr->Send(buf, len));
    } catch (...) {
        return -1;
    }
}

long i2pd_stream_receive(I2pdStream *stream, unsigned char *buf, size_t len, int timeout_seconds) {
    if (!stream || !stream->ptr)
        return -1;
    try {
        return static_cast<long>(stream->ptr->Receive(buf, len, timeout_seconds));
    } catch (...) {
        return -1;
    }
}

int i2pd_stream_is_open(I2pdStream *stream) {
    if (!stream || !stream->ptr)
        return 0;
    try {
        return stream->ptr->IsOpen() ? 1 : 0;
    } catch (...) {
        return 0;
    }
}

void i2pd_stream_close(I2pdStream *stream) {
    if (!stream || !stream->ptr)
        return;
    try {
        stream->ptr->Close();
    } catch (...) {
    }
}

void i2pd_destroy_stream(I2pdStream *stream) {
    if (!stream)
        return;
    delete stream;
}

} // extern "C"