mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Implement FFI object stack
This commit is contained in:
175
cpp/include/idevice++/tcp_object_stack.hpp
Normal file
175
cpp/include/idevice++/tcp_object_stack.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <idevice++/core_device_proxy.hpp>
|
||||
#include <idevice++/ffi.hpp>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
// ---------------- OwnedBuffer: RAII for zero-copy read buffers ----------------
|
||||
class OwnedBuffer {
|
||||
public:
|
||||
OwnedBuffer() noexcept : p_(nullptr), n_(0) {}
|
||||
OwnedBuffer(const OwnedBuffer&) = delete;
|
||||
OwnedBuffer& operator=(const OwnedBuffer&) = delete;
|
||||
|
||||
OwnedBuffer(OwnedBuffer&& o) noexcept : p_(o.p_), n_(o.n_) {
|
||||
o.p_ = nullptr;
|
||||
o.n_ = 0;
|
||||
}
|
||||
OwnedBuffer& operator=(OwnedBuffer&& o) noexcept {
|
||||
if (this != &o) {
|
||||
reset();
|
||||
p_ = o.p_;
|
||||
n_ = o.n_;
|
||||
o.p_ = nullptr;
|
||||
o.n_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~OwnedBuffer() { reset(); }
|
||||
|
||||
const uint8_t* data() const noexcept { return p_; }
|
||||
uint8_t* data() noexcept { return p_; }
|
||||
std::size_t size() const noexcept { return n_; }
|
||||
bool empty() const noexcept { return n_ == 0; }
|
||||
|
||||
void reset() noexcept {
|
||||
if (p_) {
|
||||
::idevice_data_free(p_, n_);
|
||||
p_ = nullptr;
|
||||
n_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class TcpObjectStackEater;
|
||||
void adopt(uint8_t* p, std::size_t n) noexcept {
|
||||
reset();
|
||||
p_ = p;
|
||||
n_ = n;
|
||||
}
|
||||
|
||||
uint8_t* p_;
|
||||
std::size_t n_;
|
||||
};
|
||||
|
||||
// ---------------- TcpFeeder: push inbound IP packets into the stack ----------
|
||||
class TcpObjectStackFeeder {
|
||||
public:
|
||||
TcpObjectStackFeeder() = default;
|
||||
TcpObjectStackFeeder(const TcpObjectStackFeeder&) = delete;
|
||||
TcpObjectStackFeeder& operator=(const TcpObjectStackFeeder&) = delete;
|
||||
|
||||
TcpObjectStackFeeder(TcpObjectStackFeeder&& o) noexcept : h_(o.h_) { o.h_ = nullptr; }
|
||||
TcpObjectStackFeeder& operator=(TcpObjectStackFeeder&& o) noexcept {
|
||||
if (this != &o) {
|
||||
reset();
|
||||
h_ = o.h_;
|
||||
o.h_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~TcpObjectStackFeeder() { reset(); }
|
||||
|
||||
bool write(const uint8_t* data, std::size_t len, FfiError& err) const;
|
||||
::TcpFeedObject* raw() const { return h_; }
|
||||
|
||||
private:
|
||||
friend class TcpObjectStack;
|
||||
explicit TcpObjectStackFeeder(::TcpFeedObject* h) : h_(h) {}
|
||||
|
||||
void reset() {
|
||||
if (h_) {
|
||||
::idevice_free_tcp_feed_object(h_);
|
||||
h_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
::TcpFeedObject* h_ = nullptr;
|
||||
};
|
||||
|
||||
// ---------------- TcpEater: blocking read of outbound packets ----------------
|
||||
class TcpObjectStackEater {
|
||||
public:
|
||||
TcpObjectStackEater() = default;
|
||||
TcpObjectStackEater(const TcpObjectStackEater&) = delete;
|
||||
TcpObjectStackEater& operator=(const TcpObjectStackEater&) = delete;
|
||||
|
||||
TcpObjectStackEater(TcpObjectStackEater&& o) noexcept : h_(o.h_) { o.h_ = nullptr; }
|
||||
TcpObjectStackEater& operator=(TcpObjectStackEater&& o) noexcept {
|
||||
if (this != &o) {
|
||||
reset();
|
||||
h_ = o.h_;
|
||||
o.h_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~TcpObjectStackEater() { reset(); }
|
||||
|
||||
// Blocks until a packet is available. On success, 'out' adopts the buffer
|
||||
// and you must keep 'out' alive until done (RAII frees via idevice_data_free).
|
||||
bool read(OwnedBuffer& out, FfiError& err) const;
|
||||
|
||||
::TcpEatObject* raw() const { return h_; }
|
||||
|
||||
private:
|
||||
friend class TcpObjectStack;
|
||||
explicit TcpObjectStackEater(::TcpEatObject* h) : h_(h) {}
|
||||
|
||||
void reset() {
|
||||
if (h_) {
|
||||
::idevice_free_tcp_eat_object(h_);
|
||||
h_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
::TcpEatObject* h_ = nullptr;
|
||||
};
|
||||
|
||||
// ---------------- Stack builder: returns feeder + eater + adapter ------------
|
||||
class TcpObjectStack {
|
||||
public:
|
||||
TcpObjectStack() = default;
|
||||
TcpObjectStack(const TcpObjectStack&) = delete; // no sharing
|
||||
TcpObjectStack& operator=(const TcpObjectStack&) = delete;
|
||||
TcpObjectStack(TcpObjectStack&&) noexcept = default; // movable
|
||||
TcpObjectStack& operator=(TcpObjectStack&&) noexcept = default;
|
||||
|
||||
// Build the stack (dual-handle). Name kept to minimize churn.
|
||||
static std::optional<TcpObjectStack>
|
||||
create(const std::string& our_ip, const std::string& their_ip, FfiError& err);
|
||||
|
||||
TcpObjectStackFeeder& feeder();
|
||||
const TcpObjectStackFeeder& feeder() const;
|
||||
|
||||
TcpObjectStackEater& eater();
|
||||
const TcpObjectStackEater& eater() const;
|
||||
|
||||
Adapter& adapter();
|
||||
const Adapter& adapter() const;
|
||||
|
||||
std::optional<TcpObjectStackFeeder> release_feeder(); // nullptr inside wrapper after call
|
||||
std::optional<TcpObjectStackEater> release_eater(); // nullptr inside wrapper after call
|
||||
std::optional<Adapter> release_adapter();
|
||||
|
||||
private:
|
||||
struct Impl {
|
||||
TcpObjectStackFeeder feeder;
|
||||
TcpObjectStackEater eater;
|
||||
std::optional<Adapter> adapter;
|
||||
};
|
||||
// Unique ownership so there’s a single point of truth to release from
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace IdeviceFFI
|
||||
113
cpp/src/tcp_callback_feeder.cpp
Normal file
113
cpp/src/tcp_callback_feeder.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#include <idevice++/bindings.hpp>
|
||||
#include <idevice++/tcp_object_stack.hpp>
|
||||
|
||||
namespace IdeviceFFI {
|
||||
|
||||
// ---------- TcpFeeder ----------
|
||||
bool TcpObjectStackFeeder::write(const uint8_t* data, std::size_t len, FfiError& err) const {
|
||||
if (IdeviceFfiError* e = ::idevice_tcp_feed_object_write(h_, data, len)) {
|
||||
err = FfiError(e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- TcpEater ----------
|
||||
bool TcpObjectStackEater::read(OwnedBuffer& out, FfiError& err) const {
|
||||
uint8_t* ptr = nullptr;
|
||||
std::size_t len = 0;
|
||||
if (IdeviceFfiError* e = ::idevice_tcp_eat_object_read(h_, &ptr, &len)) {
|
||||
err = FfiError(e);
|
||||
return false;
|
||||
}
|
||||
// Success: adopt the buffer (freed via idevice_data_free in OwnedBuffer dtor)
|
||||
out.adopt(ptr, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- TcpStackFromCallback ----------
|
||||
std::optional<TcpObjectStack>
|
||||
TcpObjectStack::create(const std::string& our_ip, const std::string& their_ip, FfiError& err) {
|
||||
::TcpFeedObject* feeder_h = nullptr;
|
||||
::TcpEatObject* eater_h = nullptr;
|
||||
::AdapterHandle* adapter_h = nullptr;
|
||||
|
||||
if (IdeviceFfiError* e = ::idevice_tcp_stack_into_sync_objects(
|
||||
our_ip.c_str(), their_ip.c_str(), &feeder_h, &eater_h, &adapter_h)) {
|
||||
err = FfiError(e);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto impl = std::make_unique<Impl>();
|
||||
impl->feeder = TcpObjectStackFeeder(feeder_h);
|
||||
impl->eater = TcpObjectStackEater(eater_h);
|
||||
impl->adapter = Adapter::adopt(adapter_h);
|
||||
|
||||
TcpObjectStack out;
|
||||
out.impl_ = std::move(impl);
|
||||
return out;
|
||||
}
|
||||
|
||||
TcpObjectStackFeeder& TcpObjectStack::feeder() {
|
||||
return impl_->feeder;
|
||||
}
|
||||
const TcpObjectStackFeeder& TcpObjectStack::feeder() const {
|
||||
return impl_->feeder;
|
||||
}
|
||||
|
||||
TcpObjectStackEater& TcpObjectStack::eater() {
|
||||
return impl_->eater;
|
||||
}
|
||||
const TcpObjectStackEater& TcpObjectStack::eater() const {
|
||||
return impl_->eater;
|
||||
}
|
||||
|
||||
Adapter& TcpObjectStack::adapter() {
|
||||
if (!impl_ || !impl_->adapter) {
|
||||
static Adapter* never = nullptr;
|
||||
return *never;
|
||||
}
|
||||
return *(impl_->adapter);
|
||||
}
|
||||
const Adapter& TcpObjectStack::adapter() const {
|
||||
if (!impl_ || !impl_->adapter) {
|
||||
static Adapter* never = nullptr;
|
||||
return *never;
|
||||
}
|
||||
return *(impl_->adapter);
|
||||
}
|
||||
|
||||
// ---------- Release APIs ----------
|
||||
std::optional<TcpObjectStackFeeder> TcpObjectStack::release_feeder() {
|
||||
if (!impl_)
|
||||
return std::nullopt;
|
||||
auto has = impl_->feeder.raw() != nullptr;
|
||||
if (!has)
|
||||
return std::nullopt;
|
||||
TcpObjectStackFeeder out = std::move(impl_->feeder);
|
||||
// impl_->feeder is now empty (h_ == nullptr) thanks to move
|
||||
return std::optional<TcpObjectStackFeeder>(std::move(out));
|
||||
}
|
||||
|
||||
std::optional<TcpObjectStackEater> TcpObjectStack::release_eater() {
|
||||
if (!impl_)
|
||||
return std::nullopt;
|
||||
auto has = impl_->eater.raw() != nullptr;
|
||||
if (!has)
|
||||
return std::nullopt;
|
||||
TcpObjectStackEater out = std::move(impl_->eater);
|
||||
return std::optional<TcpObjectStackEater>(std::move(out));
|
||||
}
|
||||
|
||||
std::optional<Adapter> TcpObjectStack::release_adapter() {
|
||||
if (!impl_ || !impl_->adapter)
|
||||
return std::nullopt;
|
||||
// Move out and clear our optional
|
||||
auto out = std::move(*(impl_->adapter));
|
||||
impl_->adapter.reset();
|
||||
return std::optional<Adapter>(std::move(out));
|
||||
}
|
||||
|
||||
} // namespace IdeviceFFI
|
||||
Reference in New Issue
Block a user