use crate::crypto::signatures::PQSignature;
use blake3;
use serde::{Deserialize, Serialize};
use sha3::{Digest, Sha3_512};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ObjectHash(pub String);
impl ObjectHash {
pub fn from_bytes(data: &[u8]) -> Self {
let mut sha3_hasher = Sha3_512::new();
sha3_hasher.update(data);
let sha3_result = sha3_hasher.finalize();
let blake3_result = blake3::hash(data);
let combined = format!(
"{}{}",
hex::encode(sha3_result),
hex::encode(blake3_result.as_bytes())
);
ObjectHash(combined)
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn from_hex(hex: String) -> Self {
ObjectHash(hex)
}
pub fn short(&self) -> String {
self.0.chars().take(16).collect()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Display for ObjectHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Object {
Blob(Blob),
Tree(Tree),
Commit(Commit),
Tag(Tag),
}
impl Object {
pub fn type_name(&self) -> &str {
match self {
Object::Blob(_) => "blob",
Object::Tree(_) => "tree",
Object::Commit(_) => "commit",
Object::Tag(_) => "tag",
}
}
pub fn to_bytes(&self) -> Vec<u8> {
serde_json::to_vec(self).expect("Failed to serialize object")
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
serde_json::from_slice(bytes).map_err(|e| format!("Failed to deserialize object: {}", e))
}
pub fn hash(&self) -> ObjectHash {
ObjectHash::from_bytes(&self.to_bytes())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Blob {
pub content: Vec<u8>,
}
impl Blob {
pub fn new(content: Vec<u8>) -> Self {
Blob { content }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TreeEntry {
pub mode: String,
pub name: String,
pub hash: ObjectHash,
pub object_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tree {
pub entries: Vec<TreeEntry>,
}
impl Default for Tree {
fn default() -> Self {
Self::new()
}
}
impl Tree {
pub fn new() -> Self {
Tree {
entries: Vec::new(),
}
}
pub fn add_entry(&mut self, mode: String, name: String, hash: ObjectHash, object_type: String) {
self.entries.push(TreeEntry {
mode,
name,
hash,
object_type,
});
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Commit {
pub tree: ObjectHash,
pub parents: Vec<ObjectHash>,
pub author: String,
pub committer: String,
pub timestamp: i64,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pq_signature: Option<PQSignature>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
}
impl Commit {
pub fn new(
tree: ObjectHash,
parents: Vec<ObjectHash>,
author: String,
message: String,
) -> Self {
let timestamp = chrono::Utc::now().timestamp();
Commit {
tree,
parents,
author: author.clone(),
committer: author,
timestamp,
message,
pq_signature: None, metadata: None,
timezone: None, }
}
pub fn sign(&mut self, keypair: &crate::crypto::signatures::PQKeyPair) {
let mut commit_data = self.clone();
commit_data.pq_signature = None;
let data = serde_json::to_vec(&commit_data).expect("Failed to serialize commit");
self.pq_signature = Some(keypair.sign(&data));
}
pub fn verify_signature(&self) -> Result<(), String> {
match &self.pq_signature {
Some(sig) => {
let mut commit_data = self.clone();
commit_data.pq_signature = None;
let data = serde_json::to_vec(&commit_data)
.map_err(|e| format!("Failed to serialize: {}", e))?;
sig.verify(&data)
}
None => Err("Commit is not signed".to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tag {
pub target: ObjectHash,
pub target_type: String,
pub tag_name: String,
pub tagger: String,
pub timestamp: i64,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pq_signature: Option<PQSignature>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
}
impl Tag {
pub fn new(
target: ObjectHash,
target_type: String,
tag_name: String,
tagger: String,
message: String,
) -> Self {
Tag {
target,
target_type,
tag_name,
tagger,
timestamp: chrono::Utc::now().timestamp(),
message,
pq_signature: None,
metadata: None,
timezone: None, }
}
pub fn sign(&mut self, keypair: &crate::crypto::signatures::PQKeyPair) {
let mut tag_data = self.clone();
tag_data.pq_signature = None;
let data = serde_json::to_vec(&tag_data).expect("Failed to serialize tag");
self.pq_signature = Some(keypair.sign(&data));
}
pub fn verify_signature(&self) -> Result<(), String> {
match &self.pq_signature {
Some(sig) => {
let mut tag_data = self.clone();
tag_data.pq_signature = None;
let data = serde_json::to_vec(&tag_data)
.map_err(|e| format!("Failed to serialize: {}", e))?;
sig.verify(&data)
}
None => Err("Tag is not signed".to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_hash() {
let data = b"test content";
let hash = ObjectHash::from_bytes(data);
assert_eq!(hash.as_str().len(), 192); }
#[test]
fn test_blob() {
let content = b"Hello, world!".to_vec();
let blob = Blob::new(content.clone());
assert_eq!(blob.content, content);
}
#[test]
fn test_tree() {
let mut tree = Tree::new();
let hash = ObjectHash::from_hex("abc123".to_string());
tree.add_entry(
"100644".to_string(),
"file.txt".to_string(),
hash,
"blob".to_string(),
);
assert_eq!(tree.entries.len(), 1);
}
}