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
|
||||||
@@ -39,6 +39,8 @@ pub mod rsd;
|
|||||||
pub mod springboardservices;
|
pub mod springboardservices;
|
||||||
#[cfg(feature = "syslog_relay")]
|
#[cfg(feature = "syslog_relay")]
|
||||||
pub mod syslog_relay;
|
pub mod syslog_relay;
|
||||||
|
#[cfg(feature = "tunnel_tcp_stack")]
|
||||||
|
pub mod tcp_object_stack;
|
||||||
#[cfg(feature = "usbmuxd")]
|
#[cfg(feature = "usbmuxd")]
|
||||||
pub mod usbmuxd;
|
pub mod usbmuxd;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|||||||
217
ffi/src/tcp_object_stack.rs
Normal file
217
ffi/src/tcp_object_stack.rs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
//! Just to make things more complicated, some setups need an IP input from FFI. Or maybe a packet
|
||||||
|
//! input that is sync only. This is a stupid simple shim between callbacks and an input for the
|
||||||
|
//! legendary idevice TCP stack.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ffi::{CStr, c_char, c_void},
|
||||||
|
ptr::null_mut,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tokio::{
|
||||||
|
io::AsyncWriteExt,
|
||||||
|
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{IdeviceFfiError, RUNTIME, core_device_proxy::AdapterHandle, ffi_err};
|
||||||
|
|
||||||
|
pub struct TcpFeedObject {
|
||||||
|
sender: Arc<Mutex<OwnedWriteHalf>>,
|
||||||
|
}
|
||||||
|
pub struct TcpEatObject {
|
||||||
|
receiver: Arc<Mutex<OwnedReadHalf>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UserContext(*mut c_void);
|
||||||
|
unsafe impl Send for UserContext {}
|
||||||
|
unsafe impl Sync for UserContext {}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// Pass valid pointers.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_tcp_stack_into_sync_objects(
|
||||||
|
our_ip: *const c_char,
|
||||||
|
their_ip: *const c_char,
|
||||||
|
feeder: *mut *mut TcpFeedObject, // feed the TCP stack with IP packets
|
||||||
|
tcp_receiver: *mut *mut TcpEatObject,
|
||||||
|
adapter_handle: *mut *mut AdapterHandle, // this object can be used throughout the rest of the
|
||||||
|
// idevice ecosystem
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if our_ip.is_null() || their_ip.is_null() || feeder.is_null() || adapter_handle.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let our_ip = unsafe { CStr::from_ptr(our_ip) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
let our_ip = match our_ip.parse::<std::net::IpAddr>() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let their_ip = unsafe { CStr::from_ptr(their_ip) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
let their_ip = match their_ip.parse::<std::net::IpAddr>() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = RUNTIME.block_on(async {
|
||||||
|
let mut port = 4000;
|
||||||
|
loop {
|
||||||
|
if port > 4050 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let listener = match tokio::net::TcpListener::bind(format!("127.0.0.1:{port}")).await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(_) => {
|
||||||
|
port += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{port}"))
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
stream.set_nodelay(true).ok()?;
|
||||||
|
let (stream2, _) = listener.accept().await.ok()?;
|
||||||
|
stream2.set_nodelay(true).ok()?;
|
||||||
|
break Some((stream, stream2));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let (stream, stream2) = match res {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
return ffi_err!(IdeviceError::NoEstablishedConnection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (r, w) = stream2.into_split();
|
||||||
|
let w = Arc::new(Mutex::new(w));
|
||||||
|
let r = Arc::new(Mutex::new(r));
|
||||||
|
|
||||||
|
// let w = Arc::new(Mutex::new(stream2));
|
||||||
|
// let r = w.clone();
|
||||||
|
|
||||||
|
let feed_object = TcpFeedObject { sender: w };
|
||||||
|
let eat_object = TcpEatObject { receiver: r };
|
||||||
|
|
||||||
|
// we must be inside the runtime for the inner function to spawn threads
|
||||||
|
let new_adapter = RUNTIME.block_on(async {
|
||||||
|
idevice::tcp::adapter::Adapter::new(Box::new(stream), our_ip, their_ip).to_async_handle()
|
||||||
|
});
|
||||||
|
// this object can now be used with the rest of the idevice FFI library
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*feeder = Box::into_raw(Box::new(feed_object));
|
||||||
|
*tcp_receiver = Box::into_raw(Box::new(eat_object));
|
||||||
|
*adapter_handle = Box::into_raw(Box::new(AdapterHandle(new_adapter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Feed the TCP stack with data
|
||||||
|
/// # Safety
|
||||||
|
/// Pass valid pointers. Data is cloned out of slice.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_tcp_feed_object_write(
|
||||||
|
object: *mut TcpFeedObject,
|
||||||
|
data: *const u8,
|
||||||
|
len: usize,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
if object.is_null() || data.is_null() {
|
||||||
|
return ffi_err!(IdeviceError::FfiInvalidArg);
|
||||||
|
}
|
||||||
|
let object = unsafe { &mut *object };
|
||||||
|
let data = unsafe { std::slice::from_raw_parts(data, len) };
|
||||||
|
RUNTIME.block_on(async move {
|
||||||
|
let mut lock = object.sender.lock().await;
|
||||||
|
match lock.write_all(data).await {
|
||||||
|
Ok(_) => {
|
||||||
|
lock.flush().await.ok();
|
||||||
|
null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
ffi_err!(IdeviceError::Socket(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::BrokenPipe,
|
||||||
|
format!("could not send: {e:?}")
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block on getting a block of data to write to the underlying stream.
|
||||||
|
/// Write this to the stream as is, and free the data with idevice_data_free
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Pass valid pointers
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_tcp_eat_object_read(
|
||||||
|
object: *mut TcpEatObject,
|
||||||
|
data: *mut *mut u8,
|
||||||
|
len: *mut usize,
|
||||||
|
) -> *mut IdeviceFfiError {
|
||||||
|
let object = unsafe { &mut *object };
|
||||||
|
let mut buf = [0; 2048];
|
||||||
|
RUNTIME.block_on(async {
|
||||||
|
let lock = object.receiver.lock().await;
|
||||||
|
match lock.try_read(&mut buf) {
|
||||||
|
Ok(size) => {
|
||||||
|
debug!("EATING");
|
||||||
|
let bytes = buf[..size].to_vec();
|
||||||
|
let mut res = bytes.into_boxed_slice();
|
||||||
|
unsafe {
|
||||||
|
*len = res.len();
|
||||||
|
*data = res.as_mut_ptr();
|
||||||
|
}
|
||||||
|
std::mem::forget(res);
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
std::io::ErrorKind::WouldBlock => {
|
||||||
|
unsafe {
|
||||||
|
*len = 0;
|
||||||
|
}
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ffi_err!(IdeviceError::Socket(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::BrokenPipe,
|
||||||
|
"channel closed"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// Pass a valid pointer allocated by this library
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_free_tcp_feed_object(object: *mut TcpFeedObject) {
|
||||||
|
if object.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = unsafe { Box::from_raw(object) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// Pass a valid pointer allocated by this library
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn idevice_free_tcp_eat_object(object: *mut TcpEatObject) {
|
||||||
|
if object.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = unsafe { Box::from_raw(object) };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user