1use super::{codec, proto::gnmi as pb};
3use serde::{Deserialize, Serialize};
4use std::{collections::BTreeMap, io::Write};
5use tonic::Status;
6
7#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
8#[serde(deny_unknown_fields)]
9pub struct Path {
10 #[serde(default)]
11 pub origin: String,
12 #[serde(default)]
13 pub target: String,
14 #[serde(default)]
15 pub elem: Vec<Element>,
16}
17#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(deny_unknown_fields)]
19pub struct Element {
20 pub name: String,
21 #[serde(default)]
22 pub key: BTreeMap<String, String>,
23}
24#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
25#[serde(
26 tag = "kind",
27 content = "value",
28 rename_all = "snake_case",
29 deny_unknown_fields
30)]
31pub enum Value {
32 String(String),
33 Int(String),
34 Uint(String),
35 Bool(bool),
36 Double(f64),
37 Decimal(Decimal),
38 Leaflist(Vec<Value>),
39 Json(serde_json::Value),
40 JsonIetf(serde_json::Value),
41 Ascii(String),
42}
43#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
44#[serde(deny_unknown_fields)]
45pub struct Decimal {
46 pub digits: String,
47 pub precision: u32,
48}
49#[derive(Clone, Debug, Serialize, Deserialize)]
50#[serde(deny_unknown_fields)]
51pub struct Update {
52 pub path: Path,
53 pub value: Value,
54 #[serde(default)]
55 pub duplicates: u32,
56}
57#[derive(Clone, Debug, Serialize, Deserialize)]
58#[serde(deny_unknown_fields)]
59pub struct Notification {
60 pub timestamp: String,
61 #[serde(default)]
62 pub prefix: Path,
63 #[serde(default)]
64 pub update: Vec<Update>,
65 #[serde(default)]
66 pub delete: Vec<Path>,
67 #[serde(default)]
68 pub atomic: bool,
69}
70fn limit(ok: bool) -> Result<(), Status> {
71 if ok {
72 Ok(())
73 } else {
74 Err(Status::resource_exhausted("gNMI typed value/path bound"))
75 }
76}
77fn text(value: &str, max: usize) -> Result<(), Status> {
78 limit(value.len() <= max)?;
79 if value.contains('\0') {
80 return Err(Status::invalid_argument("NUL in gNMI text"));
81 }
82 Ok(())
83}
84impl Path {
85 pub fn check(&self) -> Result<(), Status> {
86 text(&self.origin, 256)?;
87 text(&self.target, 256)?;
88 limit(self.elem.len() <= 32)?;
89 for elem in &self.elem {
90 text(&elem.name, 128)?;
91 if elem.name.is_empty() {
92 return Err(Status::invalid_argument("empty path element name"));
93 }
94 limit(elem.key.len() <= 8)?;
95 for (key, value) in &elem.key {
96 text(key, 128)?;
97 text(value, 1024)?;
98 if key.is_empty() {
99 return Err(Status::invalid_argument("empty path key"));
100 }
101 }
102 }
103 Ok(())
104 }
105 pub fn into_proto(self) -> Result<pb::Path, Status> {
106 self.check()?;
107 Ok(pb::Path {
108 element: vec![],
109 origin: self.origin,
110 target: self.target,
111 elem: self
112 .elem
113 .into_iter()
114 .map(|e| pb::PathElem {
115 name: e.name,
116 key: e.key.into_iter().collect(),
117 })
118 .collect(),
119 })
120 }
121 pub fn from_proto(path: Option<pb::Path>) -> Result<Self, Status> {
122 let path = path.unwrap_or_default();
123 if !path.element.is_empty() {
124 return Err(Status::unimplemented(
125 "deprecated string paths are excluded; use Path.elem",
126 ));
127 }
128 let result = Self {
129 origin: path.origin,
130 target: path.target,
131 elem: path
132 .elem
133 .into_iter()
134 .map(|e| Element {
135 name: e.name,
136 key: e.key.into_iter().collect(),
137 })
138 .collect(),
139 };
140 result.check()?;
141 Ok(result)
142 }
143}
144struct Writer(Vec<u8>);
145impl Write for Writer {
146 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
147 if self.0.len().saturating_add(bytes.len()) > codec::MAX_VALUE_BYTES {
148 return Err(std::io::Error::other("JSON bound"));
149 }
150 self.0.extend_from_slice(bytes);
151 Ok(bytes.len())
152 }
153 fn flush(&mut self) -> std::io::Result<()> {
154 Ok(())
155 }
156}
157pub fn json_bytes(value: &serde_json::Value) -> Result<Vec<u8>, Status> {
158 let mut writer = Writer(Vec::new());
159 serde_json::to_writer(&mut writer, value)
160 .map_err(|_| Status::resource_exhausted("gNMI JSON exceeds 64 KiB"))?;
161 super::json::parse(&writer.0)?;
162 Ok(writer.0)
163}
164impl Value {
165 pub fn into_proto(self) -> Result<pb::TypedValue, Status> {
166 use pb::typed_value::Value as V;
167 let value = match self {
168 Self::String(v) => {
169 limit(v.len() <= codec::MAX_VALUE_BYTES)?;
170 V::StringVal(v)
171 }
172 Self::Ascii(v) => {
173 limit(v.len() <= codec::MAX_VALUE_BYTES)?;
174 if !v.is_ascii() {
175 return Err(Status::invalid_argument("ASCII value is not ASCII"));
176 }
177 V::AsciiVal(v)
178 }
179 Self::Int(v) => V::IntVal(v.parse().map_err(|_| {
180 Status::invalid_argument("int value must be an i64 decimal string")
181 })?),
182 Self::Uint(v) => V::UintVal(v.parse().map_err(|_| {
183 Status::invalid_argument("uint value must be a u64 decimal string")
184 })?),
185 Self::Bool(v) => V::BoolVal(v),
186 Self::Double(v) => {
187 if !v.is_finite() {
188 return Err(Status::invalid_argument("nonfinite value"));
189 }
190 V::DoubleVal(v)
191 }
192 Self::Decimal(v) => {
193 if v.precision > 18 {
194 return Err(Status::invalid_argument("decimal precision exceeds 18"));
195 }
196 V::DecimalVal(pb::Decimal64 {
197 digits: v.digits.parse().map_err(|_| {
198 Status::invalid_argument("decimal digits must be an i64 string")
199 })?,
200 precision: v.precision,
201 })
202 }
203 Self::Json(v) => V::JsonVal(json_bytes(&v)?),
204 Self::JsonIetf(v) => V::JsonIetfVal(json_bytes(&v)?),
205 Self::Leaflist(values) => {
206 limit(values.len() <= 64)?;
207 if values
208 .iter()
209 .any(|v| matches!(v, Self::Leaflist(_) | Self::Json(_) | Self::JsonIetf(_)))
210 {
211 return Err(Status::invalid_argument(
212 "leaf-list elements must be scalars",
213 ));
214 }
215 V::LeaflistVal(pb::ScalarArray {
216 element: values
217 .into_iter()
218 .map(Self::into_proto)
219 .collect::<Result<_, _>>()?,
220 })
221 }
222 };
223 Ok(pb::TypedValue { value: Some(value) })
224 }
225 pub fn from_proto(value: pb::TypedValue) -> Result<Self, Status> {
226 use pb::typed_value::Value as V;
227 let result = match value
228 .value
229 .ok_or_else(|| Status::invalid_argument("missing TypedValue member"))?
230 {
231 V::StringVal(v) => Self::String(v),
232 V::AsciiVal(v) => Self::Ascii(v),
233 V::IntVal(v) => Self::Int(v.to_string()),
234 V::UintVal(v) => Self::Uint(v.to_string()),
235 V::BoolVal(v) => Self::Bool(v),
236 V::DoubleVal(v) => Self::Double(v),
237 V::FloatVal(v) => Self::Double(f64::from(v)),
238 V::DecimalVal(v) => Self::Decimal(Decimal {
239 digits: v.digits.to_string(),
240 precision: v.precision,
241 }),
242 V::JsonVal(v) => Self::Json(super::json::parse(&v)?),
243 V::JsonIetfVal(v) => Self::JsonIetf(super::json::parse(&v)?),
244 V::LeaflistVal(v) => Self::Leaflist(
245 v.element
246 .into_iter()
247 .map(Self::from_proto)
248 .collect::<Result<_, _>>()?,
249 ),
250 V::BytesVal(_) | V::AnyVal(_) | V::ProtoBytes(_) => {
251 return Err(Status::unimplemented("opaque values are excluded"))
252 }
253 };
254 result.clone().into_proto()?;
256 Ok(result)
257 }
258}
259impl Update {
260 pub fn into_proto(self) -> Result<pb::Update, Status> {
261 Ok(pb::Update {
262 path: Some(self.path.into_proto()?),
263 value: None,
264 val: Some(self.value.into_proto()?),
265 duplicates: self.duplicates,
266 })
267 }
268 pub fn from_proto(v: pb::Update) -> Result<Self, Status> {
269 if v.value.is_some() {
270 return Err(Status::unimplemented("legacy values are excluded"));
271 }
272 Ok(Self {
273 path: Path::from_proto(v.path)?,
274 value: Value::from_proto(
275 v.val
276 .ok_or_else(|| Status::invalid_argument("missing update value"))?,
277 )?,
278 duplicates: v.duplicates,
279 })
280 }
281}
282impl Notification {
283 pub fn into_proto(self) -> Result<pb::Notification, Status> {
284 limit(self.update.len() <= 256 && self.delete.len() <= 256)?;
285 Ok(pb::Notification {
286 timestamp: self.timestamp.parse().map_err(|_| {
287 Status::invalid_argument("timestamp must be i64 nanosecond decimal string")
288 })?,
289 prefix: Some(self.prefix.into_proto()?),
290 update: self
291 .update
292 .into_iter()
293 .map(Update::into_proto)
294 .collect::<Result<_, _>>()?,
295 delete: self
296 .delete
297 .into_iter()
298 .map(Path::into_proto)
299 .collect::<Result<_, _>>()?,
300 atomic: self.atomic,
301 })
302 }
303 pub fn from_proto(v: pb::Notification) -> Result<Self, Status> {
304 limit(v.update.len() <= 256 && v.delete.len() <= 256)?;
305 Ok(Self {
306 timestamp: v.timestamp.to_string(),
307 prefix: Path::from_proto(v.prefix)?,
308 update: v
309 .update
310 .into_iter()
311 .map(Update::from_proto)
312 .collect::<Result<_, _>>()?,
313 delete: v
314 .delete
315 .into_iter()
316 .map(|p| Path::from_proto(Some(p)))
317 .collect::<Result<_, _>>()?,
318 atomic: v.atomic,
319 })
320 }
321}
322pub fn encoding(name: &str) -> Result<i32, Status> {
323 Ok(match name {
324 "JSON" => 0,
325 "PROTO" => 2,
326 "ASCII" => 3,
327 "JSON_IETF" => 4,
328 _ => return Err(Status::unimplemented("unsupported gNMI encoding")),
329 })
330}
331pub fn encoding_name(value: i32) -> Result<&'static str, Status> {
332 match value {
333 0 => Ok("JSON"),
334 2 => Ok("PROTO"),
335 3 => Ok("ASCII"),
336 4 => Ok("JSON_IETF"),
337 _ => Err(Status::unimplemented("unsupported gNMI encoding")),
338 }
339}
340pub fn notification_encoding(notification: &pb::Notification, encoding: i32) -> Result<(), Status> {
341 encoding_name(encoding)?;
342 for update in ¬ification.update {
343 use pb::typed_value::Value as V;
344 let value = update
345 .val
346 .as_ref()
347 .and_then(|v| v.value.as_ref())
348 .ok_or_else(|| Status::invalid_argument("missing typed value"))?;
349 if !match encoding {
350 0 => matches!(value, V::JsonVal(_)),
351 4 => matches!(value, V::JsonIetfVal(_)),
352 3 => matches!(value, V::AsciiVal(_)),
353 2 => !matches!(
354 value,
355 V::JsonVal(_)
356 | V::JsonIetfVal(_)
357 | V::AsciiVal(_)
358 | V::BytesVal(_)
359 | V::AnyVal(_)
360 | V::ProtoBytes(_)
361 ),
362 _ => false,
363 } {
364 return Err(Status::invalid_argument(
365 "notification value does not match requested encoding",
366 ));
367 }
368 }
369 Ok(())
370}
371pub fn checked<T: prost::Message + codec::WireName>(v: T) -> Result<T, Status> {
372 if v.encoded_len() > codec::MAX_MESSAGE_BYTES {
373 return Err(Status::resource_exhausted("gNMI message exceeds 1 MiB"));
374 }
375 codec::validate_wire(T::NAME, &v.encode_to_vec())?;
376 Ok(v)
377}