use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::path::Path;
use std::process::Stdio;
use std::sync::{Arc, PoisonError};
use std::time::{Duration, Instant};
use anyhow::{Context, Result, anyhow, bail};
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64;
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, ChildStdout, Command};
use tokio::sync::{mpsc, watch};
use crate::targets::{
CommandSpec, SSH_RETRY_ATTEMPTS, SshAdmission, SshPermit, is_transport_rejection,
};
use mj_core::config::harness_authentication_marker;
use mj_core::credentials::{
CredentialSnapshot, CredentialSyncAction, CredentialSyncHandle, CredentialSyncOutcome,
CredentialSyncResult, CredentialSyncTarget, SYNC_INTERVAL, SyncAction, SyncTrigger, enqueue,
profiles_with_targets, read_credential_file, reconcile, validate_credential_payload,
write_credential_file,
};
use mj_core::elicitation::ElicitationResponse;
use mj_core::relay::{
MAX_FRAME_BYTES, RELAY_EVENT_GENESIS_DIGEST, RELAY_MIN_PROTOCOL_VERSION,
RELAY_PROTOCOL_VERSION, RelayCommand, RelayCursor, RelayErrorCode, RelayEvent,
RelayOperationalState, RelayProtocolError, RelayRequest, RelayRequestEnvelope,
RelayResponseBody, RelayResponseEnvelope, RelayResponsePayload, RelayVersionRange,
ReviewerRequest, validate_relay_event,
};
pub use mj_client::session::{RelayAttachment, StartedReviewer};
use mj_core::worker_launch::ReviewerLaunchConfig;
const RELAY_RPC_TIMEOUT: Duration = Duration::from_secs(15);
const RELAY_SLOW_OPERATION_WARNING: Duration = Duration::from_secs(5);
const RELAY_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(300);
const RELAY_HISTORY_TIMEOUT: Duration = Duration::from_secs(900);
const RELAY_ACKNOWLEDGE_TIMEOUT: Duration = Duration::from_secs(300);
const REVIEW_CAPTURE_TIMEOUT: Duration = Duration::from_secs(300);
const REVIEW_ANALYSIS_TIMEOUT: Duration = Duration::from_secs(660);
const RELAY_PROXY_DETACH_GRACE: Duration = Duration::from_millis(500);
const RELAY_PROXY_REAP_POLL: Duration = Duration::from_millis(10);
const RELAY_PROXY_STDERR_TAIL: usize = 10;
type ProxyStderrTail = Arc<std::sync::Mutex<VecDeque<String>>>;
async fn drain_proxy_stderr(
errors: tokio::process::ChildStderr,
purpose: String,
session_id: String,
tail: ProxyStderrTail,
) {
let mut lines = BufReader::new(errors).lines();
loop {
match lines.next_line().await {
Ok(Some(line)) if line.trim().is_empty() => continue,
Ok(Some(line)) => {
tracing::warn!(%session_id, %purpose, %line, "relay proxy stderr");
let mut tail = tail.lock().unwrap_or_else(PoisonError::into_inner);
if tail.len() == RELAY_PROXY_STDERR_TAIL {
tail.pop_front();
}
tail.push_back(line);
}
Ok(None) => return,
Err(error) => {
tracing::warn!(%session_id, %purpose, %error, "read relay proxy stderr");
return;
}
}
}
}
mod errors;
pub use errors::*;
mod connect;
mod exchange;
mod relay;
mod reviewer;
mod transport;
use transport::*;
mod credential_sync;
pub use credential_sync::*;
pub struct RelayClient {
child: Option<Child>,
input: Option<ChildStdin>,
output: BufReader<ChildStdout>,
request_timeout: Duration,
abandoned: Option<String>,
next_request: u64,
connection_nonce: u64,
protocol_version: u32,
session_id: String,
relay_version: String,
worker_build: Option<String>,
latest_ordinal: u64,
latest_digest: String,
}
#[cfg(test)]
mod tests;