use crate::feed::FeedPosition;
pub use crate::replica_error::ReplicaError;
pub use crate::replica_event::ReplicaEvent;
use kevy_resp::Argv;
use std::io::{self, Read, Write};
use std::net::{TcpStream, ToSocketAddrs};
pub use crate::replica_connect::ConnectOptions;
#[cfg(feature = "secure")]
pub use crate::replica_secure::ReplicaSecurity;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct DecodedFrame {
pub offset: u64,
pub argv: Argv,
}
impl DecodedFrame {
pub fn new(offset: u64, argv: Argv) -> Self {
Self { offset, argv }
}
}
#[derive(Debug)]
pub struct ReplicaClient {
pub(crate) sock: TcpStream,
pub(crate) buf: Vec<u8>,
pub(crate) cursor: usize,
pub(crate) primary_at_handshake: FeedPosition,
pub(crate) expected_offset: u64,
pub(crate) in_snapshot: bool,
pub(crate) noise: Option<crate::replica_secure::ClientNoise>,
}
impl ReplicaClient {
pub fn connect<A: ToSocketAddrs>(
addr: A,
replica_id: &str,
from_offset: u64,
) -> Result<Self, ReplicaError> {
Self::connect_with(
addr,
&ConnectOptions::new(replica_id).with_from(FeedPosition::new(0, from_offset)),
)
}
pub(crate) fn connect_plain<A: ToSocketAddrs>(
addr: A,
opts: &ConnectOptions,
) -> Result<Self, ReplicaError> {
let connect_timeout = opts.timeout;
let replica_id = opts.replica_id.as_str();
let mut sock = connect_stream(addr, connect_timeout)?;
let req = encode_replicate_from(opts.from, replica_id);
sock.write_all(&req)?;
sock.set_read_timeout(Some(connect_timeout))?;
let primary_at_handshake = read_ack(&mut sock)?;
sock.set_read_timeout(None)?;
sock.set_nonblocking(false)?;
Ok(ReplicaClient {
sock,
buf: Vec::with_capacity(8 * 1024),
cursor: 0,
primary_at_handshake,
expected_offset: opts.from.offset,
in_snapshot: false,
noise: None,
})
}
#[inline]
pub fn primary_at_handshake(&self) -> FeedPosition {
self.primary_at_handshake
}
pub fn socket_handle(&self) -> io::Result<TcpStream> {
self.sock.try_clone()
}
pub fn send_ack(&mut self, offset: u64) -> std::io::Result<()> {
use std::io::Write as _;
let ack = crate::wire::encode_replconf_ack(offset);
match self.noise.as_mut() {
Some(n) => n.write(&mut self.sock, &ack),
None => self.sock.write_all(&ack),
}
}
pub fn expected_offset(&self) -> u64 {
self.expected_offset
}
pub fn next_frame(&mut self) -> Option<Result<DecodedFrame, ReplicaError>> {
loop {
match self.next_event()? {
Ok(ReplicaEvent::Frame(f)) => return Some(Ok(f)),
Ok(ReplicaEvent::Ping { .. }) => {}
Ok(_) => return Some(Err(ReplicaError::SnapshotInProgress)),
Err(e) => return Some(Err(e)),
}
}
}
pub(crate) fn maybe_compact_buf(&mut self) {
if self.cursor >= 4 * 1024 {
self.buf.drain(..self.cursor);
self.cursor = 0;
}
}
}
impl Iterator for ReplicaClient {
type Item = Result<DecodedFrame, ReplicaError>;
fn next(&mut self) -> Option<Self::Item> {
self.next_frame()
}
}
pub(crate) fn connect_stream<A: ToSocketAddrs>(
addr: A,
connect_timeout: Duration,
) -> Result<TcpStream, ReplicaError> {
let mut last_err: Option<io::Error> = None;
for sa in addr.to_socket_addrs().map_err(ReplicaError::Io)? {
match TcpStream::connect_timeout(&sa, connect_timeout) {
Ok(s) => return Ok(s),
Err(e) => last_err = Some(e),
}
}
Err(ReplicaError::Io(last_err.unwrap_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "no socket address resolved")
})))
}
pub(crate) fn encode_replicate_from(from: FeedPosition, replica_id: &str) -> Vec<u8> {
let mut v = Vec::with_capacity(80 + replica_id.len());
v.extend_from_slice(b"*6\r\n");
let gen_str = from.generation.to_string();
let offset_str = from.offset.to_string();
for arg in [
b"REPLICATE".as_slice(),
b"FROM",
gen_str.as_bytes(),
offset_str.as_bytes(),
b"ID",
replica_id.as_bytes(),
] {
let header = format!("${}\r\n", arg.len());
v.extend_from_slice(header.as_bytes());
v.extend_from_slice(arg);
v.extend_from_slice(b"\r\n");
}
v
}
fn read_ack(sock: &mut TcpStream) -> Result<FeedPosition, ReplicaError> {
let mut line = Vec::with_capacity(32);
let mut b = [0u8; 1];
loop {
match sock.read(&mut b) {
Ok(0) => return Err(ReplicaError::HandshakeRejected),
Ok(_) => {
line.push(b[0]);
if line.len() >= 2 && line.ends_with(b"\r\n") {
break;
}
if line.len() > 256 {
return Err(ReplicaError::AckMalformed);
}
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(ReplicaError::Io(e)),
}
}
parse_ack_line(&line)
}
pub(crate) fn parse_ack_line(line: &[u8]) -> Result<FeedPosition, ReplicaError> {
let body = line.strip_suffix(b"\r\n").ok_or(ReplicaError::AckMalformed)?;
let body = body.strip_prefix(b"+ACK ").ok_or(ReplicaError::AckMalformed)?;
let s = std::str::from_utf8(body).map_err(|_| ReplicaError::AckMalformed)?;
let (gen_s, off_s) = s.split_once(' ').ok_or(ReplicaError::AckMalformed)?;
let generation = gen_s.parse::<u64>().map_err(|_| ReplicaError::AckMalformed)?;
let offset = off_s.parse::<u64>().map_err(|_| ReplicaError::AckMalformed)?;
Ok(FeedPosition::new(generation, offset))
}
#[cfg(test)]
impl ReplicaClient {
pub(crate) fn from_socket_for_test(sock: TcpStream, expected_offset: u64) -> Self {
Self {
sock,
buf: Vec::with_capacity(8 * 1024),
cursor: 0,
primary_at_handshake: FeedPosition::new(1, expected_offset),
expected_offset,
in_snapshot: false,
noise: None,
}
}
}
#[cfg(test)]
#[path = "replica_tests.rs"]
mod tests;