| Crates.io | bitcoin-sock |
| lib.rs | bitcoin-sock |
| version | 0.1.19 |
| created_at | 2023-01-18 10:10:07.034098+00 |
| updated_at | 2025-12-01 16:56:04.860319+00 |
| description | Low-level, cross-platform RAII wrapper and utilities for raw OS sockets, mirroring Bitcoin Core networking semantics (send/recv, timeouts, readiness, error classification). |
| homepage | |
| repository | https://github.com/klebs6/bitcoin-rs |
| max_upload_size | |
| id | 761626 |
| size | 236,555 |
Low-level, cross-platform socket primitives extracted from the Bitcoin Core networking layer, written in Rust.
bitcoin-sock provides a thin, well-instrumented RAII wrapper over OS sockets (CSocket) together with carefully-behaved higher-level operations such as:
MSG_PEEK)select(2) or poll(2)io_error_is_permanent) and OS-neutral error stringsThe crate is designed for consumers that need explicit control over system calls and error semantics (e.g. Bitcoin node implementations, protocol daemons, or custom P2P stacks) while still benefiting from RAII and structured logging.
The core primitive is the platform-dependent alias CSocket:
#[cfg(target_os = "windows")]
pub type CSocket = usize; // SOCKET
#[cfg(not(target_os = "windows"))]
pub type CSocket = libc::c_int; // POSIX fd
``
All operations in this crate are expressed in terms of `CSocket`, with conditional compilation to choose between Winsock (`send`, `recv`, `connect`, `closesocket`, error codes) and POSIX (`send`, `recv`, `connect`, `close`, `errno`).
This makes the crate appropriate for cross-platform network stacks where you want the exact C API semantics, but in Rust.
### Sock: RAII wrapper around CSocket
`Sock` is the central type:
```rust
#[derive(Debug, Getters, MutGetters)]
#[getset(get = "pub", get_mut = "pub")]
pub struct Sock {
socket: CSocket,
}
Key properties:
Drop closes the socket (if not empty), mirroring std::unique_ptr<SOCKET> semantics from C++.assign_from and From<CSocket> implement move-style transfer of ownership between Sock instances.INVALID_SOCKET denotes an inert Sock that does nothing on drop.Sock exposes exact-thin wrappers around the core socket syscalls:
send(&self, data: *const c_void, len: usize, flags: i32) -> isizerecv(&self, buf: *mut c_void, len: usize, flags: i32) -> isizeconnect(&self, addr: *const SocketAddr, addr_len: libc::socklen_t) -> i32get_sock_opt(&self, level, opt_name, opt_val, opt_len) -> i32These mirror the C interfaces verbatim, including flags like MSG_NOSIGNAL and MSG_PEEK where applicable. The functions are deliberately unsafe in spirit: they accept raw pointers and sizes and return raw OS return codes, converted to Rust integer types.
On top of the primitive calls, Sock adds value in several ways:
io_error_is_permanentNetwork errors are not all equal. Temporary conditions such as EAGAIN, EINTR, or EWOULDBLOCK should be retried, while others should be treated as fatal.
#[cfg(unix)]
#[inline]
pub fn io_error_is_permanent(err: i32) -> bool {
use libc::{EAGAIN, EINTR, EWOULDBLOCK, EINPROGRESS};
!(err == EAGAIN || err == EINTR || err == EWOULDBLOCK || err == EINPROGRESS)
}
#[cfg(windows)]
#[inline]
pub fn io_error_is_permanent(err: i32) -> bool {
use winapi::um::winsock2::{WSAEAGAIN, WSAEINTR, WSAEWOULDBLOCK, WSAEINPROGRESS};
!(err == WSAEAGAIN || err == WSAEINTR || err == WSAEWOULDBLOCK || err == WSAEINPROGRESS)
}
This function is used centrally in send/receive loops to distinguish between retryable and terminal errors.
wait and is_selectable_socketSock::wait blocks until the socket becomes ready for the requested SockEvents (read and/or write), bounded by a timeout:
feature = "use_poll": uses poll(2).select(2) with an FD_SETSIZE check via is_selectable_socket to avoid UB on large descriptors.This design is intended to faithfully reproduce the behaviour of the original Bitcoin networking code while staying portable.
send_completeimpl Sock {
pub fn send_complete(
&self,
data: &String,
timeout: chrono::Duration,
interrupt: &mut ThreadInterrupt,
) {
// ...
}
}
Algorithmically:
chrono::Duration) into a hard deadline (Instant).send on the remaining byte slice.sent and continue.ret <= 0):
last_socket_error and decide permanent vs transient via io_error_is_permanent.network_error_string.deadline and interrupt; if either fires, panic with an informative message about partial progress.compute_bounded_wait(deadline) and call wait for SOCK_SEND before retry.The logic is carefully structured to avoid busy-waiting while still honouring both timeout and interrupt conditions.
Mathematically, you can model the loop as a bounded retry algorithm on a non-deterministic channel with the following invariants:
sent is monotonically non-decreasing and bounded by data.len().deadline and interrupt stays false, the loop must terminate with sent == len.recv_until_terminatorimpl Sock {
pub fn recv_until_terminator(
&self,
terminator: u8,
timeout: chrono::Duration,
interrupt: &mut ThreadInterrupt,
max_data: usize,
) -> String {
// ...
}
}
Core idea: we want to read until a sentinel byte without consuming any data beyond that point from the socket. Reading byte-by-byte would preserve the invariant but be asymptotically inefficient (roughly O(n) syscalls for n bytes).
Instead, the implementation uses a repeated pattern of:
MSG_PEEK-style recv into a small buffer (e.g. 512 bytes).pos, issue a non-peeking read of exactly pos + 1 bytes and append them to data.data.len() <= max_data, panicking if exceeded.interrupt, with the same compute_bounded_wait pattern as in send_complete.This yields a roughly 50× improvement over byte-wise reading for realistic workloads (as noted by the inline comments), while preserving a strong invariant:
After
recv_until_terminatorreturns, the underlying socket has consumed exactly the bytes up to and including the first occurrence ofterminator, and no further.
The returned String strips the terminator before returning, and asserts UTF‑8 validity.
is_connectedimpl Sock {
pub fn is_connected(&self, errmsg: &mut String) -> bool {
// ...
}
}
is_connected implements the canonical Bitcoin approach:
INVALID_SOCKET, set errmsg to "not connected" and return false.recv of one byte with MSG_PEEK.ret == -1 and error is transient → treat as still connected.ret == -1 and error is permanent → set errmsg to the OS error string and return false.ret == 0 → remote has closed the connection; set errmsg to "closed" and return false.ret > 0 → data available; socket considered connected.This function allows higher-level code to cheaply check connectivity status without altering the read cursor.
The crate also provides utilities for system-level error introspection:
last_socket_error() -> i32: returns errno on Unix, WSAGetLastError() on Windows.network_error_string(err: i32) -> String: returns a descriptive message including the error code, using strerror_r on Unix or FormatMessageW on Windows.These are engineered to behave consistently across libc variants (GNU vs POSIX strerror_r) and to produce strings of the form:
"<system message> (<code>)"
which mirrors the logic used in C++ Bitcoin Core.
On Unix platforms that use select(2), there is a hard ceiling (FD_SETSIZE) on descriptors that can safely be used. is_selectable_socket encodes this constraint:
pub fn is_selectable_socket(s: &CSocket) -> bool {
#[cfg(any(target_os = "windows", feature = "use_poll"))]
{ true }
#[cfg(not(any(target_os = "windows", feature = "use_poll")))]
{ (*s as usize) < libc::FD_SETSIZE as usize }
}
``
Higher-level code can use this to decide whether a given socket can participate in a global `select`-based event loop or must instead be handled differently (e.g., via `poll` or epoll in an external layer).
### Socket pair utility (Unix)
For local IPC and test harnesses, the crate includes:
```rust
#[cfg(unix)]
pub fn make_socket_pair() -> (libc::c_int, libc::c_int) {
// ... AF_UNIX, SOCK_STREAM pair
}
This constructs a bidirectional connected pair of Unix domain sockets, asserting on failure; useful for deterministic test constructions and local pipelines.
The crate defines several traits that abstract over socket-like backends:
pub trait SockInterface {}
pub trait SockGet {
fn get(&self) -> CSocket;
}
pub trait SockRelease {
fn release(&mut self) -> CSocket;
}
pub trait Reset {
fn reset(&mut self);
}
pub trait SockSend {
fn send(&self, data: *const c_void, len: usize, flags: i32) -> isize;
}
pub trait SockRecv {
fn recv(&self, buf: *mut c_void, len: usize, flags: i32) -> isize;
}
pub trait SockConnect {
fn connect(&self, addr: *const SocketAddr, addr_len: libc::socklen_t) -> i32;
}
pub trait SockGetSockOpt {
fn get_sock_opt(
&self,
level: i32,
opt_name: i32,
opt_val: *mut c_void,
opt_len: *mut libc::socklen_t,
) -> i32;
}
pub trait SockWait {
fn wait(&self, timeout: Instant, requested: SockEvent, occurred: *mut SockEvent) -> bool;
}
pub trait SockSendComplete {
fn send_complete(
&self,
data: &String,
timeout: Instant,
interrupt: &mut ThreadInterrupt,
);
}
pub trait SockRecvUntilTerminator {
fn recv_until_terminator(
&self,
terminator: u8,
timeout: Instant,
interrupt: &mut ThreadInterrupt,
max_data: usize,
) -> String;
}
pub trait SockIsConnected {
fn is_connected(&self, errmsg: &mut String) -> bool;
}
The concrete Sock type effectively implements the semantics of these traits. In your own code, you can:
SockRecvUntilTerminator and SockSendComplete.This allows you to decouple protocol logic from transport implementation while retaining the precise semantics that the Bitcoin networking stack expects.
use bitcoin_sock::{CSocket, Sock};
fn adopt_raw_socket(raw: CSocket) {
// Transfer ownership from a raw descriptor into RAII-managed Sock.
let mut sock = Sock::from(raw);
// Query the underlying handle without transferring ownership.
let fd = sock.get();
println!("underlying fd = {}", fd);
// Release ownership back to the caller; Sock becomes inert.
let raw_again = sock.release();
assert_eq!(raw_again, fd);
}
use bitcoin_sock::Sock;
use chrono::Duration;
fn send_message(sock: &Sock, payload: &str, interrupt: &mut ThreadInterrupt) {
let timeout = Duration::seconds(30);
sock.send_complete(&payload.to_string(), timeout, interrupt);
}
The function will either:
You can wrap it and convert panics into error values if you need a non-panicking API at a higher layer.
use bitcoin_sock::Sock;
use chrono::Duration;
fn recv_line(sock: &Sock, interrupt: &mut ThreadInterrupt) -> String {
// Read until '\n', up to 64 KiB, with a 60s timeout.
let timeout = Duration::seconds(60);
sock.recv_until_terminator(b'\n', timeout, interrupt, 64 * 1024)
}
The returned String will exclude the newline terminator and be valid UTF‑8.
use bitcoin_sock::Sock;
fn ensure_connected(sock: &Sock) -> Result<(), String> {
let mut errmsg = String::new();
if sock.is_connected(&mut errmsg) {
Ok(())
} else {
Err(errmsg)
}
}
This is particularly useful for long-lived P2P connections where you want to periodically verify liveness without mutating the receive stream.
use bitcoin_sock::{Sock, SockEvent};
use time::Duration;
const SOCK_RECV: SockEvent = 0x01;
const SOCK_SEND: SockEvent = 0x02;
fn wait_for_read(sock: &Sock, max_wait_ms: i64) -> bool {
let requested = SOCK_RECV;
let mut occurred: SockEvent = 0;
let timeout = Duration::milliseconds(max_wait_ms);
if sock.wait(timeout, requested, &mut occurred as *mut SockEvent) {
occurred & SOCK_RECV != 0
} else {
false
}
}
This pattern allows you to build sophisticated event loops or integrate bitcoin-sock into existing synchronous multiplexing infrastructure.
trace!, debug!, and warn! macros. You should configure your logging/tracing backend (for example via tracing or log) to obtain detailed diagnostics about socket behaviour.send_complete, recv_until_terminator) panic on unrecoverable conditions, as they are modeled after C++ exceptions. If your application requires error-returning APIs, wrap these functions and translate panic payloads into typed error values.ThreadInterrupt type exposing an as_bool() method to indicate whether a cooperative interrupt has been requested. Integrate this with your own cancellation infrastructure.use_poll feature switches the readiness mechanism from select to poll. This is useful when dealing with many file descriptors or when you need to avoid the FD_SETSIZE limit.bitcoin-sock0.1.19klebs <none>This crate appears to be a Rust reimplementation/port of socket handling from Bitcoin Core, with the goal of preserving semantics and behaviour while leveraging Rust's ownership model and RAII.
Reminder: This README was generated automatically by an AI model. It may omit details or slightly misrepresent edge semantics. Consult the source code in the repository for definitive behaviour.