Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions crates/hiroz-codegen/src/hashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ pub fn calculate_service_type_hash(
service_event_info_desc: &TypeDescription,
resolved_deps: &BTreeMap<String, TypeDescription>,
) -> Result<TypeHash> {
// ROS2 uses slash format, not :: format
// Detect if this is an action service (contains SendGoal/GetResult/CancelGoal)
let is_action = service_name.contains("SendGoal")
|| service_name.contains("GetResult")
|| service_name.contains("CancelGoal");
// Per-action synthetic services (Foo_SendGoal / Foo_GetResult) live under
// `{pkg}/action/…`. CancelGoal is the shared action_msgs *srv*
// (`action_msgs/srv/CancelGoal`) — do NOT treat it as /action/ or the RIHS
// hash diverges from rmw_zenoh and cancel queries never match the server.
let is_action = service_name.contains("SendGoal") || service_name.contains("GetResult");
let path = if is_action { "action" } else { "srv" };

// Action services use /action/ path, regular services use /srv/ path
Expand Down
54 changes: 44 additions & 10 deletions crates/hiroz-codegen/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,19 +734,31 @@ impl Resolver {
}],
};

// CancelGoal_Response: return_code (int8)
// CancelGoal_Response: return_code (int8) + goals_canceling (GoalInfo[])
let response_desc = TypeDescription {
type_name: "action_msgs/srv/CancelGoal_Response".to_string(),
fields: vec![FieldDescription {
name: "return_code".to_string(),
field_type: FieldTypeDescription {
type_id: 2, // int8
capacity: 0,
string_capacity: 0,
nested_type_name: String::new(),
fields: vec![
FieldDescription {
name: "return_code".to_string(),
field_type: FieldTypeDescription {
type_id: 2, // int8
capacity: 0,
string_capacity: 0,
nested_type_name: String::new(),
},
default_value: String::new(),
},
default_value: String::new(),
}],
FieldDescription {
name: "goals_canceling".to_string(),
field_type: FieldTypeDescription {
type_id: 145, // NESTED_TYPE_UNBOUNDED_SEQUENCE
capacity: 0,
string_capacity: 0,
nested_type_name: "action_msgs/msg/GoalInfo".to_string(),
},
default_value: String::new(),
},
],
};

// Get dependencies
Expand All @@ -762,6 +774,28 @@ impl Resolver {
deps.insert(time_desc.type_name.clone(), time_desc.clone());
}

// GoalInfo also nests UUID — required for a matching RIHS hash.
if let Some(uuid_desc) = self.type_descriptions.get("unique_identifier_msgs/UUID") {
deps.insert(uuid_desc.type_name.clone(), uuid_desc.clone());
} else {
// Fallback matching calculate_status_hash when UUID isn't resolved yet.
use crate::hashing::TypeId;
let uuid_desc = TypeDescription {
type_name: "unique_identifier_msgs/msg/UUID".to_string(),
fields: vec![FieldDescription {
name: "uuid".to_string(),
field_type: FieldTypeDescription {
type_id: TypeId::UINT8_ARRAY,
capacity: 16,
string_capacity: 0,
nested_type_name: String::new(),
},
default_value: String::new(),
}],
};
deps.insert(uuid_desc.type_name.clone(), uuid_desc);
}

// Get ServiceEventInfo
let service_event_info_desc = self
.type_descriptions
Expand Down
48 changes: 48 additions & 0 deletions crates/hiroz-codegen/tests/action_hash_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,51 @@ fn test_fibonacci_get_result_hash() {
"status hash mismatch"
);
}

#[test]
fn test_cancel_goal_hash_matches_ros() {
use hiroz_codegen::{
discovery::{discover_actions, discover_messages},
resolver::Resolver,
};

// Same package set as Fibonacci hash check; CancelGoal hash is shared
// across all actions (action_msgs/srv/CancelGoal).
let assets = assets_dir();
let packages = [
"builtin_interfaces",
"unique_identifier_msgs",
"action_msgs",
"service_msgs",
"action_tutorials_interfaces",
];
let mut all_messages = Vec::new();
for pkg in &packages {
let pkg_path = assets.join(pkg);
let msgs = discover_messages(&pkg_path, pkg).unwrap_or_default();
all_messages.extend(msgs);
}

let mut resolver = Resolver::new(false);
resolver
.resolve_messages(all_messages)
.expect("resolve messages");

let pkg_path = assets.join("action_tutorials_interfaces");
let actions =
discover_actions(&pkg_path, "action_tutorials_interfaces").expect("discover actions");
let resolved = resolver.resolve_actions(actions).expect("resolve actions");
let fib = resolved
.iter()
.find(|a| a.parsed.name == "Fibonacci")
.expect("Fibonacci action");

let hash = fib.cancel_goal_hash.to_rihs_string();
println!("cancel_goal_hash: {hash}");
// From /opt/ros/lyrical/share/action_msgs/srv/CancelGoal.json
assert_eq!(
hash,
"RIHS01_573d8b0a534451d7bc2ac8c5ffde8ac14b8593b7001175d0cd6516dcbeb8689a",
"CancelGoal hash must match ROS action_msgs/srv/CancelGoal.json"
);
}
10 changes: 8 additions & 2 deletions crates/hiroz/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ impl ZNode {
let get_result_client =
self.create_raw_service_client(&get_result_service, &get_result_type, result_hash)?;
let cancel_goal_client =
self.create_raw_service_client(&cancel_goal_service, &cancel_goal_type, "")?;
self.create_raw_service_client(&cancel_goal_service, &cancel_goal_type, CANCEL_GOAL_TYPE_HASH)?;

// Feedback subscriber (no-op callback for now; Go handles via polling or separate mechanism)
let feedback_sub =
Expand Down Expand Up @@ -904,7 +904,7 @@ impl ZNode {
let get_result_server =
self.create_raw_service_server(&get_result_service, &get_result_type, result_hash)?;
let cancel_goal_server =
self.create_raw_service_server(&cancel_goal_service, &cancel_goal_type, "")?;
self.create_raw_service_server(&cancel_goal_service, &cancel_goal_type, CANCEL_GOAL_TYPE_HASH)?;

let feedback_pub =
self.create_raw_publisher(&feedback_topic, &feedback_type_dds, feedback_hash)?;
Expand Down Expand Up @@ -1368,6 +1368,12 @@ impl ZNode {
}
}

/// ROS 2 Humble/Jazzy RIHS01 type hash for `action_msgs/srv/CancelGoal`.
/// Must match rmw_zenoh keyexpr hashing or cancel requests never reach the server.
#[cfg(feature = "ffi")]
const CANCEL_GOAL_TYPE_HASH: &str =
"RIHS01_573d8b0a534451d7bc2ac8c5ffde8ac14b8593b7001175d0cd6516dcbeb8689a";

/// Parse an action type string like `"example_interfaces/action/Fibonacci"` into
/// `(package, action_name)` — i.e., `("example_interfaces", "Fibonacci")`.
/// These are used to construct DDS-style type names for rmw_zenoh_cpp graph discovery.
Expand Down
Loading