scion-stack 0.5.2

SCION endhost network stack
Documentation
// Copyright 2025 Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Integration tests for error SCION stack error handling.

use pocketscion::topologies::{IA132, UnderlayType, minimal::two_path_topology};
use scion_proto::address::{ScionAddrSvc, ServiceAddr, SocketAddr};
use scion_sdk_reqwest_connect_rpc::client::CrpcClientError;
use scion_stack::scionstack::{
    InvalidBindAddressError, ScionSocketBindError, ScionStackBuilder, SnapConnectionError,
    builder::{AllEndhostApisFailed, ApiAttemptError, BuildScionStackError},
};
use snap_tokens::v0::dummy_snap_token;
use test_log::test;

// Test implementations and their corresponding test functions

// This test doesn't depend on underlay type, so we only need one version
#[test(tokio::test)]
#[ntest::timeout(10_000)]
async fn endhost_api_unreachable_should_error() {
    let unreachable_url = "http://127.0.0.1:1".parse().unwrap();

    let result = ScionStackBuilder::new()
        .with_endhost_api(unreachable_url)
        .with_auth_token(dummy_snap_token())
        .build()
        .await;

    match result {
        Err(BuildScionStackError::AllEndhostApisFailed(AllEndhostApisFailed(errs)))
            if matches!(
                errs.as_slice(),
                [(
                    _,
                    ApiAttemptError::UnderlayDiscovery(CrpcClientError::ConnectionError { .. })
                )]
            ) => {}
        _ => {
            panic!(
                "expected BuildScionStackError::AllEndhostApisFailed for unreachable server, got {result:?}"
            )
        }
    };
}

#[test(tokio::test)]
#[ntest::timeout(10_000)]
async fn test_invalid_snap_token() {
    let ps_handle = two_path_topology(UnderlayType::Snap).await;

    let result = ScionStackBuilder::new()
        .with_endhost_api(ps_handle.endhost_api(IA132).await.unwrap())
        .with_auth_token("invalid token".to_string())
        .build()
        .await
        .unwrap()
        .bind(None)
        .await;

    // TODO(uniquefine): this should match a more specific error to indicate that the auth token
    // is invalid.
    assert!(
        matches!(
            result,
            Err(ScionSocketBindError::SnapConnectionError(
                SnapConnectionError::DataPlaneDiscoveryError(_)
            ))
        ),
        "expected Snap::DataPlaneDiscoveryError::CrpcError with Unauthenticated code for invalid token, got {result:?}"
    );
}

async fn test_bind_service_address_fails_impl(underlay: UnderlayType) {
    let ps_handle = two_path_topology(underlay).await;

    let stack = ScionStackBuilder::new()
        .with_endhost_api(ps_handle.endhost_api(IA132).await.unwrap())
        .with_auth_token(dummy_snap_token())
        .build()
        .await
        .unwrap();

    let service_addr = SocketAddr::new(ScionAddrSvc::new(0.into(), ServiceAddr::CONTROL).into(), 0);

    let result = stack.bind(Some(service_addr)).await;
    let error = result.unwrap_err();
    assert!(
        matches!(error, ScionSocketBindError::InvalidBindAddress(InvalidBindAddressError::ServiceAddress(addr)) if addr == service_addr),
        "expected InvalidBindAddress({service_addr:?}) when binding to service address, got {error:?}"
    );
}

#[test(tokio::test)]
#[ntest::timeout(10_000)]
async fn test_bind_service_address_fails_snap() {
    test_bind_service_address_fails_impl(UnderlayType::Snap).await;
}

#[test(tokio::test)]
#[ntest::timeout(10_000)]
async fn test_bind_service_address_fails_udp() {
    test_bind_service_address_fails_impl(UnderlayType::Udp).await;
}