Skip to content

Commit 585f944

Browse files
committed
VISS reading branches
1 parent 3be386a commit 585f944

File tree

5 files changed

+151
-37
lines changed

5 files changed

+151
-37
lines changed

databroker/src/viss/v2/server.rs

+132-26
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use tracing::warn;
2929
use crate::{
3030
authorization::Authorization,
3131
broker::{self, AuthorizedAccess, UpdateError},
32+
glob::Matcher,
3233
permissions::{self, Permissions},
3334
};
3435

@@ -100,36 +101,141 @@ impl Viss for Server {
100101
request_id,
101102
metadata,
102103
}));
103-
}
104-
let permissions = resolve_permissions(&self.authorization, &request.authorization)
105-
.map_err(|error| GetErrorResponse {
106-
request_id: request_id.clone(),
107-
error,
108-
ts: SystemTime::now().into(),
109-
})?;
110-
let broker = self.broker.authorized_access(&permissions);
104+
} else if let Some(Filter::Paths(paths_filter)) = &request.filter {
105+
let request_path = request.path.as_ref();
106+
if request_path.contains('*') {
107+
return Err(GetErrorResponse {
108+
request_id,
109+
ts: SystemTime::now().into(),
110+
error: Error::NotFoundInvalidPath,
111+
});
112+
}
113+
114+
let permissions = resolve_permissions(&self.authorization, &request.authorization)
115+
.map_err(|error| GetErrorResponse {
116+
request_id: request_id.clone(),
117+
error,
118+
ts: SystemTime::now().into(),
119+
})?;
120+
let broker = self.broker.authorized_access(&permissions);
121+
122+
let mut request_matcher: Vec<(Matcher, bool)> = Vec::new();
123+
let mut entries_data = Vec::new();
124+
let mut signal_errors = Vec::new();
125+
126+
for path in &paths_filter.parameter {
127+
let new_path = format!("{}.{}", request_path, path);
128+
if let Ok(matcher) = Matcher::new(&new_path) {
129+
request_matcher.push((matcher, false));
130+
}
131+
}
132+
133+
if !request_matcher.is_empty() {
134+
for (matcher, is_match) in &mut request_matcher {
135+
broker
136+
.for_each_entry(|entry| {
137+
let glob_path = &entry.metadata().glob_path;
138+
let path = entry.metadata().path.clone();
139+
if matcher.is_match(glob_path) {
140+
match entry.datapoint() {
141+
Ok(datapoint) => {
142+
let dp = DataPoint::from(datapoint.clone());
143+
*is_match = true;
144+
entries_data.push(DataObject {
145+
path: Path::from(path),
146+
dp,
147+
});
148+
}
149+
Err(_) => {
150+
signal_errors.push(path);
151+
}
152+
}
153+
}
154+
})
155+
.await;
156+
157+
// Not found any matches meaning it could be a branch path request
158+
// Only support branches like Vehicle.Cabin.Sunroof but not like **.Sunroof
159+
if !matcher.as_string().starts_with("**")
160+
&& !matcher.as_string().ends_with("/**")
161+
&& !(*is_match)
162+
{
163+
if let Ok(branch_matcher) = Matcher::new(&(matcher.as_string() + "/**")) {
164+
broker
165+
.for_each_entry(|entry| {
166+
let glob_path = &entry.metadata().glob_path;
167+
let path = entry.metadata().path.clone();
168+
if branch_matcher.is_match(glob_path) {
169+
match entry.datapoint() {
170+
Ok(datapoint) => {
171+
let dp = DataPoint::from(datapoint.clone());
172+
*is_match = true;
173+
entries_data.push(DataObject {
174+
path: Path::from(path),
175+
dp,
176+
});
177+
}
178+
Err(_) => {
179+
signal_errors.push(path);
180+
}
181+
}
182+
}
183+
})
184+
.await;
185+
}
186+
}
187+
}
188+
}
111189

112-
// Get datapoints
113-
match broker.get_datapoint_by_path(request.path.as_ref()).await {
114-
Ok(datapoint) => {
115-
let dp = DataPoint::from(datapoint);
190+
// https://w3c.github.io/automotive/spec/VISSv2_Core.html#error-handling
191+
if signal_errors.is_empty() {
116192
Ok(GetSuccessResponse::Data(DataResponse {
117193
request_id,
118-
data: Data::Object(DataObject {
119-
path: request.path,
120-
dp,
121-
}),
194+
data: Data::Array(entries_data),
122195
}))
196+
} else {
197+
Err(GetErrorResponse {
198+
request_id,
199+
ts: SystemTime::now().into(),
200+
error: Error::Forbidden {
201+
msg: Some(format!(
202+
"Permission denied for some signal: {}",
203+
signal_errors.join(", ")
204+
)),
205+
},
206+
})
207+
}
208+
} else {
209+
let permissions = resolve_permissions(&self.authorization, &request.authorization)
210+
.map_err(|error| GetErrorResponse {
211+
request_id: request_id.clone(),
212+
error,
213+
ts: SystemTime::now().into(),
214+
})?;
215+
let broker = self.broker.authorized_access(&permissions);
216+
217+
// Get datapoints
218+
match broker.get_datapoint_by_path(request.path.as_ref()).await {
219+
Ok(datapoint) => {
220+
let dp = DataPoint::from(datapoint);
221+
Ok(GetSuccessResponse::Data(DataResponse {
222+
request_id,
223+
data: Data::Object(DataObject {
224+
path: request.path,
225+
dp,
226+
}),
227+
}))
228+
}
229+
Err(err) => Err(GetErrorResponse {
230+
request_id,
231+
ts: SystemTime::now().into(),
232+
error: match err {
233+
broker::ReadError::NotFound => Error::NotFoundInvalidPath,
234+
broker::ReadError::PermissionDenied => Error::Forbidden { msg: None },
235+
broker::ReadError::PermissionExpired => Error::UnauthorizedTokenExpired,
236+
},
237+
}),
123238
}
124-
Err(err) => Err(GetErrorResponse {
125-
request_id,
126-
ts: SystemTime::now().into(),
127-
error: match err {
128-
broker::ReadError::NotFound => Error::NotFoundInvalidPath,
129-
broker::ReadError::PermissionDenied => Error::Forbidden,
130-
broker::ReadError::PermissionExpired => Error::UnauthorizedTokenExpired,
131-
},
132-
}),
133239
}
134240
}
135241

@@ -211,7 +317,7 @@ impl Viss for Server {
211317
UpdateError::UnsupportedType => Error::BadRequest {
212318
msg: Some("Unsupported data type.".into()),
213319
},
214-
UpdateError::PermissionDenied => Error::Forbidden,
320+
UpdateError::PermissionDenied => Error::Forbidden { msg: None },
215321
UpdateError::PermissionExpired => Error::UnauthorizedTokenExpired,
216322
}
217323
} else {

databroker/src/viss/v2/types.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ pub struct GenericErrorResponse {
196196
pub enum Filter {
197197
#[serde(rename = "static-metadata")]
198198
StaticMetadata(StaticMetadataFilter),
199+
#[serde(rename = "paths")]
200+
Paths(PathsFilter),
201+
}
202+
203+
#[derive(Deserialize)]
204+
#[serde(rename_all = "camelCase")]
205+
pub struct PathsFilter {
206+
pub parameter: Vec<String>,
199207
}
200208

201209
#[derive(Deserialize)]
@@ -389,7 +397,7 @@ pub enum Error {
389397
UnauthorizedTokenInvalid,
390398
UnauthorizedTokenMissing,
391399
UnauthorizedReadOnly,
392-
Forbidden,
400+
Forbidden { msg: Option<String> },
393401
NotFoundInvalidPath,
394402
NotFoundUnavailableData,
395403
NotFoundInvalidSubscriptionId,
@@ -437,10 +445,10 @@ impl From<Error> for ErrorSpec {
437445
message: "The desired signal cannot be set since it is a read only signal.".into(),
438446
},
439447
// Forbidden 403 user_forbidden The user is not permitted to access the requested resource. Retrying does not help.
440-
Error::Forbidden => ErrorSpec {
448+
Error::Forbidden{ msg: custom_msg } => ErrorSpec {
441449
number: 403,
442450
reason: "user_forbidden".into(),
443-
message: "The user is not permitted to access the requested resource. Retrying does not help.".into(),
451+
message: custom_msg.unwrap_or("The user is not permitted to access the requested resource. Retrying does not help.".into()),
444452
},
445453
// Forbidden 403 user_unknown The user is unknown. Retrying does not help.
446454
// Forbidden 403 device_forbidden The device is not permitted to access the requested resource. Retrying does not help.

integration_test/viss/features/viss_v2_multiple_paths.feature

+6-6
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ Feature: VISS v2 Compliance Testing - Multiple Paths
2020
@ShouldHave
2121
Scenario: Request for multiple values from a single node
2222
# This scenario can be expanded based on specific use cases.
23-
When I send a read request with path "Vehicle.Cabin.TemperatureSetpoint"
24-
Then I should receive multiple values from a single node
23+
When I search "Vehicle.Cabin" using a path filter "Door.*.*.IsOpen"
24+
Then I should receive multiple data points
2525

2626
# 5.1.2 Read request - Single value request from multiple nodes
2727
# The VISS server must support reading values from multiple nodes using path filters.
2828
@MustHave
2929
Scenario: Request for a single value from multiple nodes
3030
When I search "Vehicle" using a path filter "*"
31-
Then I should receive a single value from multiple nodes
31+
Then I should receive multiple data points
3232

3333
# 5.1.2 Read request - Multiple values from multiple nodes
3434
# The VISS server should support reading multiple values from multiple nodes.
3535
@ShouldHave
36-
Scenario: Request for multiple values from multiple nodes
37-
When I send a read request with path "Vehicle.*"
38-
Then I should receive multiple values from multiple nodes
36+
Scenario: Path request for multiple values must not contain any wildcards
37+
When I search "Vehicle.*" using a path filter "*"
38+
Then I should receive an error response

integration_test/viss/features/viss_v2_transport_wss_filter.feature

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Feature: VISS v2 Compliance Testing - Filter
3333
# The VISS server must support searching for multiple signals using a path filter (e.g., "*.IsOpen").
3434
@MustHave
3535
Scenario: Search for multiple signals using path filter
36-
When I search "Vehicle" using a path filter "*.IsOpen"
36+
When I search "Vehicle" using a path filter "*.*.*.IsOpen"
3737
Then I should receive multiple data points
3838

3939
# 5.5.1 Subscribe with change filter

integration_test/viss/tests/test_steps/viss_v2_steps.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ def receive_ws_read_multiple_datapoints(connected_clients,request_id):
377377
responses = connected_clients.find_messages(request_id=request_id)
378378
# TODO: Unpack envelope
379379
# TODO: Count number of valid "dp" items
380-
actual_count = len(responses)
380+
actual_count = len(responses[0]["body"]["data"])
381381
assert actual_count > 1, f"Expected multiple messages but only got {actual_count}"
382382

383383
@then("I should receive a single value from multiple nodes")

0 commit comments

Comments
 (0)