logbrew-cli 0.1.27

Public command-line interface for LogBrew.
Documentation
//! Upload-only dSYM archive integration fixtures.

use std::ffi::{OsStr, OsString};

use crate::support::upload_args;

pub(crate) fn dsym_upload_args(path: &OsStr, image_uuids: &[&str], dry_run: bool) -> Vec<OsString> {
    let mut args = upload_args(path);
    let _json = args.pop();
    for image_uuid in image_uuids {
        args.push(OsString::from("--expect-image-uuid"));
        args.push(OsString::from(image_uuid));
    }
    if dry_run {
        args.push(OsString::from("--dry-run"));
    }
    args.push(OsString::from("--json"));
    args
}

pub(crate) struct ZipFixtureEntry<'a> {
    name: &'a str,
    bytes: &'a [u8],
    unix_mode: u32,
}

impl<'a> ZipFixtureEntry<'a> {
    pub(crate) fn file(name: &'a str, bytes: &'a [u8]) -> Self {
        Self {
            name,
            bytes,
            unix_mode: 0o100_644,
        }
    }

    pub(crate) fn symlink(name: &'a str, target: &'a [u8]) -> Self {
        Self {
            name,
            bytes: target,
            unix_mode: 0o120_777,
        }
    }
}

pub(crate) fn stored_zip(
    entries: &[ZipFixtureEntry<'_>],
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let mut archive = Vec::new();
    let mut central = Vec::new();
    for entry in entries {
        let offset = u32::try_from(archive.len())?;
        let name = entry.name.as_bytes();
        let name_len = u16::try_from(name.len())?;
        let size = u32::try_from(entry.bytes.len())?;
        let checksum = crc32(entry.bytes);

        archive.extend_from_slice(0x0403_4b50u32.to_le_bytes().as_slice());
        for value in [20u16, 0, 0, 0, 0] {
            archive.extend_from_slice(value.to_le_bytes().as_slice());
        }
        for value in [checksum, size, size] {
            archive.extend_from_slice(value.to_le_bytes().as_slice());
        }
        archive.extend_from_slice(name_len.to_le_bytes().as_slice());
        archive.extend_from_slice(0u16.to_le_bytes().as_slice());
        archive.extend_from_slice(name);
        archive.extend_from_slice(entry.bytes);

        central.extend_from_slice(0x0201_4b50u32.to_le_bytes().as_slice());
        for value in [(3u16 << 8) | 20, 20, 0, 0, 0, 0] {
            central.extend_from_slice(value.to_le_bytes().as_slice());
        }
        for value in [checksum, size, size] {
            central.extend_from_slice(value.to_le_bytes().as_slice());
        }
        for value in [name_len, 0, 0, 0, 0] {
            central.extend_from_slice(value.to_le_bytes().as_slice());
        }
        central.extend_from_slice((entry.unix_mode << 16).to_le_bytes().as_slice());
        central.extend_from_slice(offset.to_le_bytes().as_slice());
        central.extend_from_slice(name);
    }
    let central_offset = u32::try_from(archive.len())?;
    let central_size = u32::try_from(central.len())?;
    archive.extend_from_slice(central.as_slice());
    archive.extend_from_slice(0x0605_4b50u32.to_le_bytes().as_slice());
    for value in [
        0u16,
        0,
        u16::try_from(entries.len())?,
        u16::try_from(entries.len())?,
    ] {
        archive.extend_from_slice(value.to_le_bytes().as_slice());
    }
    archive.extend_from_slice(central_size.to_le_bytes().as_slice());
    archive.extend_from_slice(central_offset.to_le_bytes().as_slice());
    archive.extend_from_slice(0u16.to_le_bytes().as_slice());
    Ok(archive)
}

fn crc32(bytes: &[u8]) -> u32 {
    let mut crc = u32::MAX;
    for byte in bytes {
        crc ^= u32::from(*byte);
        for _ in 0..8 {
            let mask = 0u32.wrapping_sub(crc & 1);
            crc = (crc >> 1) ^ (0xedb8_8320 & mask);
        }
    }
    !crc
}