use sha2::{Digest, Sha512}; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom}; use tracing::instrument; use typed_builder::TypedBuilder; use super::header::{FileSection, Header}; use super::manifest::Manifest; use super::SIG_CONTEXT; use crate::util::io::to_cbor_async_writer; use crate::util::HashWriter; use crate::{Error, ResultExt}; #[derive(TypedBuilder)] pub struct S9pkPacker< 'a, W: AsyncWriteExt + AsyncSeekExt, RLicense: AsyncReadExt + Unpin, RInstructions: AsyncReadExt + Unpin, RIcon: AsyncReadExt + Unpin, RDockerImages: AsyncReadExt + Unpin, RAssets: AsyncReadExt + Unpin, RScripts: AsyncReadExt + Unpin, > { writer: W, manifest: &'a Manifest, license: RLicense, instructions: RInstructions, icon: RIcon, docker_images: RDockerImages, assets: RAssets, scripts: Option, } impl< 'a, W: AsyncWriteExt + AsyncSeekExt + Unpin, RLicense: AsyncReadExt + Unpin, RInstructions: AsyncReadExt + Unpin, RIcon: AsyncReadExt + Unpin, RDockerImages: AsyncReadExt + Unpin, RAssets: AsyncReadExt + Unpin, RScripts: AsyncReadExt + Unpin, > S9pkPacker<'a, W, RLicense, RInstructions, RIcon, RDockerImages, RAssets, RScripts> { /// BLOCKING #[instrument(skip_all)] pub async fn pack(mut self, key: &ed25519_dalek::SigningKey) -> Result<(), Error> { let header_pos = self.writer.stream_position().await?; if header_pos != 0 { tracing::warn!("Appending to non-empty file."); } let mut header = Header::placeholder(); header.serialize(&mut self.writer).await.with_ctx(|_| { ( crate::ErrorKind::Serialization, "Writing Placeholder Header", ) })?; let mut position = self.writer.stream_position().await?; let mut writer = HashWriter::new(Sha512::new(), &mut self.writer); // manifest to_cbor_async_writer(&mut writer, self.manifest).await?; let new_pos = writer.inner_mut().stream_position().await?; header.table_of_contents.manifest = FileSection { position, length: new_pos - position, }; position = new_pos; // license tokio::io::copy(&mut self.license, &mut writer) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying License"))?; let new_pos = writer.inner_mut().stream_position().await?; header.table_of_contents.license = FileSection { position, length: new_pos - position, }; position = new_pos; // instructions tokio::io::copy(&mut self.instructions, &mut writer) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Instructions"))?; let new_pos = writer.inner_mut().stream_position().await?; header.table_of_contents.instructions = FileSection { position, length: new_pos - position, }; position = new_pos; // icon tokio::io::copy(&mut self.icon, &mut writer) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Icon"))?; let new_pos = writer.inner_mut().stream_position().await?; header.table_of_contents.icon = FileSection { position, length: new_pos - position, }; position = new_pos; // docker_images tokio::io::copy(&mut self.docker_images, &mut writer) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Docker Images"))?; let new_pos = writer.inner_mut().stream_position().await?; header.table_of_contents.docker_images = FileSection { position, length: new_pos - position, }; position = new_pos; // assets tokio::io::copy(&mut self.assets, &mut writer) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Assets"))?; let new_pos = writer.inner_mut().stream_position().await?; header.table_of_contents.assets = FileSection { position, length: new_pos - position, }; position = new_pos; // scripts if let Some(mut scripts) = self.scripts { tokio::io::copy(&mut scripts, &mut writer) .await .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Scripts"))?; let new_pos = writer.inner_mut().stream_position().await?; header.table_of_contents.scripts = Some(FileSection { position, length: new_pos - position, }); position = new_pos; } // header let (hash, _) = writer.finish(); self.writer.seek(SeekFrom::Start(header_pos)).await?; header.pubkey = key.into(); header.signature = key.sign_prehashed(hash, Some(SIG_CONTEXT))?; header .serialize(&mut self.writer) .await .with_ctx(|_| (crate::ErrorKind::Serialization, "Writing Header"))?; self.writer.seek(SeekFrom::Start(position)).await?; Ok(()) } }