1use super::{
3 proto::gnmi as pb,
4 value::{self, Notification, Path, Update},
5};
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use tonic::Status;
9pub fn check_model(action: &Value) -> Result<(), Status> {
10 fn walk(v: &Value, depth: usize, nodes: &mut usize, bytes: &mut usize) -> Result<(), Status> {
11 *nodes += 1;
12 *bytes += 64;
13 if depth > 32 || *nodes > 10_000 || *bytes > 1024 * 1024 {
14 return Err(Status::resource_exhausted(
15 "gNMI model depth/node/retained bound",
16 ));
17 }
18 match v {
19 Value::String(s) => {
20 *bytes += s.len();
21 if s.len() > 65536 || *bytes > 1024 * 1024 {
22 return Err(Status::resource_exhausted("gNMI model text/retained bound"));
23 }
24 }
25 Value::Array(v) => {
26 for value in v {
27 walk(value, depth + 1, nodes, bytes)?;
28 }
29 }
30 Value::Object(v) => {
31 for (key, value) in v {
32 if key.len() > 256 {
33 return Err(Status::resource_exhausted("gNMI model key bound"));
34 }
35 *bytes += key.len();
36 walk(value, depth + 1, nodes, bytes)?;
37 }
38 }
39 _ => {}
40 }
41 Ok(())
42 }
43 walk(action, 0, &mut 0, &mut 0)
44}
45#[derive(Clone, Debug, Serialize, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct Model {
48 pub name: String,
49 #[serde(default)]
50 pub organization: String,
51 #[serde(default)]
52 pub version: String,
53}
54pub fn models(values: Vec<pb::ModelData>) -> Result<Vec<Model>, Status> {
55 let models: Vec<_> = values
56 .into_iter()
57 .map(|v| Model {
58 name: v.name,
59 organization: v.organization,
60 version: v.version,
61 })
62 .collect();
63 model_proto(models.clone())?;
64 Ok(models)
65}
66pub fn model_proto(values: Vec<Model>) -> Result<Vec<pb::ModelData>, Status> {
67 if values.len() > 128 {
68 return Err(Status::resource_exhausted("model count exceeds 128"));
69 }
70 values
71 .into_iter()
72 .map(|v| {
73 if v.name.is_empty()
74 || [&v.name, &v.organization, &v.version]
75 .iter()
76 .any(|s| s.len() > 256 || s.contains('\0'))
77 {
78 return Err(Status::invalid_argument("invalid model name/text"));
79 }
80 Ok(pb::ModelData {
81 name: v.name,
82 organization: v.organization,
83 version: v.version,
84 })
85 })
86 .collect()
87}
88pub fn get_request(request: &pb::GetRequest) -> Result<Value, Status> {
89 value::encoding_name(request.encoding)?;
90 let kind = match request.r#type {
91 0 => "ALL",
92 1 => "CONFIG",
93 2 => "STATE",
94 3 => "OPERATIONAL",
95 _ => return Err(Status::invalid_argument("unknown Get data type")),
96 };
97 if request.path.len() > 128 || !request.extension.is_empty() {
98 return Err(Status::unimplemented(
99 "Get paths/extensions outside selected scope",
100 ));
101 }
102 Ok(
103 json!({"prefix":Path::from_proto(request.prefix.clone())?,"path":request.path.iter().cloned().map(|p|Path::from_proto(Some(p))).collect::<Result<Vec<_>,_>>()?,"data_type":kind,"encoding":value::encoding_name(request.encoding)?,"use_models":models(request.use_models.clone())?}),
104 )
105}
106pub fn set_request(request: &pb::SetRequest) -> Result<Value, Status> {
107 if !request.union_replace.is_empty() || !request.extension.is_empty() {
108 return Err(Status::unimplemented(
109 "union_replace and extensions are excluded",
110 ));
111 }
112 if request.delete.len() + request.replace.len() + request.update.len() > 256 {
113 return Err(Status::resource_exhausted("Set operations exceed 256"));
114 }
115 let convert = |values: &[pb::Update]| {
116 values
117 .iter()
118 .cloned()
119 .map(Update::from_proto)
120 .collect::<Result<Vec<_>, _>>()
121 };
122 Ok(
123 json!({"prefix":Path::from_proto(request.prefix.clone())?,"delete":request.delete.iter().cloned().map(|p|Path::from_proto(Some(p))).collect::<Result<Vec<_>,_>>()?,"replace":convert(&request.replace)?,"update":convert(&request.update)?}),
124 )
125}
126pub fn set_response(request: &pb::SetRequest, timestamp: &str) -> Result<pb::SetResponse, Status> {
127 set_request(request)?;
128 let mut results = Vec::new();
129 for (paths, operation) in [
130 (request.delete.clone(), 1),
131 (
132 request
133 .replace
134 .iter()
135 .map(|v| v.path.clone().unwrap_or_default())
136 .collect(),
137 2,
138 ),
139 (
140 request
141 .update
142 .iter()
143 .map(|v| v.path.clone().unwrap_or_default())
144 .collect(),
145 3,
146 ),
147 ] {
148 for path in paths {
149 results.push(pb::UpdateResult {
150 timestamp: 0,
151 path: Some(path),
152 message: None,
153 op: operation,
154 });
155 }
156 }
157 value::checked(pb::SetResponse {
158 prefix: request.prefix.clone(),
159 response: results,
160 message: None,
161 timestamp: timestamp.parse().map_err(|_| {
162 Status::invalid_argument("timestamp must be i64 nanosecond decimal string")
163 })?,
164 extension: vec![],
165 })
166}
167pub fn set_result(response: pb::SetResponse) -> Result<Value, Status> {
168 if response.message.is_some() || !response.extension.is_empty() {
169 return Err(Status::unimplemented(
170 "legacy errors/extensions are excluded",
171 ));
172 }
173 let result = response
174 .response
175 .into_iter()
176 .map(|r| {
177 if r.message.is_some() {
178 return Err(Status::unimplemented("legacy update errors are excluded"));
179 }
180 let operation = match r.op {
181 1 => "DELETE",
182 2 => "REPLACE",
183 3 => "UPDATE",
184 _ => {
185 return Err(Status::invalid_argument(
186 "unsupported Set acknowledgement operation",
187 ))
188 }
189 };
190 Ok(json!({"path":Path::from_proto(r.path)?,"operation":operation}))
191 })
192 .collect::<Result<Vec<_>, _>>()?;
193 Ok(
194 json!({"prefix":Path::from_proto(response.prefix)?,"timestamp":response.timestamp.to_string(),"response":result}),
195 )
196}
197pub fn check_set_ack(request: &pb::SetRequest, response: &pb::SetResponse) -> Result<(), Status> {
198 let expected = set_response(request, "0")?;
199 if Path::from_proto(response.prefix.clone())? != Path::from_proto(expected.prefix)?
200 || response.response.len() != expected.response.len()
201 {
202 return Err(Status::invalid_argument(
203 "Set response does not acknowledge the requested transaction",
204 ));
205 }
206 for (actual, expected) in response.response.iter().zip(expected.response) {
207 if actual.op != expected.op
208 || Path::from_proto(actual.path.clone())? != Path::from_proto(expected.path)?
209 {
210 return Err(Status::invalid_argument(
211 "Set response does not acknowledge the requested transaction",
212 ));
213 }
214 }
215 Ok(())
216}
217pub fn get_result(response: pb::GetResponse, encoding: i32) -> Result<Value, Status> {
218 if response.error.is_some() || !response.extension.is_empty() {
219 return Err(Status::unimplemented(
220 "legacy errors/extensions are excluded",
221 ));
222 }
223 for n in &response.notification {
224 value::notification_encoding(n, encoding)?;
225 }
226 Ok(
227 json!({"notification":response.notification.into_iter().map(Notification::from_proto).collect::<Result<Vec<_>,_>>()?}),
228 )
229}
230pub fn capability_result(response: pb::CapabilityResponse) -> Result<Value, Status> {
231 if !response.extension.is_empty() {
232 return Err(Status::unimplemented("capability extensions are excluded"));
233 }
234 if response.g_nmi_version.len() > 256 {
235 return Err(Status::resource_exhausted("gNMI version bound"));
236 }
237 let encodings = response
238 .supported_encodings
239 .into_iter()
240 .map(value::encoding_name)
241 .collect::<Result<Vec<_>, _>>()?;
242 Ok(
243 json!({"supported_models":models(response.supported_models)?,"supported_encodings":encodings,"gnmi_version":response.g_nmi_version}),
244 )
245}
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum Mode {
248 Once,
249 Poll,
250 Stream,
251}
252#[derive(Clone)]
253pub struct Subscription {
254 pub mode: Mode,
255 pub encoding: i32,
256 pub updates_only: bool,
257 pub event: Value,
258}
259pub fn subscription(list: &pb::SubscriptionList) -> Result<Subscription, Status> {
260 value::encoding_name(list.encoding)?;
261 if list.qos.as_ref().is_some_and(|q| q.marking != 0) || list.allow_aggregation {
264 return Err(Status::unimplemented(
265 "nonzero QoS and aggregation are excluded",
266 ));
267 }
268 if list.subscription.is_empty() || list.subscription.len() > 128 {
269 return Err(Status::invalid_argument(
270 "subscription requires 1..128 paths",
271 ));
272 }
273 let mode = match list.mode {
274 0 => Mode::Stream,
275 1 => Mode::Once,
276 2 => Mode::Poll,
277 _ => return Err(Status::invalid_argument("unknown subscription list mode")),
278 };
279 let paths=list.subscription.iter().map(|s|{
280 if !(0..=1).contains(&s.mode)||s.sample_interval!=0||s.suppress_redundant||s.heartbeat_interval!=0{return Err(Status::unimplemented("SAMPLE, suppression and heartbeat are excluded"));}
281 Ok(json!({"path":Path::from_proto(s.path.clone())?,"mode":if s.mode==0{"TARGET_DEFINED"}else{"ON_CHANGE"}}))
282 }).collect::<Result<Vec<_>,_>>()?;
283 let event = json!({"prefix":Path::from_proto(list.prefix.clone())?,"subscription":paths,"mode":match mode{Mode::Once=>"ONCE",Mode::Poll=>"POLL",Mode::Stream=>"STREAM"},"encoding":value::encoding_name(list.encoding)?,"updates_only":list.updates_only,"use_models":models(list.use_models.clone())?});
284 Ok(Subscription {
285 mode,
286 encoding: list.encoding,
287 updates_only: list.updates_only,
288 event,
289 })
290}
291pub enum Answer {
292 Capabilities(pb::CapabilityResponse),
293 Get(Vec<pb::Notification>),
294 Set(String),
295 Update(Vec<pb::Notification>),
296 Sync,
297 Wait(u64),
298 Finish,
299 Error(Status),
300}
301fn decode<T: serde::de::DeserializeOwned>(v: &Value) -> Result<T, Status> {
302 serde_json::from_value(v.clone())
303 .map_err(|_| Status::invalid_argument("invalid typed gNMI action"))
304}
305fn notifications(v: &Value) -> Result<Vec<pb::Notification>, Status> {
306 let values = v
307 .as_array()
308 .ok_or_else(|| Status::invalid_argument("notification must be an array"))?;
309 if values.len() > 128 {
310 return Err(Status::resource_exhausted("notification count exceeds 128"));
311 }
312 values
313 .iter()
314 .map(|v| decode::<Notification>(v)?.into_proto())
315 .collect()
316}
317pub fn answer(action: &Value) -> Result<Answer, Status> {
318 Ok(match action["type"].as_str().unwrap_or("") {
319 "gnmi_capabilities" => {
320 let model_values = action["supported_models"]
321 .as_array()
322 .ok_or_else(|| Status::invalid_argument("supported_models must be an array"))?;
323 if model_values.len() > 128 {
324 return Err(Status::resource_exhausted("model count exceeds 128"));
325 }
326 let model = model_proto(decode(&action["supported_models"])?)?;
327 let encodings = action["supported_encodings"]
328 .as_array()
329 .ok_or_else(|| Status::invalid_argument("supported_encodings must be an array"))?;
330 if encodings.is_empty() || encodings.len() > 4 {
331 return Err(Status::invalid_argument(
332 "advertise 1..4 supported encodings",
333 ));
334 }
335 let encodings = encodings
336 .iter()
337 .map(|v| value::encoding(v.as_str().unwrap_or("")))
338 .collect::<Result<Vec<_>, _>>()?;
339 let response = pb::CapabilityResponse {
340 supported_models: model,
341 supported_encodings: encodings,
342 g_nmi_version: "0.10.0".into(),
343 extension: vec![],
344 };
345 Answer::Capabilities(value::checked(response)?)
346 }
347 "gnmi_get_response" => {
348 let n = notifications(&action["notification"])?;
349 value::checked(pb::GetResponse {
350 notification: n.clone(),
351 error: None,
352 extension: vec![],
353 })?;
354 Answer::Get(n)
355 }
356 "gnmi_set_accepted" => {
357 let t = action["timestamp"]
358 .as_str()
359 .ok_or_else(|| Status::invalid_argument("timestamp required"))?;
360 t.parse::<i64>()
361 .map_err(|_| Status::invalid_argument("invalid timestamp"))?;
362 Answer::Set(t.into())
363 }
364 "gnmi_update" => {
365 let n = notifications(&action["notification"])?;
366 if n.len() > 16 {
367 return Err(Status::resource_exhausted(
368 "at most 16 queued notifications",
369 ));
370 }
371 let mut bytes = 0usize;
372 for v in &n {
373 let response = value::checked(pb::SubscribeResponse {
374 response: Some(pb::subscribe_response::Response::Update(v.clone())),
375 extension: vec![],
376 })?;
377 bytes += prost::Message::encoded_len(&response);
378 if bytes > super::codec::MAX_MESSAGE_BYTES {
379 return Err(Status::resource_exhausted(
380 "pending notifications exceed 1 MiB",
381 ));
382 }
383 }
384 Answer::Update(n)
385 }
386 "gnmi_sync" => Answer::Sync,
387 "gnmi_wait" => {
388 let milliseconds = action["milliseconds"]
389 .as_u64()
390 .filter(|v| (1..=1000).contains(v))
391 .ok_or_else(|| Status::invalid_argument("wait milliseconds must be 1..1000"))?;
392 Answer::Wait(milliseconds)
393 }
394 "gnmi_finish" => Answer::Finish,
395 "gnmi_error" => {
396 let code = action["code"]
397 .as_i64()
398 .filter(|v| (1..=16).contains(v))
399 .ok_or_else(|| Status::invalid_argument("error code must be 1..16"))?;
400 let message = action["message"]
401 .as_str()
402 .ok_or_else(|| Status::invalid_argument("error message required"))?;
403 if message.len() > 512 {
404 return Err(Status::resource_exhausted(
405 "error diagnostic exceeds 512 bytes",
406 ));
407 }
408 Answer::Error(Status::new(tonic::Code::from_i32(code as i32), message))
409 }
410 _ => return Err(Status::invalid_argument("unknown gNMI server action")),
411 })
412}