///|
fn nibble_value(char : Char) -> Int raise {
if char >= '0' && char <= '9' {
char.to_int() - '0'.to_int()
} else if char >= 'a' && char <= 'f' {
10 + char.to_int() - 'a'.to_int()
} else if char >= 'A' && char <= 'F' {
10 + char.to_int() - 'A'.to_int()
} else {
raise @frame.FrameError::InvalidHexText(char.to_string())
}
}
///|
fn parse_hex_identifier(text : StringView) -> Int raise {
let trimmed = text.trim()
let body = if trimmed.has_prefix("0x") || trimmed.has_prefix("0X") {
trimmed[2:]
} else {
trimmed
}
guard body.length() > 0 else {
raise @frame.FrameError::InvalidHexText(text.to_owned())
}
let mut value = 0
for char in body {
value = (value << 4) + nibble_value(char)
}
value
}
///|
fn nibble_char(value : Int) -> Char {
if value < 10 {
('0'.to_int() + value).unsafe_to_char()
} else {
('A'.to_int() + value - 10).unsafe_to_char()
}
}
///|
fn to_hex_identifier(value : Int?) -> String {
match value {
Some(int_value) => {
let masked = int_value.land(0xFFFF)
String::from_array([
'0',
'x',
nibble_char((masked >> 12).land(0xF)),
nibble_char((masked >> 8).land(0xF)),
nibble_char((masked >> 4).land(0xF)),
nibble_char(masked.land(0xF)),
])
}
None => ""
}
}
///|
fn to_optional_int_text(value : Int?) -> String {
match value {
Some(int_value) => int_value.to_string()
None => ""
}
}
///|
fn assess_observed_timing(
observed_ms : Int?,
mandatory_ms : Int,
recommended_ms? : Int? = None,
) -> String {
match observed_ms {
None => "not_measured"
Some(value) =>
match recommended_ms {
Some(recommended) =>
if value <= recommended {
"within_recommended"
} else if value <= mandatory_ms {
"within_mandatory"
} else {
"out_of_spec"
}
None =>
if value <= mandatory_ms {
"within_mandatory"
} else {
"out_of_spec"
}
}
}
}
///|
fn validate_device_summary_against_gsd(
summary : @dcp.DeviceSummary,
gsd_summary : @gsd.GsdSummary,
observed_set_usage_ms : Int?,
observed_ip_startup_ms : Int?,
observed_reset_to_factory_ms : Int?,
observed_ar_resource_release_ms : Int?,
observed_autonegotiation_ms : Int?,
) -> String raise {
let response_vendor_id = to_hex_identifier(summary.vendor_id)
let response_device_id = to_hex_identifier(summary.device_id)
let response_station_name = summary.station_name.unwrap_or("")
let response_vendor_name = summary.vendor_name.unwrap_or("")
let vendor_id_match = match summary.vendor_id {
Some(value) => value == parse_hex_identifier(gsd_summary.vendor_id[:])
None => false
}
let device_id_match = match summary.device_id {
Some(value) => value == parse_hex_identifier(gsd_summary.device_id[:])
None => false
}
let vendor_name_match = match summary.vendor_name {
Some(value) => value == gsd_summary.vendor_name
None => false
}
let station_name_match = if gsd_summary.dns_compatible_name == "" {
true
} else {
match summary.station_name {
Some(value) => value == gsd_summary.dns_compatible_name
None => false
}
}
let vendor_name_is_advisory_mismatch = summary.vendor_name is Some(_) &&
!vendor_name_match &&
vendor_id_match &&
device_id_match &&
station_name_match
let set_usage_time_assessment = assess_observed_timing(
observed_set_usage_ms,
mandatory_set_usage_time_ms(),
)
let ip_startup_time_assessment = assess_observed_timing(
observed_ip_startup_ms,
mandatory_ip_startup_time_ms(),
recommended_ms=Some(recommended_ip_startup_time_ms()),
)
let reset_to_factory_time_assessment = assess_observed_timing(
observed_reset_to_factory_ms,
mandatory_reset_to_factory_time_ms(),
recommended_ms=Some(recommended_reset_to_factory_time_ms()),
)
let ar_resource_release_time_assessment = assess_observed_timing(
observed_ar_resource_release_ms,
mandatory_ar_resource_release_timeout_ms(),
recommended_ms=Some(recommended_ar_resource_release_timeout_ms()),
)
let autonegotiation_time_assessment = assess_observed_timing(
observed_autonegotiation_ms,
mandatory_autonegotiation_timeout_ms(),
recommended_ms=Some(recommended_autonegotiation_timeout_ms()),
)
let mismatch_fields : Array[String] = []
if !vendor_id_match {
mismatch_fields.push("vendor_id")
}
if !device_id_match {
mismatch_fields.push("device_id")
}
if summary.vendor_name is Some(_) &&
!vendor_name_match &&
!vendor_name_is_advisory_mismatch {
mismatch_fields.push("vendor_name")
}
if !station_name_match {
mismatch_fields.push("station_name")
}
if set_usage_time_assessment == "out_of_spec" {
mismatch_fields.push("set_usage_time")
}
if ip_startup_time_assessment == "out_of_spec" {
mismatch_fields.push("ip_startup_time")
}
if reset_to_factory_time_assessment == "out_of_spec" {
mismatch_fields.push("reset_to_factory_time")
}
if ar_resource_release_time_assessment == "out_of_spec" {
mismatch_fields.push("ar_resource_release_timeout")
}
if autonegotiation_time_assessment == "out_of_spec" {
mismatch_fields.push("autonegotiation_timeout")
}
let overall_match = mismatch_fields.is_empty()
[
"source=" + summary.source.to_hex(),
"station_name=" + response_station_name,
"response_vendor_id=" + response_vendor_id,
"response_device_id=" + response_device_id,
"response_vendor_name=" + response_vendor_name,
"gsd_vendor_id=" + gsd_summary.vendor_id,
"gsd_device_id=" + gsd_summary.device_id,
"gsd_vendor_name=" + gsd_summary.vendor_name,
"gsd_main_family=" + gsd_summary.main_family,
"gsd_product_family=" + gsd_summary.product_family,
"gsd_order_number=" + gsd_summary.order_number,
"gsd_device_access_point_id=" + gsd_summary.device_access_point_id,
"gsd_dns_compatible_name=" + gsd_summary.dns_compatible_name,
"vendor_id_match=" + vendor_id_match.to_string(),
"device_id_match=" + device_id_match.to_string(),
"vendor_name_match=" + vendor_name_match.to_string(),
"vendor_name_advisory_mismatch=" +
vendor_name_is_advisory_mismatch.to_string(),
"station_name_match=" + station_name_match.to_string(),
"device_identity_match=" + (vendor_id_match && device_id_match).to_string(),
"ip_address_change_bumpless=" + ip_address_change_bumpless().to_string(),
"station_name_change_bumpless=" + station_name_change_bumpless().to_string(),
"set_usage_time_mandatory_ms=" + mandatory_set_usage_time_ms().to_string(),
"observed_set_usage_time_ms=" + to_optional_int_text(observed_set_usage_ms),
"set_usage_time_assessment=" + set_usage_time_assessment,
"set_usage_time_probeability=" + set_usage_time_probeability(),
"set_usage_time_probeability_class=" + set_usage_time_probeability_class(),
"ip_startup_time_recommended_ms=" +
recommended_ip_startup_time_ms().to_string(),
"ip_startup_time_mandatory_ms=" + mandatory_ip_startup_time_ms().to_string(),
"observed_ip_startup_time_ms=" +
to_optional_int_text(observed_ip_startup_ms),
"ip_startup_time_assessment=" + ip_startup_time_assessment,
"ip_startup_time_probeability=" + ip_startup_time_probeability(),
"ip_startup_time_probeability_class=" + ip_startup_time_probeability_class(),
"reset_to_factory_time_recommended_ms=" +
recommended_reset_to_factory_time_ms().to_string(),
"reset_to_factory_time_mandatory_ms=" +
mandatory_reset_to_factory_time_ms().to_string(),
"observed_reset_to_factory_time_ms=" +
to_optional_int_text(observed_reset_to_factory_ms),
"reset_to_factory_time_assessment=" + reset_to_factory_time_assessment,
"reset_to_factory_time_probeability=" + reset_to_factory_time_probeability(),
"reset_to_factory_time_probeability_class=" +
reset_to_factory_time_probeability_class(),
"ar_resource_release_time_recommended_ms=" +
recommended_ar_resource_release_timeout_ms().to_string(),
"ar_resource_release_time_mandatory_ms=" +
mandatory_ar_resource_release_timeout_ms().to_string(),
"observed_ar_resource_release_time_ms=" +
to_optional_int_text(observed_ar_resource_release_ms),
"ar_resource_release_time_assessment=" + ar_resource_release_time_assessment,
"ar_resource_release_time_probeability=" +
ar_resource_release_time_probeability(),
"ar_resource_release_time_probeability_class=" +
ar_resource_release_time_probeability_class(),
"autonegotiation_time_recommended_ms=" +
recommended_autonegotiation_timeout_ms().to_string(),
"autonegotiation_time_mandatory_ms=" +
mandatory_autonegotiation_timeout_ms().to_string(),
"observed_autonegotiation_time_ms=" +
to_optional_int_text(observed_autonegotiation_ms),
"autonegotiation_time_assessment=" + autonegotiation_time_assessment,
"autonegotiation_time_probeability=" + autonegotiation_time_probeability(),
"autonegotiation_time_probeability_class=" +
autonegotiation_time_probeability_class(),
"mandatory_set_storage_time_ms=" +
mandatory_set_storage_time_ms().to_string(),
"maximum_diagnosis_data_per_submodule_octets_exclusive_upper_bound=" +
maximum_diagnosis_data_per_submodule_octets_exclusive_upper_bound().to_string(),
"minimum_logbook_entries=" + minimum_logbook_entries().to_string(),
"logbook_data_probeability=" + logbook_data_probeability(),
"logbook_data_probeability_class=" + logbook_data_probeability_class(),
"pruning_mac_destination_support_recommended=" +
pruning_mac_destination_support_recommended().to_string(),
"lldp_system_capabilities_station_only_required=" +
lldp_system_capabilities_station_only_required().to_string(),
"lldp_msg_tx_hold=" + lldp_msg_tx_hold().to_string(),
"lldp_msg_tx_interval_ms=" + lldp_msg_tx_interval_ms().to_string(),
"lldp_msg_tx_interval_positive_jitter_ms=" +
lldp_msg_tx_interval_positive_jitter_ms().to_string(),
"lldp_tx_ttl_ms=" + lldp_tx_ttl_ms().to_string(),
"lldp_tx_ttl_positive_jitter_ms=" +
lldp_tx_ttl_positive_jitter_ms().to_string(),
"lldp_tx_now_on_change=" + lldp_tx_now_on_change().to_string(),
"lldp_minimum_neighbor_capacity=" +
lldp_minimum_neighbor_capacity().to_string(),
"lldp_recommended_neighbor_capacity=" +
lldp_recommended_neighbor_capacity().to_string(),
"lldp_neighbor_probeability=" + lldp_neighbor_probeability(),
"lldp_neighbor_probeability_class=" + lldp_neighbor_probeability_class(),
"minimum_arp_cache_size=" + minimum_arp_cache_size().to_string(),
"arp_cache_timeout_ms=" + arp_cache_timeout_ms().to_string(),
"arp_response_timeout_ms=" + arp_response_timeout_ms().to_string(),
"dns_request_timeout_ms=" + dns_request_timeout_ms().to_string(),
"reject_dcp_set_requests_support=" + reject_dcp_set_requests_support(),
"snmp_service_optional=" + snmp_service_optional().to_string(),
"snmp_default_access_disabled=" + snmp_default_access_disabled().to_string(),
"snmp_explicit_enable_required=" +
snmp_explicit_enable_required().to_string(),
"snmp_disabled_after_ar_abort=" + snmp_disabled_after_ar_abort().to_string(),
"snmp_cim_interface_overrides_ar=" +
snmp_cim_interface_overrides_ar().to_string(),
"snmp_community_public_access=" + snmp_community_public_access(),
"snmp_community_private_access=" + snmp_community_private_access(),
"snmp_set_timeout_recommended_ms=" +
recommended_snmp_set_timeout_ms().to_string(),
"snmp_set_timeout_mandatory_ms=" +
mandatory_snmp_set_timeout_ms().to_string(),
"snmp_get_timeout_recommended_ms=" +
recommended_snmp_get_timeout_ms().to_string(),
"snmp_get_timeout_mandatory_ms=" +
mandatory_snmp_get_timeout_ms().to_string(),
"snmp_get_next_timeout_recommended_ms=" +
recommended_snmp_get_next_timeout_ms().to_string(),
"snmp_get_next_timeout_mandatory_ms=" +
mandatory_snmp_get_next_timeout_ms().to_string(),
"snmp_mib_update_time_recommended_ms=" +
recommended_snmp_mib_update_time_ms().to_string(),
"snmp_mib_update_time_mandatory_ms=" +
mandatory_snmp_mib_update_time_ms().to_string(),
"snmp_feature_mib2_required=" + snmp_feature_mib2_required().to_string(),
"snmp_feature_lldp_mib_required=" +
snmp_feature_lldp_mib_required().to_string(),
"snmp_feature_ieee_8021as_mib_optional=" +
snmp_feature_ieee_8021as_mib_optional().to_string(),
"snmp_feature_bridge_mib_optional=" +
snmp_feature_bridge_mib_optional().to_string(),
"snmp_feature_pno_mib_optional=" +
snmp_feature_pno_mib_optional().to_string(),
"snmp_default_state_probeability=" + snmp_default_state_probeability(),
"snmp_default_state_probeability_class=" +
snmp_default_state_probeability_class(),
"snmp_timeout_probeability=" + snmp_timeout_probeability(),
"snmp_timeout_probeability_class=" + snmp_timeout_probeability_class(),
"snmp_mib_update_probeability=" + snmp_mib_update_probeability(),
"snmp_mib_update_probeability_class=" + snmp_mib_update_probeability_class(),
"dhcp_service_optional=" + dhcp_service_optional().to_string(),
"dhcp_subnet_mask_option_required=" +
dhcp_subnet_mask_option_required().to_string(),
"dhcp_gateway_option_required=" + dhcp_gateway_option_required().to_string(),
"dhcp_parameter_data_required_value=" +
dhcp_parameter_data_required_value().to_string(),
"dhcp_other_options_optional=" + dhcp_other_options_optional().to_string(),
"dhcp_option_support_probeability=" + dhcp_option_support_probeability(),
"dhcp_option_support_probeability_class=" +
dhcp_option_support_probeability_class(),
"dhcp_runtime_state_probeability=" + dhcp_runtime_state_probeability(),
"dhcp_runtime_state_probeability_class=" +
dhcp_runtime_state_probeability_class(),
"overall_match=" + overall_match.to_string(),
"mismatch_fields=" + mismatch_fields.join(","),
].join("\n")
}
///|
fn format_live_validation_report(
operation : String,
observation : LiveSetObservation,
validation : String?,
) -> String {
let lines : Array[String] = []
lines.push("operation=" + operation)
lines.push("response_text_begin")
lines.push(observation.response_text)
lines.push("response_text_end")
lines.push(
"target_state_observed=" +
(observation.observed_set_usage_ms is Some(_)).to_string(),
)
match observation.observed_set_usage_ms {
Some(value) => lines.push("observed_set_usage_time_ms=" + value.to_string())
None => lines.push("observed_set_usage_time_ms=")
}
match validation {
Some(report) => {
lines.push("")
lines.push(report)
}
None => lines.push("validation_status=target_state_not_observed")
}
lines.join("\n")
}
///|
pub fn parse_gsdml_summary_file(path : StringView) -> String raise {
format_gsd_summary(@gsd.parse_gsdml_summary_file(path))
}
///|
pub fn validate_device_fixture_against_gsd_file(
packet_hex : StringView,
gsd_path : StringView,
) -> String raise {
validate_device_fixture_against_gsd_file_with_timings(packet_hex, gsd_path)
}
///|
pub fn validate_device_fixture_against_gsd_file_with_timings(
packet_hex : StringView,
gsd_path : StringView,
observed_set_usage_ms? : Int? = None,
observed_ip_startup_ms? : Int? = None,
observed_reset_to_factory_ms? : Int? = None,
observed_ar_resource_release_ms? : Int? = None,
observed_autonegotiation_ms? : Int? = None,
) -> String raise {
let response = @dcp.parse_response(@frame.bytes_from_hex(packet_hex))
let summary = @dcp.summarize_response(response)
let gsd_summary = @gsd.parse_gsdml_summary_file(gsd_path)
validate_device_summary_against_gsd(
summary, gsd_summary, observed_set_usage_ms, observed_ip_startup_ms, observed_reset_to_factory_ms,
observed_ar_resource_release_ms, observed_autonegotiation_ms,
)
}
///|
pub fn validate_scanned_devices_against_gsd_file(
interface_name : StringView,
gsd_path : StringView,
timeout_ms? : Int = default_dcp_identify_timeout_ms(),
xid? : Int = 16909060,
) -> String {
validate_scanned_devices_against_gsd_file_with_timings(
interface_name,
gsd_path,
timeout_ms~,
xid~,
)
}
///|
pub fn validate_scanned_devices_against_gsd_file_with_timings(
interface_name : StringView,
gsd_path : StringView,
timeout_ms? : Int = default_dcp_identify_timeout_ms(),
xid? : Int = 16909060,
observed_set_usage_ms? : Int? = None,
observed_ip_startup_ms? : Int? = None,
observed_reset_to_factory_ms? : Int? = None,
observed_ar_resource_release_ms? : Int? = None,
observed_autonegotiation_ms? : Int? = None,
) -> String {
root_facade_native_handoff_text(
"validate_scanned_devices_against_gsd_file_with_timings",
"@rawnet.RawSocket::open + DCP Identify + GSD validation",
[
"interface=" + interface_name.to_owned(),
"gsd_path=" + gsd_path.to_owned(),
"timeout_ms=" + timeout_ms.to_string(),
"xid=" + xid.to_string(),
"observed_set_usage_time_ms=" +
to_optional_int_text(observed_set_usage_ms),
"observed_ip_startup_time_ms=" +
to_optional_int_text(observed_ip_startup_ms),
"observed_reset_to_factory_time_ms=" +
to_optional_int_text(observed_reset_to_factory_ms),
"observed_ar_resource_release_time_ms=" +
to_optional_int_text(observed_ar_resource_release_ms),
"observed_autonegotiation_time_ms=" +
to_optional_int_text(observed_autonegotiation_ms),
"gsd_validation_materialized=false",
],
)
}
///|
pub fn validate_live_set_name_against_gsd_file(
interface_name : StringView,
destination_mac : StringView,
station_name : String,
gsd_path : StringView,
timeout_ms? : Int = default_dcp_set_get_timeout_ms(),
observe_window_ms? : Int = 2000,
xid? : Int = 16909060,
) -> String raise {
let observation = live_set_station_name_observation(
interface_name,
destination_mac,
station_name,
timeout_ms~,
observe_window_ms~,
xid~,
)
let validation = match observation.observed_summary {
Some(summary) => {
let gsd_summary = @gsd.parse_gsdml_summary_file(gsd_path)
Some(
validate_device_summary_against_gsd(
summary,
gsd_summary,
observation.observed_set_usage_ms,
None,
None,
None,
None,
),
)
}
None => None
}
format_live_validation_report("set-name", observation, validation)
}
///|
pub fn validate_live_set_ip_against_gsd_file(
interface_name : StringView,
destination_mac : StringView,
address : StringView,
mask : StringView,
gateway : StringView,
gsd_path : StringView,
timeout_ms? : Int = default_dcp_set_get_timeout_ms(),
observe_window_ms? : Int = 2000,
xid? : Int = 16909060,
) -> String raise {
let observation = live_set_ip_parameters_observation(
interface_name,
destination_mac,
address,
mask,
gateway,
timeout_ms~,
observe_window_ms~,
xid~,
)
let validation = match observation.observed_summary {
Some(summary) => {
let gsd_summary = @gsd.parse_gsdml_summary_file(gsd_path)
Some(
validate_device_summary_against_gsd(
summary,
gsd_summary,
observation.observed_set_usage_ms,
None,
None,
None,
None,
),
)
}
None => None
}
format_live_validation_report("set-ip", observation, validation)
}