rialo-cdk 0.2.0-alpha.0

Rialo CDK - A comprehensive toolkit for building with the Rialo blockchain
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Program data source abstractions for the Rialo blockchain.
//!
//! This module provides the `ProgramDataSource` trait and its implementations,
//! which abstract over different sources of program binary data for deployment.

use std::{fs, path::Path};

use crate::error::{Result, RialoError};

/// Trait for program data sources that can provide program binary data.
///
/// This abstraction allows for different sources of program data:
/// - Filesystem paths (existing usage pattern)
/// - In-memory data (for build pipeline integration)
/// - Network sources (future extensibility)
pub trait ProgramDataSource: Send + Sync + std::fmt::Debug {
    /// Loads the program data from this source.
    ///
    /// Returns the program binary data as a vector of bytes.
    fn load_program_data(&self) -> Result<Vec<u8>>;

    /// Returns a human-readable description of this data source.
    fn description(&self) -> String;
}

/// Filesystem-based program data source.
///
/// This implements the existing behavior where programs are loaded from
/// filesystem paths.
#[derive(Debug, Clone)]
pub struct FileProgramDataSource {
    path: String,
}

impl FileProgramDataSource {
    /// Creates a new filesystem-based program data source.
    pub fn new<P: AsRef<Path>>(path: P) -> Self {
        Self {
            path: path.as_ref().to_string_lossy().to_string(),
        }
    }
}

impl ProgramDataSource for FileProgramDataSource {
    fn load_program_data(&self) -> Result<Vec<u8>> {
        let data = fs::read(&self.path).map_err(|e| {
            RialoError::InvalidInput(format!(
                "Failed to read program file '{}': {}",
                self.path, e
            ))
        })?;

        if data.is_empty() {
            return Err(RialoError::InvalidInput(
                "Program file is empty".to_string(),
            ));
        }

        if data.len() < 4 {
            return Err(RialoError::InvalidInput(
                "Program file too small to be valid".to_string(),
            ));
        }

        // Validate program format - we support eBPF, RISC-V, and PVM loaders
        // Note: eBPF and RISC-V programs use ELF format internally, but we don't expose "ELF" as a loader type
        let is_ebpf_or_riscv = &data[0..4] == b"\x7fELF"; // eBPF and RISC-V programs use ELF format
        let is_pvm = &data[0..4] == b"PVM\0"; // PVM bytecode format

        if !is_ebpf_or_riscv && !is_pvm {
            return Err(RialoError::InvalidInput(
                format!(
                    "Invalid program format: expected eBPF, RISC-V, or PVM program (got magic bytes: {:02x} {:02x} {:02x} {:02x})",
                    data[0], data[1], data[2], data[3]
                ),
            ));
        }

        Ok(data)
    }

    fn description(&self) -> String {
        format!("file:{}", self.path)
    }
}

/// In-memory program data source.
///
/// This allows for program data to be provided directly in memory,
/// useful for build pipeline integration where you want to pipe
/// build output directly to deployment without touching the filesystem.
#[derive(Debug, Clone)]
pub struct MemoryProgramDataSource {
    data: Vec<u8>,
    description: String,
}

impl MemoryProgramDataSource {
    /// Creates a new in-memory program data source.
    ///
    /// # Arguments
    /// * `data` - The program binary data
    /// * `description` - Optional description of the data source
    pub fn new(data: Vec<u8>, description: Option<String>) -> Self {
        Self {
            data,
            description: description.unwrap_or_else(|| "memory:unknown".to_string()),
        }
    }

    /// Creates a new in-memory program data source with a default description.
    pub fn new_with_default_description(data: Vec<u8>) -> Self {
        Self::new(data, None)
    }
}

impl ProgramDataSource for MemoryProgramDataSource {
    fn load_program_data(&self) -> Result<Vec<u8>> {
        if self.data.is_empty() {
            return Err(RialoError::InvalidInput(
                "Program data is empty".to_string(),
            ));
        }

        if self.data.len() < 4 {
            return Err(RialoError::InvalidInput(
                "Program data too small to be valid".to_string(),
            ));
        }

        // Validate program format - we support eBPF, RISC-V, and PVM loaders
        // Note: eBPF and RISC-V programs use ELF format internally, but we don't expose "ELF" as a loader type
        let is_ebpf_or_riscv = &self.data[0..4] == b"\x7fELF"; // eBPF and RISC-V programs use ELF format
        let is_pvm = &self.data[0..4] == b"PVM\0"; // PVM bytecode format

        if !is_ebpf_or_riscv && !is_pvm {
            return Err(RialoError::InvalidInput(
                format!(
                    "Invalid program format: expected eBPF, RISC-V, or PVM program (got magic bytes: {:02x} {:02x} {:02x} {:02x})",
                    self.data[0], self.data[1], self.data[2], self.data[3]
                ),
            ));
        }

        Ok(self.data.clone())
    }

    fn description(&self) -> String {
        self.description.clone()
    }
}

// Implement automatic conversion from Path/PathBuf for ease of use
// This covers String, &str, Path, PathBuf, and any other type that implements AsRef<Path>
impl<P: AsRef<Path>> From<P> for FileProgramDataSource {
    fn from(path: P) -> Self {
        Self::new(path)
    }
}