hl-engine 0.1.11

Safe Rust lifecycle API for the standalone HL Linux guest engine
mod codec;
mod input;
mod model;
mod namespace;

pub use codec::{decode_reply, decode_request, encode_reply, encode_request};
pub use model::{Reply, Request, SeekWhence, ServiceFailure, ServiceStat};
pub use namespace::{
    decode_namespace_install, encode_namespace_install, ProjectionKind, ServiceProjection,
};

#[cfg(test)]
mod tests {
    use super::{
        codec::WRITE, decode_namespace_install, decode_reply, decode_request,
        encode_namespace_install, encode_reply, encode_request, input::protocol, ProjectionKind,
        Reply, Request, ServiceFailure, ServiceProjection,
    };
    use crate::api::extension::ServiceId;
    use crate::provider::{LinuxError, Readiness, ReadyState};

    #[test]
    fn frozen_codec_round_trips_requests_replies_and_errno() {
        let request = Request::Write {
            handle: 7,
            offset: 11,
            bytes: b"owned".to_vec(),
        };
        let encoded = encode_request(&request, 16).unwrap();
        assert_eq!(
            encoded,
            [
                vec![WRITE],
                7_u64.to_le_bytes().to_vec(),
                11_u64.to_le_bytes().to_vec(),
                5_u32.to_le_bytes().to_vec(),
                b"owned".to_vec(),
            ]
            .concat()
        );
        assert_eq!(decode_request(&encoded, 16).unwrap(), request);

        let ready = Reply::Ready(Readiness {
            states: [ReadyState::Readable, ReadyState::Hangup]
                .into_iter()
                .collect(),
        });
        let encoded = encode_reply(&Ok(ready.clone()), 16).unwrap();
        assert_eq!(decode_reply(&encoded, 16).unwrap(), ready);

        let failure = ServiceFailure::Linux(LinuxError {
            errno: 19,
            context: "provider unavailable".into(),
        });
        let encoded = encode_reply(&Err(failure.clone()), 64).unwrap();
        assert_eq!(decode_reply(&encoded, 64).unwrap_err(), failure);
    }

    #[test]
    fn codecs_reject_trailing_invalid_and_oversized_payloads() {
        let mut request = encode_request(&Request::Close { handle: 1 }, 8).unwrap();
        request.push(0);
        assert_eq!(decode_request(&request, 8).unwrap_err(), protocol());
        assert_eq!(decode_request(&[WRITE], 8).unwrap_err(), protocol());
        assert!(matches!(
            encode_request(
                &Request::Write {
                    handle: 1,
                    offset: 0,
                    bytes: vec![0; 9],
                },
                8,
            ),
            Err(ServiceFailure::Linux(LinuxError { errno: 22, .. }))
        ));
    }

    #[test]
    fn namespace_codec_enforces_transaction_bounds_and_normalization() {
        let entries = vec![ServiceProjection {
            path: "/run/provider".into(),
            service: ServiceId(9),
            mode: 0o660,
            uid: 10,
            gid: 20,
            kind: ProjectionKind::Service,
        }];
        let wire = encode_namespace_install(&entries, 4, 128).unwrap();
        assert_eq!(decode_namespace_install(&wire, 4, 128).unwrap(), entries);

        let devices = vec![ServiceProjection {
            path: "/dev/provider".into(),
            service: ServiceId(10),
            mode: 0o660,
            uid: 11,
            gid: 12,
            kind: ProjectionKind::CharacterDevice {
                major: 226,
                minor: 128,
            },
        }];
        let wire = encode_namespace_install(&devices, 4, 128).unwrap();
        assert_eq!(decode_namespace_install(&wire, 4, 128).unwrap(), devices);

        let conflicts = vec![
            ServiceProjection {
                path: "/run/provider".into(),
                service: ServiceId(1),
                mode: 0o600,
                uid: 0,
                gid: 0,
                kind: ProjectionKind::Service,
            },
            ServiceProjection {
                path: "/run/provider/child".into(),
                service: ServiceId(2),
                mode: 0o600,
                uid: 0,
                gid: 0,
                kind: ProjectionKind::Service,
            },
        ];
        assert!(matches!(
            encode_namespace_install(&conflicts, 4, 128),
            Err(ServiceFailure::Linux(LinuxError { errno: 20, .. }))
        ));

        let mut trailing = encode_namespace_install(&entries, 4, 128).unwrap();
        trailing.push(0);
        assert_eq!(
            decode_namespace_install(&trailing, 4, 128).unwrap_err(),
            protocol()
        );
    }
}