use livekit_common::ParticipantIdentity;
use livekit_protocol as proto;
use std::{error::Error, fmt::Display, time::Duration};
#[derive(Debug, Clone)]
pub struct PerformRpcData {
pub destination_identity: String,
pub method: String,
pub payload: String,
pub response_timeout: Duration,
pub max_round_trip_latency: Duration,
}
impl PerformRpcData {
pub fn new(
destination_identity: impl Into<ParticipantIdentity>,
method: impl Into<String>,
) -> Self {
Self::default().with_destination_identity(destination_identity).with_method(method)
}
pub fn with_destination_identity(
mut self,
destination_identity: impl Into<ParticipantIdentity>,
) -> Self {
let identity: ParticipantIdentity = destination_identity.into();
self.destination_identity = identity.into();
self
}
pub fn with_method(mut self, method: impl Into<String>) -> Self {
self.method = method.into();
self
}
pub fn with_payload(mut self, payload: impl Into<String>) -> Self {
self.payload = payload.into();
self
}
pub fn with_response_timeout(mut self, response_timeout: Duration) -> Self {
self.response_timeout = response_timeout;
self
}
pub fn with_max_round_trip_latency(mut self, max_round_trip_latency: Duration) -> Self {
self.max_round_trip_latency = max_round_trip_latency;
self
}
}
impl Default for PerformRpcData {
fn default() -> Self {
Self {
destination_identity: Default::default(),
method: Default::default(),
payload: Default::default(),
response_timeout: Duration::from_secs(15),
max_round_trip_latency: Duration::from_secs(7),
}
}
}
#[derive(Debug, Clone)]
pub struct RpcInvocationData {
pub request_id: String,
pub caller_identity: ParticipantIdentity,
pub payload: String,
pub response_timeout: Duration,
}
#[derive(Debug, Clone)]
pub struct RpcError {
pub code: u32,
pub message: String,
pub data: Option<String>,
}
impl RpcError {
pub const MAX_MESSAGE_BYTES: usize = 256;
pub const MAX_DATA_BYTES: usize = MAX_V1_PAYLOAD_BYTES;
pub fn new(code: u32, message: String, data: Option<String>) -> Self {
Self {
code,
message: truncate_bytes(&message, Self::MAX_MESSAGE_BYTES),
data: data.map(|d| truncate_bytes(&d, Self::MAX_DATA_BYTES)),
}
}
pub fn from_proto(error: proto::RpcError) -> Self {
Self::new(error.code, error.message, Some(error.data))
}
pub fn to_proto(&self) -> proto::RpcError {
proto::RpcError {
code: self.code,
message: self.message.clone(),
data: self.data.clone().unwrap_or_default(),
}
}
}
impl Display for RpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "RPC Error: {} ({})", self.message, self.code)
}
}
impl Error for RpcError {}
#[derive(Debug, Clone, Copy)]
pub enum RpcErrorCode {
ApplicationError = 1500,
ConnectionTimeout = 1501,
ResponseTimeout = 1502,
RecipientDisconnected = 1503,
ResponsePayloadTooLarge = 1504,
SendFailed = 1505,
UnsupportedMethod = 1400,
RecipientNotFound = 1401,
RequestPayloadTooLarge = 1402,
UnsupportedServer = 1403,
UnsupportedVersion = 1404,
}
impl RpcErrorCode {
pub(crate) fn message(&self) -> &'static str {
match self {
Self::ApplicationError => "Application error in method handler",
Self::ConnectionTimeout => "Connection timeout",
Self::ResponseTimeout => "Response timeout",
Self::RecipientDisconnected => "Recipient disconnected",
Self::ResponsePayloadTooLarge => "Response payload too large",
Self::SendFailed => "Failed to send",
Self::UnsupportedMethod => "Method not supported at destination",
Self::RecipientNotFound => "Recipient not found",
Self::RequestPayloadTooLarge => "Request payload too large",
Self::UnsupportedServer => "RPC not supported by server",
Self::UnsupportedVersion => "Unsupported RPC version",
}
}
}
impl RpcError {
pub fn built_in(code: RpcErrorCode, data: Option<String>) -> Self {
Self::new(code as u32, code.message().to_string(), data)
}
}
pub const MAX_V1_PAYLOAD_BYTES: usize = 15360;
fn byte_length(s: &str) -> usize {
s.as_bytes().len()
}
fn truncate_bytes(s: &str, max_bytes: usize) -> String {
if byte_length(s) <= max_bytes {
return s.to_string();
}
let mut result = String::new();
for c in s.chars() {
if byte_length(&(result.clone() + &c.to_string())) > max_bytes {
break;
}
result.push(c);
}
result
}