Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discoverabiltiy in kuksa.val.v1 API to enable e.g. Autocompletion #605

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
306dd27
Add discoverability feature to DataBroker
rafaeling Jul 4, 2023
67551e1
Regex for valid path request
rafaeling Jul 18, 2023
966ab61
Merge branch 'eclipse:master' into fb_add_discoverability_feature_to_…
rafaeling Jul 18, 2023
f346c78
Fix regex to accept correct use cases
rafaeling Jul 18, 2023
02082a7
Differentiate between leaf and branch according to VSS
rafaeling Jul 19, 2023
c85df9e
Merge branch 'eclipse:master' into fb_add_discoverability_feature_to_…
rafaeling Jul 20, 2023
c8a102d
Apply cargo fmt
rafaeling Jul 20, 2023
f786b8e
Fix unused import
rafaeling Jul 20, 2023
cc4bb94
Add Branch type to EntryType
rafaeling Jul 26, 2023
4d2c731
Error deleting spaces
rafaeling Jul 26, 2023
173c6b3
Error proto style
rafaeling Jul 26, 2023
9b7be38
Fix missing branch field
rafaeling Aug 1, 2023
8608557
Merge branch 'eclipse:master' into fb_add_discoverability_feature_to_…
rafaeling Aug 8, 2023
713d242
cargo format
rafaeling Aug 8, 2023
df7435e
clippy errors
rafaeling Aug 8, 2023
40e4c1b
No newline at end of file
rafaeling Aug 8, 2023
b8c7417
Pre-commit errors
rafaeling Aug 8, 2023
5d40304
Pre-commit errors 2
rafaeling Aug 8, 2023
3db4b7e
Merge branch 'eclipse:master' into fb_add_discoverability_feature_to_…
rafaeling Aug 11, 2023
27870f9
New autocompletion impl after desing discussions
rafaeling Aug 11, 2023
f124d51
Get rid of branches on broker
rafaeling Aug 15, 2023
3bbd8c9
Move regex path matching to glob
rafaeling Aug 17, 2023
873d413
Remove branch implementation
rafaeling Aug 17, 2023
79a41ee
Clean up branch implementation
rafaeling Aug 17, 2023
9d9cb65
Clean up branch proto
rafaeling Aug 17, 2023
aa56d51
Use glob regexes
rafaeling Aug 18, 2023
913a3fc
Reduce codelines
rafaeling Aug 18, 2023
effe593
Generalize regex and fix unit tests
rafaeling Aug 18, 2023
28c7ae3
Make regex more robust
rafaeling Aug 21, 2023
80aaf5a
Fix code
rafaeling Aug 21, 2023
808bd9b
fmt format
rafaeling Aug 21, 2023
aa3eace
fix regex
rafaeling Aug 21, 2023
38e7683
fix jobs
rafaeling Aug 21, 2023
e4d260f
Use generic regex and improve implementation
rafaeling Aug 22, 2023
518c347
Remove unused code
rafaeling Aug 22, 2023
8971f7a
Adapt regex and unit tests
rafaeling Aug 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 22 additions & 26 deletions kuksa-client/kuksa_client/grpc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ def from_message(cls, message: types_pb2.Datapoint):
) if message.HasField('timestamp') else None,
)


def cast_array_values(cast, array):
"""
Parses array input and cast individual values to wanted type.
Expand All @@ -339,7 +338,7 @@ def cast_array_values(cast, array):
# My Way
# ... without quotes
if item.strip() == '':
#skip
# skip
pass
else:
yield cast(item)
Expand All @@ -365,7 +364,7 @@ def cast_str(value) -> str:
new_val = new_val.replace('\\\"', '\"')
new_val = new_val.replace("\\\'", "\'")
return new_val

def to_message(self, value_type: DataType) -> types_pb2.Datapoint:
message = types_pb2.Datapoint()

Expand All @@ -374,7 +373,6 @@ def set_array_attr(obj, attr, values):
array.Clear()
array.values.extend(values)


field, set_field, cast_field = {
DataType.INT8: ('int32', setattr, int),
DataType.INT16: ('int32', setattr, int),
Expand All @@ -388,29 +386,29 @@ def set_array_attr(obj, attr, values):
DataType.DOUBLE: ('double', setattr, float),
DataType.BOOLEAN: ('bool', setattr, Datapoint.cast_bool),
DataType.STRING: ('string', setattr, Datapoint.cast_str),
DataType.INT8_ARRAY: ('int32_array', set_array_attr,
DataType.INT8_ARRAY: ('int32_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.INT16_ARRAY: ('int32_array', set_array_attr,
DataType.INT16_ARRAY: ('int32_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.INT32_ARRAY: ('int32_array', set_array_attr,
DataType.INT32_ARRAY: ('int32_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.UINT8_ARRAY: ('uint32_array', set_array_attr,
DataType.UINT8_ARRAY: ('uint32_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.UINT16_ARRAY: ('uint32_array', set_array_attr,
DataType.UINT16_ARRAY: ('uint32_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.UINT32_ARRAY: ('uint32_array', set_array_attr,
DataType.UINT32_ARRAY: ('uint32_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.UINT64_ARRAY: ('uint64_array', set_array_attr,
DataType.UINT64_ARRAY: ('uint64_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.INT64_ARRAY: ('int64_array', set_array_attr,
DataType.INT64_ARRAY: ('int64_array', set_array_attr,
lambda array: Datapoint.cast_array_values(int, array)),
DataType.FLOAT_ARRAY: ('float_array', set_array_attr,
DataType.FLOAT_ARRAY: ('float_array', set_array_attr,
lambda array: Datapoint.cast_array_values(float, array)),
DataType.DOUBLE_ARRAY: ('double_array', set_array_attr,
DataType.DOUBLE_ARRAY: ('double_array', set_array_attr,
lambda array: Datapoint.cast_array_values(float, array)),
DataType.BOOLEAN_ARRAY: ('bool_array', set_array_attr,
DataType.BOOLEAN_ARRAY: ('bool_array', set_array_attr,
lambda array: Datapoint.cast_array_values(Datapoint.cast_bool, array)),
DataType.STRING_ARRAY: ('string_array', set_array_attr,
DataType.STRING_ARRAY: ('string_array', set_array_attr,
lambda array: Datapoint.cast_array_values(Datapoint.cast_str, array)),
}.get(value_type, (None, None, None))
if self.value is not None:
Expand Down Expand Up @@ -523,6 +521,7 @@ class ServerInfo:
def from_message(cls, message: val_pb2.GetServerInfoResponse):
return cls(name=message.name, version=message.version)


class BaseVSSClient:
def __init__(
self,
Expand All @@ -536,7 +535,6 @@ def __init__(
connected: bool = False,
tls_server_name: Optional[str] = None
):


self.authorization_header = self.get_authorization_header(token)
self.target_host = f'{host}:{port}'
Expand All @@ -559,11 +557,10 @@ def _load_creds(self) -> Optional[grpc.ChannelCredentials]:
logger.info("Using client private key and certificates, mutual TLS supported if supported by server")
return grpc.ssl_channel_credentials(root_certificates, private_key, certificate_chain)
else:
logger.info(f"No client certificates provided, mutual TLS not supported!")
logger.info("No client certificates provided, mutual TLS not supported!")
return grpc.ssl_channel_credentials(root_certificates)
logger.info(f"No Root CA present, it will not be posible to use a secure connection!")
logger.info("No Root CA present, it will not be posible to use a secure connection!")
return None


def _prepare_get_request(self, entries: Iterable[EntryRequest]) -> val_pb2.GetRequest:
req = val_pb2.GetRequest(entries=[])
Expand Down Expand Up @@ -649,7 +646,7 @@ def get_authorization_header(self, token: str):
return "Bearer " + token

def generate_metadata_header(self, metadata: list, header=None) -> list:
if header == None:
if header is None:
header = self.authorization_header
if metadata:
metadata = dict(metadata)
Expand Down Expand Up @@ -686,20 +683,20 @@ def connect(self, target_host=None):
creds = self._load_creds()
if target_host is None:
target_host = self.target_host

if creds is not None:
logger.info("Establishing secure channel")
if self.tls_server_name:
logger.info(f"Using TLS server name {self.tls_server_name}")
logger.info("Using TLS server name {self.tls_server_name}")
options = [('grpc.ssl_target_name_override', self.tls_server_name)]
channel = grpc.secure_channel(target_host, creds, options)
else:
logger.debug(f"Not providing explicit TLS server name")
logger.debug("Not providing explicit TLS server name")
channel = grpc.secure_channel(target_host, creds)
else:
logger.info("Establishing insecure channel")
channel = grpc.insecure_channel(target_host)

self.channel = self.exit_stack.enter_context(channel)
self.client_stub = val_pb2_grpc.VALStub(self.channel)
self.connected = True
Expand Down Expand Up @@ -973,7 +970,6 @@ def get_server_info(self, **rpc_kwargs) -> Optional[ServerInfo]:
else:
raise VSSClientError.from_grpc_error(exc) from exc
return None


def get_value_types(self, paths: Collection[str], **rpc_kwargs) -> Dict[str, DataType]:
"""
Expand Down
27 changes: 27 additions & 0 deletions kuksa_databroker/databroker/src/broker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,24 @@ impl<'a, 'b> DatabaseReadAccess<'a, 'b> {
}
}

pub fn get_entries_by_regex(&self, regex: regex::Regex) -> Result<Vec<Entry>, ReadError> {
let mut entries: Vec<Entry> = Vec::new();
for key in self.db.path_to_id.keys() {
if regex.is_match(key) {
entries.push(
self.get_entry_by_id(self.db.path_to_id.get(key).unwrap().to_owned())
.unwrap()
.to_owned(),
);
}
}
if entries.is_empty() {
return Err(ReadError::NotFound);
}

Ok(entries)
}

pub fn get_metadata_by_id(&self, id: i32) -> Option<&Metadata> {
self.db.entries.get(&id).map(|entry| &entry.metadata)
}
Expand Down Expand Up @@ -1159,6 +1177,15 @@ impl<'a, 'b> AuthorizedAccess<'a, 'b> {
.cloned()
}

pub async fn get_entries_by_regex(&self, regex: regex::Regex) -> Result<Vec<Entry>, ReadError> {
self.broker
.database
.read()
.await
.authorized_read_access(self.permissions)
.get_entries_by_regex(regex)
}

pub async fn get_entry_by_id(&self, id: i32) -> Result<Entry, ReadError> {
self.broker
.database
Expand Down
165 changes: 153 additions & 12 deletions kuksa_databroker/databroker/src/glob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use regex::Regex;

#[derive(Debug)]
pub enum Error {
RegexError,
}
Expand All @@ -24,20 +25,30 @@ pub fn to_regex_string(glob: &str) -> String {
// Start from the beginning
let mut re = String::from("^");

// Replace all "standalone" wildcards with ".*"
re.push_str(
&glob
.split('.')
.map(|part| match part {
"*" => ".*",
other => other,
})
.collect::<Vec<&str>>()
.join(r"\."),
);
if glob.ends_with(".*") {
re.push_str(glob.replace(".*", ".[^.]+").as_str());
} else if glob.ends_with(".**") {
re.push_str(glob.replace(".**", ".*").as_str());
} else {
// Replace all "standalone" wildcards with ".*"
re.push_str(
&glob
.split('.')
.map(|part| match part {
"**" => ".*",
other => other,
})
.collect::<Vec<&str>>()
.join(r"\."),
);
}

// If it doesn't already end with a wildcard, add it
if !re.ends_with(".*") {
if !re.starts_with("^*")
&& !re.starts_with("^.*")
&& !re.ends_with(".*")
&& !re.ends_with("[^.]+")
{
re.push_str(".*");
}

Expand All @@ -51,3 +62,133 @@ pub fn to_regex(glob: &str) -> Result<Regex, Error> {
let re = to_regex_string(glob);
Regex::new(&re).map_err(|_err| Error::RegexError)
}

pub fn is_valid_pattern(input: &str) -> bool {
let re = regex::Regex::new(
r"(?x)
^ # anchor at start (only match full paths)

# At least one subpath (consisting of either of three):
(?:
[A-Z][a-zA-Z0-9]* # An alphanumeric name (first letter capitalized)
|
\* # An asterisk
|
\*\* # A double asterisk
)

# which can be followed by ( separator + another subpath )
# repeated any number of times
(?:
\. # Separator, literal dot
(?:
[A-Z][a-zA-Z0-9]* # Alphanumeric name (first letter capitalized)
|
\* # An asterisk
|
\*\* # A double asterisk
)
)*
$ # anchor at end (to match only full paths)
",
)
.unwrap();

re.is_match(input)
}

#[tokio::test]
async fn test_valid_request_path() {
assert!(is_valid_pattern("String.*"));
assert!(is_valid_pattern("String.**"));
assert!(is_valid_pattern("Vehicle.Chassis.Axle.Row2.Wheel.*"));
assert!(is_valid_pattern("String.String.String.String.*"));
assert!(is_valid_pattern("String.String.String.String.**"));
assert!(is_valid_pattern("String.String.String.String"));
assert!(is_valid_pattern("String.String.String.String.String.**"));
assert!(is_valid_pattern("String.String.String.*.String"));
assert!(is_valid_pattern("String.String.String.**.String"));
assert!(is_valid_pattern(
"String.String.String.String.String.**.String"
));
assert!(is_valid_pattern(
"String.String.String.String.*.String.String"
));

assert!(is_valid_pattern("String.*.String.String"));
assert!(is_valid_pattern("String.String.**.String.String"));
assert!(is_valid_pattern("String.**.String.String"));
assert!(is_valid_pattern("**.String.String.String.**"));
assert!(is_valid_pattern("**.String.String.String.*"));
assert!(is_valid_pattern("**.String"));
assert!(is_valid_pattern("*.String.String.String"));
assert!(is_valid_pattern("*.String"));
assert!(!is_valid_pattern("String.String.String."));
assert!(!is_valid_pattern("String.String.String.String.."));
assert!(!is_valid_pattern("String.*.String.String.."));
assert!(!is_valid_pattern("*.String.String.String.."));
}

#[tokio::test]
async fn test_valid_regex_path() {
assert_eq!(to_regex_string("String.*"), "^String.[^.]+$");
assert_eq!(to_regex_string("String.**"), "^String.*$");
assert_eq!(
to_regex_string("String.String.String.String.*"),
"^String.String.String.String.[^.]+$"
);
assert_eq!(
to_regex_string("String.String.String.String.**"),
"^String.String.String.String.*$"
);
assert_eq!(
to_regex_string("String.String.String.String"),
"^String\\.String\\.String\\.String.*$"
);
assert_eq!(
to_regex_string("String.String.String.String.String.**"),
"^String.String.String.String.String.*$"
);
assert_eq!(
to_regex_string("String.String.String.*.String"),
"^String\\.String\\.String\\.*\\.String.*$"
);
assert_eq!(
to_regex_string("String.String.String.**.String"),
"^String\\.String\\.String\\..*\\.String.*$"
);
assert_eq!(
to_regex_string("String.String.String.String.String.**.String"),
"^String\\.String\\.String\\.String\\.String\\..*\\.String.*$"
);
assert_eq!(
to_regex_string("String.String.String.String.*.String.String"),
"^String\\.String\\.String\\.String\\.*\\.String\\.String.*$"
);
assert_eq!(
to_regex_string("String.*.String.String"),
"^String\\.*\\.String\\.String.*$"
);
assert_eq!(
to_regex_string("String.String.**.String.String"),
"^String\\.String\\..*\\.String\\.String.*$"
);
assert_eq!(
to_regex_string("String.**.String.String"),
"^String\\..*\\.String\\.String.*$"
);
assert_eq!(
to_regex_string("**.String.String.String.**"),
"^**.String.String.String.*$"
);
assert_eq!(
to_regex_string("**.String.String.String.*"),
"^**.String.String.String.[^.]+$"
);
assert_eq!(to_regex_string("**.String"), "^.*\\.String$");
assert_eq!(to_regex_string("*.String"), "^*\\.String$");
assert_eq!(
to_regex_string("*.String.String.String"),
"^*\\.String\\.String\\.String$"
);
}
Loading
Loading