bacnet-server 0.15.0

BACnet server: APDU dispatch and service handlers
Documentation
use super::*;

const SERVICES: [ConfirmedServiceChoice; 10] = [
    ConfirmedServiceChoice::WRITE_PROPERTY,
    ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE,
    ConfirmedServiceChoice::CREATE_OBJECT,
    ConfirmedServiceChoice::DELETE_OBJECT,
    ConfirmedServiceChoice::ADD_LIST_ELEMENT,
    ConfirmedServiceChoice::REMOVE_LIST_ELEMENT,
    ConfirmedServiceChoice::ATOMIC_WRITE_FILE,
    ConfirmedServiceChoice::SUBSCRIBE_COV,
    ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY,
    ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
];

fn rows(c: MutationDecisionCounters) -> [MutationServiceCounters; 10] {
    [
        c.write_property,
        c.write_property_multiple,
        c.create_object,
        c.delete_object,
        c.add_list_element,
        c.remove_list_element,
        c.atomic_write_file,
        c.subscribe_cov,
        c.subscribe_cov_property,
        c.subscribe_cov_property_multiple,
    ]
}

#[test]
fn mutation_counters_fixed_shape_exactness_and_saturation() {
    let store = MutationDecisions::default();
    assert_eq!(
        rows(store.snapshot()),
        [MutationServiceCounters::default(); 10]
    );
    for (index, service) in SERVICES.into_iter().enumerate() {
        for _ in 0..=index {
            store.record(service, MutationDecision::Allow);
            store.record(service, MutationDecision::Deny);
            store.record(service, MutationDecision::PolicyDeny);
        }
    }
    for (index, row) in rows(store.snapshot()).into_iter().enumerate() {
        let n = index as u64 + 1;
        assert_eq!(
            row,
            MutationServiceCounters {
                allow_total: n,
                deny_total: 2 * n,
                policy_deny_total: n,
            }
        );
    }
    let before = store.snapshot();
    store.record(
        ConfirmedServiceChoice::READ_PROPERTY,
        MutationDecision::PolicyDeny,
    );
    assert_eq!(store.snapshot(), before);
    for row in &store.0 {
        for counter in row {
            counter.store(u64::MAX - 1, Ordering::Relaxed);
        }
    }
    for _ in 0..3 {
        for service in SERVICES {
            store.record(service, MutationDecision::Allow);
            store.record(service, MutationDecision::Deny);
            store.record(service, MutationDecision::PolicyDeny);
        }
    }
    assert_eq!(
        rows(store.snapshot()),
        [MutationServiceCounters {
            allow_total: u64::MAX,
            deny_total: u64::MAX,
            policy_deny_total: u64::MAX,
        }; 10]
    );
}

#[test]
fn mutation_counters_concurrent_quiescent_totals_are_exact() {
    let store = MutationDecisions::default();
    std::thread::scope(|scope| {
        for _ in 0..8 {
            scope.spawn(|| {
                for _ in 0..100 {
                    for service in SERVICES {
                        store.record(service, MutationDecision::Allow);
                        store.record(service, MutationDecision::Deny);
                        store.record(service, MutationDecision::PolicyDeny);
                    }
                }
            });
        }
    });
    assert_eq!(
        rows(store.snapshot()),
        [MutationServiceCounters {
            allow_total: 800,
            deny_total: 1600,
            policy_deny_total: 800,
        }; 10]
    );
}