use std::collections::{HashMap, HashSet};
use crate::Transcoder;
use prost::Message as _;
use prost_types::FileDescriptorProto;
use tonic::transport::Channel;
use tonic::{Code, Status};
struct RawFile {
bytes: Vec<u8>,
deps: Vec<String>,
}
#[allow(clippy::enum_variant_names)] pub mod v1 {
tonic::include_proto!("grpc.reflection.v1");
}
#[allow(clippy::enum_variant_names)] pub mod v1alpha {
tonic::include_proto!("grpc.reflection.v1alpha");
}
#[derive(Clone, Copy)]
enum Version {
V1,
V1alpha,
}
pub async fn load_all(channel: &Channel) -> Result<Transcoder, Status> {
let (services, version) = match list_services_v1(channel.clone()).await {
Err(e) if e.code() == Code::Unimplemented => {
(list_services_v1alpha(channel.clone()).await?, Version::V1alpha)
}
other => (other?, Version::V1),
};
let mut files: HashMap<String, RawFile> = HashMap::new();
for service in services {
let fetched = match version {
Version::V1 => fetch_v1(channel.clone(), &service).await,
Version::V1alpha => fetch_v1alpha(channel.clone(), &service).await,
};
match fetched {
Ok(svc_files) => files.extend(svc_files),
Err(e) => tracing::warn!("reflection: skipping service {service}: {e}"),
}
}
if files.is_empty() {
return Err(Status::unavailable("upstream reflection returned no descriptors"));
}
let mut set = Vec::new();
for raw in topo_sort(files) {
push_file_descriptor(&mut set, &raw);
}
Transcoder::from_file_descriptor_set(&set)
.map_err(|e| Status::internal(format!("build descriptor pool: {e}")))
}
fn file_meta(bytes: &[u8]) -> Result<(String, Vec<String>), Status> {
let fd = FileDescriptorProto::decode(bytes)
.map_err(|e| Status::internal(format!("bad descriptor: {e}")))?;
Ok((fd.name().to_string(), fd.dependency))
}
fn topo_sort(files: HashMap<String, RawFile>) -> Vec<Vec<u8>> {
fn visit(
name: &str,
files: &HashMap<String, RawFile>,
seen: &mut HashSet<String>,
out: &mut Vec<Vec<u8>>,
) {
if !seen.insert(name.to_string()) {
return;
}
if let Some(f) = files.get(name) {
for dep in &f.deps {
visit(dep, files, seen, out);
}
out.push(f.bytes.clone());
}
}
let mut seen = HashSet::new();
let mut out = Vec::with_capacity(files.len());
let mut names: Vec<String> = files.keys().cloned().collect();
names.sort(); for name in names {
visit(&name, &files, &mut seen, &mut out);
}
out
}
fn push_file_descriptor(buf: &mut Vec<u8>, raw: &[u8]) {
buf.push(0x0A);
let mut len = raw.len() as u64;
loop {
let mut byte = (len & 0x7F) as u8;
len >>= 7;
if len != 0 {
byte |= 0x80;
}
buf.push(byte);
if len == 0 {
break;
}
}
buf.extend_from_slice(raw);
}
macro_rules! reflection_driver {
($fn_name:ident, $module:ident) => {
async fn $fn_name(
channel: Channel,
symbol: &str,
) -> Result<HashMap<String, RawFile>, Status> {
use $module::server_reflection_client::ServerReflectionClient;
use $module::server_reflection_request::MessageRequest;
use $module::server_reflection_response::MessageResponse;
use $module::ServerReflectionRequest;
let make = |req: MessageRequest| ServerReflectionRequest {
host: String::new(),
message_request: Some(req),
};
let (tx, rx) = tokio::sync::mpsc::channel::<ServerReflectionRequest>(16);
let mut client = ServerReflectionClient::new(channel);
let mut inbound = client
.server_reflection_info(tokio_stream::wrappers::ReceiverStream::new(rx))
.await?
.into_inner();
let send = |req| {
let tx = tx.clone();
async move {
tx.send(req).await.map_err(|_| Status::internal("reflection request channel closed"))
}
};
send(make(MessageRequest::FileContainingSymbol(symbol.to_string()))).await?;
let mut files: HashMap<String, RawFile> = HashMap::new();
let mut requested: HashSet<String> = HashSet::new();
let mut pending: usize = 1;
while pending > 0 {
let Some(resp) = inbound.message().await? else { break };
pending -= 1;
match resp.message_response {
Some(MessageResponse::FileDescriptorResponse(fdr)) => {
for raw in fdr.file_descriptor_proto {
let (name, deps) = file_meta(&raw)?;
for dep in &deps {
if !files.contains_key(dep) && requested.insert(dep.clone()) {
send(make(MessageRequest::FileByFilename(dep.clone()))).await?;
pending += 1;
}
}
files.insert(name, RawFile { bytes: raw, deps });
}
}
Some(MessageResponse::ErrorResponse(e)) => {
return Err(Status::new(Code::from(e.error_code), e.error_message));
}
_ => {}
}
}
drop(tx);
Ok(files)
}
};
}
reflection_driver!(fetch_v1, v1);
reflection_driver!(fetch_v1alpha, v1alpha);
macro_rules! list_services_driver {
($fn_name:ident, $module:ident) => {
async fn $fn_name(channel: Channel) -> Result<Vec<String>, Status> {
use $module::server_reflection_client::ServerReflectionClient;
use $module::server_reflection_request::MessageRequest;
use $module::server_reflection_response::MessageResponse;
use $module::ServerReflectionRequest;
let (tx, rx) = tokio::sync::mpsc::channel::<ServerReflectionRequest>(4);
let mut client = ServerReflectionClient::new(channel);
let mut inbound = client
.server_reflection_info(tokio_stream::wrappers::ReceiverStream::new(rx))
.await?
.into_inner();
tx.send(ServerReflectionRequest {
host: String::new(),
message_request: Some(MessageRequest::ListServices(String::new())),
})
.await
.map_err(|_| Status::internal("reflection request channel closed"))?;
let resp = inbound
.message()
.await?
.ok_or_else(|| Status::internal("reflection stream closed before response"))?;
drop(tx);
match resp.message_response {
Some(MessageResponse::ListServicesResponse(r)) => {
Ok(r.service.into_iter().map(|s| s.name).collect())
}
Some(MessageResponse::ErrorResponse(e)) => {
Err(Status::new(Code::from(e.error_code), e.error_message))
}
_ => Err(Status::internal("unexpected reflection response to list_services")),
}
}
};
}
list_services_driver!(list_services_v1, v1);
list_services_driver!(list_services_v1alpha, v1alpha);