Skip to main content

tonic_reflection/server/
mod.rs

1/*
2 *
3 * Copyright 2025 gRPC authors.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 */
24
25use std::collections::HashMap;
26use std::fmt::{Display, Formatter};
27use std::sync::Arc;
28
29use prost::encoding::{WireType, decode_key, decode_varint};
30use prost::{DecodeError, Message};
31use prost_types::{
32    DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorProto,
33    FileDescriptorSet,
34};
35use tonic::Status;
36
37/// v1 interface for the gRPC Reflection Service server.
38pub mod v1;
39/// v1alpha interface for the gRPC Reflection Service server.
40pub mod v1alpha;
41
42/// A builder used to construct a gRPC Reflection Service.
43#[derive(Debug)]
44pub struct Builder<'b> {
45    file_descriptor_sets: Vec<FileDescriptorSet>,
46    encoded_file_descriptor_sets: Vec<&'b [u8]>,
47    include_reflection_service: bool,
48
49    service_names: Vec<String>,
50    use_all_service_names: bool,
51}
52
53impl<'b> Builder<'b> {
54    /// Create a new builder that can configure a gRPC Reflection Service.
55    pub fn configure() -> Self {
56        Builder {
57            file_descriptor_sets: Vec::new(),
58            encoded_file_descriptor_sets: Vec::new(),
59            include_reflection_service: true,
60
61            service_names: Vec::new(),
62            use_all_service_names: true,
63        }
64    }
65
66    /// Registers an instance of `prost_types::FileDescriptorSet` with the gRPC Reflection
67    /// Service builder.
68    pub fn register_file_descriptor_set(mut self, file_descriptor_set: FileDescriptorSet) -> Self {
69        self.file_descriptor_sets.push(file_descriptor_set);
70        self
71    }
72
73    /// Registers a byte slice containing an encoded `prost_types::FileDescriptorSet` with
74    /// the gRPC Reflection Service builder.
75    pub fn register_encoded_file_descriptor_set(
76        mut self,
77        encoded_file_descriptor_set: &'b [u8],
78    ) -> Self {
79        self.encoded_file_descriptor_sets
80            .push(encoded_file_descriptor_set);
81        self
82    }
83
84    /// Serve the gRPC Reflection Service descriptor via the Reflection Service. This is enabled
85    /// by default - set `include` to false to disable.
86    pub fn include_reflection_service(mut self, include: bool) -> Self {
87        self.include_reflection_service = include;
88        self
89    }
90
91    /// Advertise a fully-qualified gRPC service name.
92    ///
93    /// If not called, then all services present in the registered file descriptor sets
94    /// will be advertised.
95    pub fn with_service_name(mut self, name: impl Into<String>) -> Self {
96        self.use_all_service_names = false;
97        self.service_names.push(name.into());
98        self
99    }
100
101    /// Build a v1 gRPC Reflection Service to be served via Tonic.
102    pub fn build_v1(
103        mut self,
104    ) -> Result<v1::ServerReflectionServer<impl v1::ServerReflection>, Error> {
105        if self.include_reflection_service {
106            self = self.register_encoded_file_descriptor_set(crate::pb::v1::FILE_DESCRIPTOR_SET);
107        }
108
109        Ok(v1::ServerReflectionServer::new(
110            v1::ReflectionService::from(ReflectionServiceState::new(
111                self.service_names,
112                self.encoded_file_descriptor_sets,
113                self.file_descriptor_sets,
114                self.use_all_service_names,
115            )?),
116        ))
117    }
118
119    /// Build a v1alpha gRPC Reflection Service to be served via Tonic.
120    pub fn build_v1alpha(
121        mut self,
122    ) -> Result<v1alpha::ServerReflectionServer<impl v1alpha::ServerReflection>, Error> {
123        if self.include_reflection_service {
124            self =
125                self.register_encoded_file_descriptor_set(crate::pb::v1alpha::FILE_DESCRIPTOR_SET);
126        }
127
128        Ok(v1alpha::ServerReflectionServer::new(
129            v1alpha::ReflectionService::from(ReflectionServiceState::new(
130                self.service_names,
131                self.encoded_file_descriptor_sets,
132                self.file_descriptor_sets,
133                self.use_all_service_names,
134            )?),
135        ))
136    }
137}
138
139#[derive(Debug)]
140struct ReflectionServiceState {
141    service_names: Vec<String>,
142    // The original, encoded bytes of each `FileDescriptorProto` are stored (and
143    // served) verbatim rather than a decoded `prost_types::FileDescriptorProto`.
144    // prost silently drops unknown fields on decode, so a decode/re-encode
145    // round-trip strips custom options / extensions (e.g. `google.api.http`);
146    // keeping the raw bytes preserves them for reflection consumers.
147    files: HashMap<String, Arc<[u8]>>,
148    symbols: HashMap<String, Arc<[u8]>>,
149}
150
151impl ReflectionServiceState {
152    fn new(
153        service_names: Vec<String>,
154        encoded_file_descriptor_sets: Vec<&[u8]>,
155        file_descriptor_sets: Vec<FileDescriptorSet>,
156        use_all_service_names: bool,
157    ) -> Result<Self, Error> {
158        let mut state = ReflectionServiceState {
159            service_names,
160            files: HashMap::new(),
161            symbols: HashMap::new(),
162        };
163
164        // The builder accepts descriptor sets in two forms: raw bytes (via
165        // `register_encoded_file_descriptor_set`) and already-decoded
166        // `prost_types::FileDescriptorSet`s (via `register_file_descriptor_set`).
167        //
168        // The encoded sets are registered first, on purpose: `register_file`
169        // keeps the first entry seen for a given file name (see below), and the
170        // encoded form is the one that faithfully preserves custom options. So
171        // if the same file arrives in both forms, the lossless bytes win.
172        //
173        // Each encoded set is split into its per-file byte ranges without
174        // decoding the inner message, so the original bytes (including custom
175        // options such as `google.api.http`) are retained and served unchanged.
176        for encoded in encoded_file_descriptor_sets {
177            for raw in split_file_descriptor_set(encoded)? {
178                let fd = FileDescriptorProto::decode(raw.as_slice())?;
179                state.register_file(&fd, Arc::from(raw), use_all_service_names)?;
180            }
181        }
182
183        // Already-decoded sets carry no original bytes, so each file has to be
184        // re-encoded. This is lossy for custom options, but unavoidable: those
185        // options were already dropped when the caller decoded the set into
186        // `prost_types` (which has no field to hold them). Callers that need
187        // custom options preserved should use `register_encoded_file_descriptor_set`
188        // instead. Registered second, as a fallback for files not already
189        // supplied in encoded form above.
190        for fds in file_descriptor_sets {
191            for fd in fds.file {
192                let raw = fd.encode_to_vec();
193                state.register_file(&fd, Arc::from(raw), use_all_service_names)?;
194            }
195        }
196
197        Ok(state)
198    }
199
200    fn register_file(
201        &mut self,
202        fd: &FileDescriptorProto,
203        raw: Arc<[u8]>,
204        use_all_service_names: bool,
205    ) -> Result<(), Error> {
206        let name = match fd.name.clone() {
207            None => {
208                return Err(Error::InvalidFileDescriptorSet("missing name".to_string()));
209            }
210            Some(n) => n,
211        };
212
213        // First registration for a file name wins; later duplicates are ignored.
214        // `new` registers the option-preserving encoded sets before the
215        // re-encoded ones, so a file present in both keeps its original bytes.
216        if self.files.contains_key(&name) {
217            return Ok(());
218        }
219
220        self.files.insert(name, raw.clone());
221        self.process_file(fd, &raw, use_all_service_names)
222    }
223
224    fn process_file(
225        &mut self,
226        fd: &FileDescriptorProto,
227        raw: &Arc<[u8]>,
228        use_all_service_names: bool,
229    ) -> Result<(), Error> {
230        let prefix = &fd.package.clone().unwrap_or_default();
231
232        for msg in &fd.message_type {
233            self.process_message(raw, prefix, msg)?;
234        }
235
236        for en in &fd.enum_type {
237            self.process_enum(raw, prefix, en)?;
238        }
239
240        for service in &fd.service {
241            let service_name = extract_name(prefix, "service", service.name.as_ref())?;
242            if use_all_service_names {
243                self.service_names.push(service_name.clone());
244            }
245            self.symbols.insert(service_name.clone(), raw.clone());
246
247            for method in &service.method {
248                let method_name = extract_name(&service_name, "method", method.name.as_ref())?;
249                self.symbols.insert(method_name, raw.clone());
250            }
251        }
252
253        Ok(())
254    }
255
256    fn process_message(
257        &mut self,
258        raw: &Arc<[u8]>,
259        prefix: &str,
260        msg: &DescriptorProto,
261    ) -> Result<(), Error> {
262        let message_name = extract_name(prefix, "message", msg.name.as_ref())?;
263        self.symbols.insert(message_name.clone(), raw.clone());
264
265        for nested in &msg.nested_type {
266            self.process_message(raw, &message_name, nested)?;
267        }
268
269        for en in &msg.enum_type {
270            self.process_enum(raw, &message_name, en)?;
271        }
272
273        for field in &msg.field {
274            self.process_field(raw, &message_name, field)?;
275        }
276
277        for oneof in &msg.oneof_decl {
278            let oneof_name = extract_name(&message_name, "oneof", oneof.name.as_ref())?;
279            self.symbols.insert(oneof_name, raw.clone());
280        }
281
282        Ok(())
283    }
284
285    fn process_enum(
286        &mut self,
287        raw: &Arc<[u8]>,
288        prefix: &str,
289        en: &EnumDescriptorProto,
290    ) -> Result<(), Error> {
291        let enum_name = extract_name(prefix, "enum", en.name.as_ref())?;
292        self.symbols.insert(enum_name.clone(), raw.clone());
293
294        for value in &en.value {
295            let value_name = extract_name(&enum_name, "enum value", value.name.as_ref())?;
296            self.symbols.insert(value_name, raw.clone());
297        }
298
299        Ok(())
300    }
301
302    fn process_field(
303        &mut self,
304        raw: &Arc<[u8]>,
305        prefix: &str,
306        field: &FieldDescriptorProto,
307    ) -> Result<(), Error> {
308        let field_name = extract_name(prefix, "field", field.name.as_ref())?;
309        self.symbols.insert(field_name, raw.clone());
310        Ok(())
311    }
312
313    fn list_services(&self) -> &[String] {
314        &self.service_names
315    }
316
317    fn symbol_by_name(&self, symbol: &str) -> Result<Vec<u8>, Status> {
318        match self.symbols.get(symbol) {
319            None => Err(Status::not_found(format!("symbol '{symbol}' not found"))),
320            Some(raw) => Ok(raw.to_vec()),
321        }
322    }
323
324    fn file_by_filename(&self, filename: &str) -> Result<Vec<u8>, Status> {
325        match self.files.get(filename) {
326            None => Err(Status::not_found(format!("file '{filename}' not found"))),
327            Some(raw) => Ok(raw.to_vec()),
328        }
329    }
330}
331
332/// Splits an encoded `FileDescriptorSet` into the raw, unmodified bytes of each
333/// contained `FileDescriptorProto` (wire field 1), without decoding the inner
334/// messages.
335///
336/// Serving these original bytes preserves custom options / extensions (e.g.
337/// `google.api.http`) that prost silently drops when a `FileDescriptorProto` is
338/// decoded and re-encoded.
339fn split_file_descriptor_set(mut buf: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
340    // Field number of `repeated FileDescriptorProto file = 1;`.
341    const FILE_FIELD_TAG: u32 = 1;
342
343    let truncated = || {
344        Error::InvalidFileDescriptorSet("unexpected end of FileDescriptorSet buffer".to_string())
345    };
346
347    let mut files = Vec::new();
348    while !buf.is_empty() {
349        let (tag, wire_type) = decode_key(&mut buf)?;
350        match wire_type {
351            WireType::LengthDelimited => {
352                let len = decode_varint(&mut buf)? as usize;
353                if len > buf.len() {
354                    return Err(truncated());
355                }
356                let (field, rest) = buf.split_at(len);
357                if tag == FILE_FIELD_TAG {
358                    files.push(field.to_vec());
359                }
360                buf = rest;
361            }
362            WireType::Varint => {
363                decode_varint(&mut buf)?;
364            }
365            WireType::ThirtyTwoBit => {
366                if buf.len() < 4 {
367                    return Err(truncated());
368                }
369                buf = &buf[4..];
370            }
371            WireType::SixtyFourBit => {
372                if buf.len() < 8 {
373                    return Err(truncated());
374                }
375                buf = &buf[8..];
376            }
377            WireType::StartGroup | WireType::EndGroup => {
378                return Err(Error::InvalidFileDescriptorSet(
379                    "unexpected group wire type in FileDescriptorSet".to_string(),
380                ));
381            }
382        }
383    }
384
385    Ok(files)
386}
387
388fn extract_name(
389    prefix: &str,
390    name_type: &str,
391    maybe_name: Option<&String>,
392) -> Result<String, Error> {
393    match maybe_name {
394        None => Err(Error::InvalidFileDescriptorSet(format!(
395            "missing {name_type} name"
396        ))),
397        Some(name) => {
398            if prefix.is_empty() {
399                Ok(name.to_string())
400            } else {
401                Ok(format!("{prefix}.{name}"))
402            }
403        }
404    }
405}
406
407/// Represents an error in the construction of a gRPC Reflection Service.
408#[derive(Debug)]
409pub enum Error {
410    /// An error was encountered decoding a `prost_types::FileDescriptorSet` from a buffer.
411    DecodeError(prost::DecodeError),
412    /// An invalid `prost_types::FileDescriptorProto` was encountered.
413    InvalidFileDescriptorSet(String),
414}
415
416impl From<DecodeError> for Error {
417    fn from(e: DecodeError) -> Self {
418        Error::DecodeError(e)
419    }
420}
421
422impl std::error::Error for Error {}
423
424impl Display for Error {
425    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
426        match self {
427            Error::DecodeError(_) => f.write_str("error decoding FileDescriptorSet from buffer"),
428            Error::InvalidFileDescriptorSet(s) => {
429                write!(f, "invalid FileDescriptorSet - {s}")
430            }
431        }
432    }
433}