canic-core 0.65.18

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
use super::*;
use crate::dto::{
    capability::{CAPABILITY_VERSION_V1, CapabilityProof},
    error::ErrorCode,
    rpc::{CyclesRequest, RootRequestMetadata},
};

const NS_PER_SEC: u64 = 1_000_000_000;

fn p(id: u8) -> Principal {
    Principal::from_slice(&[id; 29])
}

fn sample_request(cycles: u128) -> Request {
    Request::Cycles(CyclesRequest {
        cycles,
        metadata: None,
    })
}

fn sample_metadata(
    request_id: u8,
    nonce: u8,
    issued_at_ns: u64,
    ttl_ns: u64,
) -> CapabilityRequestMetadata {
    CapabilityRequestMetadata {
        request_id: [request_id; 16],
        nonce: [nonce; 16],
        issued_at_ns,
        ttl_ns,
    }
}

#[test]
fn root_capability_hash_changes_with_payload() {
    let hash_a =
        root_capability_hash(p(1), CAPABILITY_VERSION_V1, &sample_request(10)).expect("hash a");
    let hash_b =
        root_capability_hash(p(1), CAPABILITY_VERSION_V1, &sample_request(11)).expect("hash b");
    assert_ne!(hash_a, hash_b);
}

#[test]
fn root_capability_hash_binds_target_canister() {
    let req = sample_request(10);
    let hash_a = root_capability_hash(p(1), CAPABILITY_VERSION_V1, &req).expect("hash a");
    let hash_b = root_capability_hash(p(2), CAPABILITY_VERSION_V1, &req).expect("hash b");
    assert_ne!(hash_a, hash_b);
}

#[test]
fn root_capability_hash_binds_capability_version() {
    let req = sample_request(10);
    let hash_a = root_capability_hash(p(1), 1, &req).expect("hash a");
    let hash_b = root_capability_hash(p(1), 2, &req).expect("hash b");
    assert_ne!(hash_a, hash_b);
}

#[test]
fn root_capability_hash_ignores_request_metadata() {
    let req_a = Request::Cycles(CyclesRequest {
        cycles: 10,
        metadata: Some(RootRequestMetadata {
            request_id: [1u8; 32],
            ttl_ns: 60 * NS_PER_SEC,
        }),
    });
    let req_b = Request::Cycles(CyclesRequest {
        cycles: 10,
        metadata: Some(RootRequestMetadata {
            request_id: [2u8; 32],
            ttl_ns: 120 * NS_PER_SEC,
        }),
    });

    let hash_a = root_capability_hash(p(1), CAPABILITY_VERSION_V1, &req_a).expect("hash a");
    let hash_b = root_capability_hash(p(1), CAPABILITY_VERSION_V1, &req_b).expect("hash b");
    assert_eq!(hash_a, hash_b);
}

#[test]
fn project_replay_metadata_rejects_expired_metadata() {
    let err = project_replay_metadata(
        sample_metadata(1, 2, 900 * NS_PER_SEC, 50 * NS_PER_SEC),
        1_000 * NS_PER_SEC,
    )
    .expect_err("expired metadata must fail");
    assert_eq!(err.code, ErrorCode::Conflict);
}

#[test]
fn project_replay_metadata_rejects_expiry_boundary() {
    let err = project_replay_metadata(
        sample_metadata(1, 2, 900 * NS_PER_SEC, 50 * NS_PER_SEC),
        950 * NS_PER_SEC,
    )
    .expect_err("metadata at expiry boundary must fail");
    assert_eq!(err.code, ErrorCode::Conflict);
}

#[test]
fn project_replay_metadata_rejects_future_metadata_beyond_skew() {
    let err = project_replay_metadata(
        sample_metadata(1, 2, 1_031 * NS_PER_SEC, 60 * NS_PER_SEC),
        1_000 * NS_PER_SEC,
    )
    .expect_err("future metadata must fail");
    assert_eq!(err.code, ErrorCode::InvalidInput);
}

#[test]
fn project_replay_metadata_binds_nonce_into_request_id() {
    let a = project_replay_metadata(
        sample_metadata(3, 1, 1_000 * NS_PER_SEC, 60 * NS_PER_SEC),
        1_000 * NS_PER_SEC,
    )
    .expect("a");
    let b = project_replay_metadata(
        sample_metadata(3, 2, 1_000 * NS_PER_SEC, 60 * NS_PER_SEC),
        1_000 * NS_PER_SEC,
    )
    .expect("b");
    assert_ne!(a.request_id, b.request_id);
}

#[test]
fn with_root_request_metadata_overrides_existing_metadata() {
    let request = Request::Cycles(CyclesRequest {
        cycles: 10,
        metadata: Some(RootRequestMetadata {
            request_id: [7u8; 32],
            ttl_ns: 10 * NS_PER_SEC,
        }),
    });
    let metadata = RootRequestMetadata {
        request_id: [9u8; 32],
        ttl_ns: 60 * NS_PER_SEC,
    };

    let updated = with_root_request_metadata(request, metadata);
    match updated {
        Request::Cycles(req) => assert_eq!(req.metadata, Some(metadata)),
        _ => panic!("expected cycles request"),
    }
}

#[test]
fn validate_nonroot_cycles_envelope_accepts_structural_cycles() {
    validate_nonroot_cycles_envelope(
        CapabilityService::Root,
        CAPABILITY_VERSION_V1,
        &CapabilityProof::Structural,
    )
    .expect("structural cycles envelope must be accepted for non-root path");
}

#[test]
fn validate_root_capability_envelope_rejects_capability_version_mismatch() {
    let err = validate_root_capability_envelope(
        CapabilityService::Root,
        CAPABILITY_VERSION_V1 + 1,
        &CapabilityProof::Structural,
    )
    .expect_err("unsupported capability version must fail");
    assert_eq!(err.code, ErrorCode::InvalidInput);
}

#[test]
fn validate_root_capability_envelope_returns_structural_mode() {
    let proof = validate_root_capability_envelope(
        CapabilityService::Root,
        CAPABILITY_VERSION_V1,
        &CapabilityProof::Structural,
    )
    .expect("valid structural proof must validate");

    assert_eq!(proof.mode(), RootCapabilityProofMode::Structural);
}

#[test]
fn validate_nonroot_cycles_envelope_returns_structural_mode() {
    let proof = validate_nonroot_cycles_envelope(
        CapabilityService::Root,
        CAPABILITY_VERSION_V1,
        &CapabilityProof::Structural,
    )
    .expect("valid non-root structural proof must validate");

    assert_eq!(proof.mode(), RootCapabilityProofMode::Structural);
}

#[test]
fn verify_capability_hash_binding_rejects_mismatch() {
    let err =
        verify_capability_hash_binding(p(1), CAPABILITY_VERSION_V1, &sample_request(10), [0u8; 32])
            .expect_err("mismatched hash must fail");
    assert_eq!(err.code, ErrorCode::InvalidInput);
}

#[test]
fn verify_capability_hash_binding_accepts_match() {
    let request = sample_request(10);
    let hash = root_capability_hash(p(1), CAPABILITY_VERSION_V1, &request).expect("hash");
    verify_capability_hash_binding(p(1), CAPABILITY_VERSION_V1, &request, hash)
        .expect("matching hash must verify");
}