#include "shim.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
#include "api.h"
#include "Destination.h"
#include "Streaming.h"
#include "Identity.h"
#include "PostQuantum.h"
#include "RouterContext.h"
#include "Tunnel.h"
struct I2pdDestination {
std::shared_ptr<i2p::client::ClientDestination> ptr;
};
struct I2pdStream {
std::shared_ptr<i2p::stream::Stream> ptr;
};
namespace {
template <typename F>
std::invoke_result_t<F> guard(F &&f, std::invoke_result_t<F> fallback) noexcept {
try {
return static_cast<F &&>(f)();
} catch (...) {
return fallback;
}
}
template <typename F>
void guard_void(F &&f) noexcept {
try {
static_cast<F &&>(f)();
} catch (...) {
}
}
void secure_zero(void *p, size_t len) noexcept {
if (!p || !len)
return;
volatile unsigned char *v = static_cast<volatile unsigned char *>(p);
while (len--)
*v++ = 0;
}
bool ct_equal(const uint8_t *a, const uint8_t *b, size_t len) noexcept {
unsigned char diff = 0;
for (size_t i = 0; i < len; i++)
diff |= static_cast<unsigned char>(a[i] ^ b[i]);
return diff == 0;
}
constexpr char kDefaultBandwidthClass = i2p::data::CAPS_FLAG_HIGH_BANDWIDTH;
constexpr size_t kMaxIoLen = static_cast<size_t>(std::numeric_limits<long>::max());
std::mutex g_lifecycle_mutex;
bool g_initialized = false;
bool g_started = false;
}
extern "C" {
void i2pd_init(const char *app_name) {
if (!app_name)
return;
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (g_initialized)
return;
g_initialized = guard(
[&] {
std::vector<char> name(app_name, app_name + std::strlen(app_name) + 1);
char *argv[] = {name.data(), nullptr};
i2p::api::InitI2P(1, argv, app_name);
i2p::context.SetBandwidth(kDefaultBandwidthClass);
return true;
},
false);
}
void i2pd_start(void) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_initialized || g_started)
return;
g_started = guard([] { i2p::api::StartI2P(); return true; }, false);
}
void i2pd_stop(void) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_started)
return;
g_started = false;
guard_void([] { i2p::api::StopI2P(); });
}
int i2pd_accepts_transit(void) {
#ifdef I2PD_SYS_NO_TRANSIT
return 0;
#else
return 1;
#endif
}
void i2pd_set_accepts_transit(int enabled) {
#ifdef I2PD_SYS_NO_TRANSIT
(void)enabled;
#else
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_initialized || g_started)
return;
guard_void([&] { i2p::context.SetAcceptsTunnels(enabled != 0); });
#endif
}
void i2pd_set_bandwidth_limit(int kbps) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_initialized || g_started)
return;
guard_void([&] {
if (kbps > 0)
i2p::context.SetBandwidth(kbps);
else
i2p::context.SetBandwidth(kDefaultBandwidthClass);
});
}
void i2pd_set_share_percent(int percent) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_initialized || g_started)
return;
const int clamped = percent < 0 ? 0 : (percent > 100 ? 100 : percent);
guard_void([&] { i2p::context.SetShareRatio(clamped); });
}
void i2pd_set_max_transit_tunnels(int max_tunnels) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_initialized || g_started || max_tunnels <= 0)
return;
guard_void([&] {
i2p::tunnel::tunnels.SetMaxNumTransitTunnels(static_cast<uint32_t>(max_tunnels));
});
}
void i2pd_set_floodfill(int enabled) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_initialized || g_started)
return;
guard_void([&] { i2p::context.SetFloodfill(enabled != 0); });
}
void i2pd_terminate(void) {
std::lock_guard<std::mutex> lock(g_lifecycle_mutex);
if (!g_initialized)
return;
if (g_started) {
g_started = false;
guard_void([] { i2p::api::StopI2P(); });
}
g_initialized = false;
guard_void([] { i2p::api::TerminateI2P(); });
}
I2pdDestination *i2pd_create_transient_destination(void) {
return guard(
[]() -> I2pdDestination * {
auto dest = i2p::api::CreateLocalDestination(true);
return dest ? new I2pdDestination{std::move(dest)} : nullptr;
},
nullptr);
}
int i2pd_generate_keys(int sig_type, int crypto_type, unsigned char **out_buf, size_t *out_len) {
(void)crypto_type; if (!out_buf || !out_len)
return 0;
return guard(
[&] {
auto keys = i2p::data::PrivateKeys::CreateRandomKeys(
static_cast<i2p::data::SigningKeyType>(sig_type),
i2p::data::CRYPTO_KEY_TYPE_ELGAMAL,
true);
size_t len = keys.GetFullLen();
if (!len)
return 0;
auto *buf = static_cast<unsigned char *>(std::malloc(len));
if (!buf)
return 0;
if (!guard([&] { keys.ToBuffer(buf, len); return true; }, false)) {
secure_zero(buf, len);
std::free(buf);
return 0;
}
*out_buf = buf;
*out_len = len;
return 1;
},
0);
}
I2pdDestination *i2pd_create_persistent_destination(const unsigned char *keys_buf, size_t keys_len, int is_public, const char *encryption_types_csv) {
if (!keys_buf || !keys_len)
return nullptr;
return guard(
[&]() -> I2pdDestination * {
i2p::data::PrivateKeys keys;
if (keys.FromBuffer(keys_buf, keys_len) == 0)
return nullptr;
std::shared_ptr<i2p::client::ClientDestination> dest;
if (encryption_types_csv && *encryption_types_csv) {
i2p::util::Mapping params;
params.Insert(i2p::client::I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, encryption_types_csv);
dest = i2p::api::CreateLocalDestination(keys, is_public != 0, ¶ms);
} else {
dest = i2p::api::CreateLocalDestination(keys, is_public != 0);
}
return dest ? new I2pdDestination{std::move(dest)} : nullptr;
},
nullptr);
}
void i2pd_free_buffer(unsigned char *ptr, size_t len) {
secure_zero(ptr, len);
std::free(ptr);
}
int i2pd_test_mlkem_roundtrip(int mlkem_variant) {
return guard(
[&] {
i2p::data::CryptoKeyType cryptoType;
switch (mlkem_variant) {
case 0: cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD; break;
case 1: cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD; break;
case 2: cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD; break;
default: return 0;
}
auto responder = i2p::crypto::CreateMLKEMKeys(cryptoType);
auto initiator = i2p::crypto::CreateMLKEMKeys(cryptoType);
if (!responder || !initiator)
return 0;
responder->GenerateKeys();
std::vector<uint8_t> pub(responder->GetKeyLen());
responder->GetPublicKey(pub.data());
initiator->SetPublicKey(pub.data());
std::vector<uint8_t> ciphertext(initiator->GetCTLen());
uint8_t sharedInitiator[32] = {0}, sharedResponder[32] = {0};
initiator->Encaps(ciphertext.data(), sharedInitiator);
responder->Decaps(ciphertext.data(), sharedResponder);
int ok = ct_equal(sharedInitiator, sharedResponder, sizeof(sharedInitiator)) ? 1 : 0;
secure_zero(sharedInitiator, sizeof(sharedInitiator));
secure_zero(sharedResponder, sizeof(sharedResponder));
return ok;
},
0);
}
void i2pd_destroy_destination(I2pdDestination *dest) {
if (!dest)
return;
guard_void([&] {
if (dest->ptr) {
dest->ptr->StopAcceptingStreams();
i2p::api::DestroyLocalDestination(dest->ptr);
}
});
delete dest;
}
char *i2pd_destination_b32_address(I2pdDestination *dest) {
if (!dest || !dest->ptr)
return nullptr;
return guard(
[&]() -> char * {
const 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;
},
nullptr);
}
int i2pd_destination_ident_hash(I2pdDestination *dest, unsigned char *out) {
static_assert(sizeof(i2p::data::IdentHash) == I2PD_IDENT_HASH_LEN,
"libi2pd's IdentHash is no longer 32 bytes -- shim.h's contract must change");
if (!dest || !dest->ptr || !out)
return 0;
return guard(
[&] {
std::memcpy(out, dest->ptr->GetIdentHash().data(), I2PD_IDENT_HASH_LEN);
return 1;
},
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;
guard_void([&] {
i2p::api::AcceptStream(dest->ptr, [cb, ctx](std::shared_ptr<i2p::stream::Stream> stream) {
guard_void([&] {
if (!stream)
return;
auto handle = std::make_unique<I2pdStream>(I2pdStream{std::move(stream)});
cb(ctx, handle.get());
(void)handle.release();
});
});
});
}
I2pdStream *i2pd_create_stream(I2pdDestination *dest, const unsigned char *remote_ident_hash, int timeout_seconds) {
if (!dest || !dest->ptr || !remote_ident_hash)
return nullptr;
return guard(
[&]() -> I2pdStream * {
const i2p::data::IdentHash hash(remote_ident_hash);
const auto deadline = std::chrono::steady_clock::now() +
std::chrono::seconds(timeout_seconds > 0 ? timeout_seconds : 0);
for (;;) {
auto stream = i2p::api::CreateStream(dest->ptr, hash);
if (stream)
return new I2pdStream{std::move(stream)};
const auto now = std::chrono::steady_clock::now();
if (now >= deadline)
return nullptr;
std::this_thread::sleep_for(std::min<std::chrono::steady_clock::duration>(
deadline - now, std::chrono::seconds(1)));
}
},
nullptr);
}
long i2pd_stream_send(I2pdStream *stream, const unsigned char *buf, size_t len) {
if (!stream || !stream->ptr || (!buf && len))
return -1;
if (len > kMaxIoLen)
len = kMaxIoLen;
return guard([&] { return static_cast<long>(stream->ptr->Send(buf, len)); }, -1L);
}
long i2pd_stream_receive(I2pdStream *stream, unsigned char *buf, size_t len, int timeout_seconds) {
if (!stream || !stream->ptr || (!buf && len))
return -1;
if (len > kMaxIoLen)
len = kMaxIoLen;
return guard([&] { return static_cast<long>(stream->ptr->Receive(buf, len, timeout_seconds)); }, -1L);
}
int i2pd_stream_is_open(I2pdStream *stream) {
if (!stream || !stream->ptr)
return 0;
return guard([&] { return stream->ptr->IsOpen() ? 1 : 0; }, 0);
}
void i2pd_stream_close(I2pdStream *stream) {
if (!stream || !stream->ptr)
return;
guard_void([&] { stream->ptr->Close(); });
}
void i2pd_destroy_stream(I2pdStream *stream) {
if (!stream)
return;
guard_void([&] {
if (stream->ptr)
stream->ptr->Close();
});
delete stream;
}
}