ic-certified-assets 0.3.0

Rust support for asset certification.
Documentation
use crate::evidence::EvidenceComputation::{Computed, NextChunkIndex, NextOperation};
use crate::state_machine::Chunk;
use crate::types::BatchOperation::{
    Clear, CreateAsset, DeleteAsset, SetAssetContent, SetAssetProperties, UnsetAssetContent,
};
use crate::types::{
    ChunkId, ClearArguments, CommitBatchArguments, CreateAssetArguments, DeleteAssetArguments,
    SetAssetContentArguments, SetAssetPropertiesArguments, UnsetAssetContentArguments,
};
use itertools::Itertools;
use serde_bytes::ByteBuf;
use sha2::{Digest, Sha256};
use std::collections::HashMap;

const TAG_FALSE: [u8; 1] = [0];
const TAG_TRUE: [u8; 1] = [1];

const TAG_NONE: [u8; 1] = [2];
const TAG_SOME: [u8; 1] = [3];

const TAG_CREATE_ASSET: [u8; 1] = [4];
const TAG_SET_ASSET_CONTENT: [u8; 1] = [5];
const TAG_UNSET_ASSET_CONTENT: [u8; 1] = [6];
const TAG_DELETE_ASSET: [u8; 1] = [7];
const TAG_CLEAR: [u8; 1] = [8];
const TAG_SET_ASSET_PROPERTIES: [u8; 1] = [9];

pub enum EvidenceComputation {
    NextOperation {
        operation_index: usize,
        hasher: Sha256,
    },

    NextChunkIndex {
        operation_index: usize,
        chunk_index: usize,
        hasher: Sha256,
    },

    Computed(ByteBuf),
}

impl Default for EvidenceComputation {
    fn default() -> Self {
        Self::new()
    }
}

impl EvidenceComputation {
    pub fn new() -> Self {
        NextOperation {
            operation_index: 0,
            hasher: Sha256::new(),
        }
    }

    pub fn advance(self, args: &CommitBatchArguments, chunks: &HashMap<ChunkId, Chunk>) -> Self {
        match self {
            NextOperation {
                operation_index,
                hasher,
            } => next_operation(args, operation_index, hasher),
            NextChunkIndex {
                operation_index,
                chunk_index,
                hasher,
            } => next_chunk_index(args, operation_index, chunk_index, hasher, chunks),
            Computed(evidence) => Computed(evidence),
        }
    }
}

fn next_operation(
    args: &CommitBatchArguments,
    operation_index: usize,
    mut hasher: Sha256,
) -> EvidenceComputation {
    match args.operations.get(operation_index) {
        None => {
            let sha256: [u8; 32] = hasher.finalize().into();
            Computed(ByteBuf::from(sha256))
        }
        Some(CreateAsset(args)) => {
            hash_create_asset(&mut hasher, args);
            NextOperation {
                operation_index: operation_index + 1,
                hasher,
            }
        }
        Some(SetAssetContent(args)) => {
            hash_set_asset_content(&mut hasher, args);
            NextChunkIndex {
                operation_index,
                chunk_index: 0,
                hasher,
            }
        }
        Some(UnsetAssetContent(args)) => {
            hash_unset_asset_content(&mut hasher, args);
            NextOperation {
                operation_index: operation_index + 1,
                hasher,
            }
        }
        Some(DeleteAsset(args)) => {
            hash_delete_asset(&mut hasher, args);
            NextOperation {
                operation_index: operation_index + 1,
                hasher,
            }
        }
        Some(Clear(args)) => {
            hash_clear(&mut hasher, args);
            NextOperation {
                operation_index: operation_index + 1,
                hasher,
            }
        }
        Some(SetAssetProperties(args)) => {
            hash_set_asset_properties(&mut hasher, args);
            NextOperation {
                operation_index: operation_index + 1,
                hasher,
            }
        }
    }
}

fn next_chunk_index(
    args: &CommitBatchArguments,
    operation_index: usize,
    chunk_index: usize,
    mut hasher: Sha256,
    chunks: &HashMap<ChunkId, Chunk>,
) -> EvidenceComputation {
    if let Some(SetAssetContent(sac)) = args.operations.get(operation_index) {
        if let Some(chunk_id) = sac.chunk_ids.get(chunk_index) {
            hash_chunk_by_id(&mut hasher, chunk_id, chunks);
            if chunk_index + 1 < sac.chunk_ids.len() {
                return NextChunkIndex {
                    operation_index,
                    chunk_index: chunk_index + 1,
                    hasher,
                };
            }
        } else if let Some(chunk_content) = sac.last_chunk.as_ref() {
            hash_chunk_by_content(&mut hasher, chunk_content);
        }
    }
    NextOperation {
        operation_index: operation_index + 1,
        hasher,
    }
}

fn hash_chunk_by_id(hasher: &mut Sha256, chunk_id: &ChunkId, chunks: &HashMap<ChunkId, Chunk>) {
    if let Some(chunk) = chunks.get(chunk_id) {
        hasher.update(&chunk.content);
    }
}

fn hash_chunk_by_content(hasher: &mut Sha256, chunk_content: &ByteBuf) {
    hasher.update(chunk_content);
}

fn hash_create_asset(hasher: &mut Sha256, args: &CreateAssetArguments) {
    hasher.update(TAG_CREATE_ASSET);
    hasher.update(&args.key);
    hasher.update(&args.content_type);
    if let Some(max_age) = args.max_age {
        hasher.update(TAG_SOME);
        hasher.update(max_age.to_be_bytes());
    } else {
        hasher.update(TAG_NONE);
    }
    hash_headers(hasher, args.headers.as_ref());
    hash_opt_bool(hasher, args.allow_raw_access);
    hash_opt_bool(hasher, args.enable_aliasing);
}

fn hash_set_asset_content(hasher: &mut Sha256, args: &SetAssetContentArguments) {
    hasher.update(TAG_SET_ASSET_CONTENT);
    hasher.update(&args.key);
    hasher.update(&args.content_encoding);
    hash_opt_bytebuf(hasher, args.sha256.as_ref());
}

fn hash_unset_asset_content(hasher: &mut Sha256, args: &UnsetAssetContentArguments) {
    hasher.update(TAG_UNSET_ASSET_CONTENT);
    hasher.update(&args.key);
    hasher.update(&args.content_encoding);
}

fn hash_delete_asset(hasher: &mut Sha256, args: &DeleteAssetArguments) {
    hasher.update(TAG_DELETE_ASSET);
    hasher.update(&args.key);
}

fn hash_clear(hasher: &mut Sha256, _args: &ClearArguments) {
    hasher.update(TAG_CLEAR);
}

fn hash_set_asset_properties(hasher: &mut Sha256, args: &SetAssetPropertiesArguments) {
    hasher.update(TAG_SET_ASSET_PROPERTIES);
    hasher.update(&args.key);
    if let Some(max_age) = args.max_age {
        hasher.update(TAG_SOME);
        if let Some(max_age) = max_age {
            hasher.update(TAG_SOME);
            hasher.update(max_age.to_be_bytes());
        } else {
            hasher.update(TAG_NONE);
        }
    } else {
        hasher.update(TAG_NONE);
    }
    if let Some(headers) = args.headers.as_ref() {
        hasher.update(TAG_SOME);
        hash_headers(hasher, headers.as_ref());
    } else {
        hasher.update(TAG_NONE);
    }
    if let Some(allow_raw_access) = args.allow_raw_access {
        hasher.update(TAG_SOME);
        hash_opt_bool(hasher, allow_raw_access);
    } else {
        hasher.update(TAG_NONE);
    }
    if let Some(enable_aliasing) = args.is_aliased {
        hasher.update(TAG_SOME);
        hash_opt_bool(hasher, enable_aliasing);
    } else {
        hasher.update(TAG_NONE);
    }
}

fn hash_opt_bool(hasher: &mut Sha256, b: Option<bool>) {
    if let Some(b) = b {
        hasher.update(TAG_SOME);
        hasher.update(if b { TAG_TRUE } else { TAG_FALSE });
    } else {
        hasher.update(TAG_NONE);
    }
}

fn hash_opt_bytebuf(hasher: &mut Sha256, buf: Option<&ByteBuf>) {
    if let Some(buf) = buf {
        hasher.update(TAG_SOME);
        hasher.update(buf);
    } else {
        hasher.update(TAG_NONE);
    }
}

fn hash_headers(hasher: &mut Sha256, headers: Option<&HashMap<String, String>>) {
    if let Some(headers) = headers {
        hasher.update(TAG_SOME);
        for k in headers.keys().sorted() {
            let v = headers.get(k).unwrap();
            hasher.update(k);
            hasher.update(v);
        }
    } else {
        hasher.update(TAG_NONE);
    }
}

#[test]
fn tag_value_uniqueness() {
    let tags = include_str!("evidence.rs")
        .lines()
        .filter(|l| l.starts_with("const TAG_"))
        .map(|line| {
            line.split(": [u8; 1] = [")
                .nth(1)
                .unwrap()
                .trim_end_matches("];")
                .parse::<u8>()
                .unwrap()
        });
    assert_eq!(
        tags.clone().count(),
        tags.unique().count(),
        "tag values must be unique"
    );
}