commit 823480491a96a02ad14d76725657dca92fcc3d7f Author: clizia Date: Sun Nov 10 03:47:58 2024 +0100 chunk type implemented (also first commit) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3662295 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "pngtoto" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..230cb27 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "pngtoto" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/chunk.rs b/src/chunk.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/chunk_type.rs b/src/chunk_type.rs new file mode 100644 index 0000000..ec8ab30 --- /dev/null +++ b/src/chunk_type.rs @@ -0,0 +1,256 @@ +use core::str; +use std::{error::Error, fmt::{write, Display}, str::FromStr, u8}; + +fn byte_to_bits(byte: u8) -> [u8; 8] { + let mut bits = [0u8; 8]; + + for i in 0..=7 { + let shifted_byte = byte >> i; + let cur_bit = shifted_byte & 1; + bits[7 - i] = cur_bit; + } + + bits +} + +/// ChunkType is defined as a 4 byte string with the 5th +/// bit of every byte having a special meaning, thus being +/// the "property bit". I have named the bytes with their +/// property bit name. +#[derive(Debug, PartialEq, Eq)] +struct ChunkType { + ancillary_byte: u8, + private_byte: u8, + reserved_byte: u8, + safe_to_copy_byte: u8, +} + +impl ChunkType { + + fn bytes(&self) -> [u8; 4] { + let bytes: [u8; 4] = [ + self.ancillary_byte, + self.private_byte, + self.reserved_byte, + self.safe_to_copy_byte + ]; + + bytes + } + + fn is_valid(&self) -> bool { + let mut x = self.bytes().len(); + + loop { + x -= 1; + + if x == 2 { + if self.bytes()[x].is_ascii_lowercase() { + break false; + } else { + continue; + } + } else if !self.bytes()[x].is_ascii_alphabetic() { + break false; + } else if x == 0 { + break true; + } else { + continue; + } + } + + } + + + fn is_critical(&self) -> bool { + self.ancillary_byte.is_ascii_uppercase() + } + + fn is_public(&self) -> bool { + self.private_byte.is_ascii_uppercase() + + } + + fn is_reserved_bit_valid(&self) -> bool { + self.reserved_byte.is_ascii_uppercase() + + } + + fn is_safe_to_copy(&self) -> bool { + self.safe_to_copy_byte.is_ascii_lowercase() + } + +} + +impl TryFrom<[u8; 4]> for ChunkType { + type Error = &'static str; + + fn try_from(value: [u8; 4]) -> Result { + + let chunk_type = ChunkType { + ancillary_byte: value[0], + private_byte: value[1], + reserved_byte: value[2], + safe_to_copy_byte: value[3], + }; + + let mut x = chunk_type.bytes().len(); + + loop { + x -= 1; + + if !chunk_type.bytes()[x].is_ascii_alphabetic() { + break Err("The chunk type can't contain non-alphabetic bytes"); + } else if x == 0 { + break Ok(chunk_type); + } else { + continue; + } + } + + } +} + +impl std::fmt::Display for ChunkType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let buf = self.bytes(); + + let s = match str::from_utf8(&buf) { + Ok(v) => v, + Err(e) => panic!("Invalid utf8 sequence: {}", e), + }; + + write!(f, "{}", s) + } + +} + +impl FromStr for ChunkType { + type Err = Box; + + fn from_str(s: &str) -> Result { + let bytes = s.as_bytes(); + + let chunk_type = ChunkType { + ancillary_byte: bytes[0], + private_byte: bytes[1], + reserved_byte: bytes[2], + safe_to_copy_byte: bytes[3], + }; + + let mut x = chunk_type.bytes().len(); + + loop { + x -= 1; + + if !chunk_type.bytes()[x].is_ascii_alphabetic() { + break Err("The chunk type can't contain non-alphabetic bytes".into()); + } else if x == 0 { + break Ok(chunk_type); + } else { + continue; + } + } + + } +} + +// unit tests +#[cfg(test)] +mod tests { + use super::*; + use std::convert::TryFrom; + use std::str::FromStr; + + #[test] + pub fn test_chunk_type_from_bytes() { + let expected = [82, 117, 83, 116]; + let actual = ChunkType::try_from([82, 117, 83, 116]).unwrap(); + + assert_eq!(expected, actual.bytes()); + } + + #[test] + pub fn test_chunk_type_from_str() { + let expected = ChunkType::try_from([82, 117, 83, 116]).unwrap(); + let actual = ChunkType::from_str("RuSt").unwrap(); + assert_eq!(expected, actual); + } + + #[test] + pub fn test_chunk_type_is_critical() { + let chunk = ChunkType::from_str("RuSt").unwrap(); + assert!(chunk.is_critical()); + } + + #[test] + pub fn test_chunk_type_is_not_critical() { + let chunk = ChunkType::from_str("ruSt").unwrap(); + assert!(!chunk.is_critical()); + } + + #[test] + pub fn test_chunk_type_is_public() { + let chunk = ChunkType::from_str("RUSt").unwrap(); + assert!(chunk.is_public()); + } + + #[test] + pub fn test_chunk_type_is_not_public() { + let chunk = ChunkType::from_str("RuSt").unwrap(); + assert!(!chunk.is_public()); + } + + #[test] + pub fn test_chunk_type_is_reserved_bit_valid() { + let chunk = ChunkType::from_str("RuSt").unwrap(); + assert!(chunk.is_reserved_bit_valid()); + } + + #[test] + pub fn test_chunk_type_is_reserved_bit_invalid() { + let chunk = ChunkType::from_str("Rust").unwrap(); + assert!(!chunk.is_reserved_bit_valid()); + } + + #[test] + pub fn test_chunk_type_is_safe_to_copy() { + let chunk = ChunkType::from_str("RuSt").unwrap(); + assert!(chunk.is_safe_to_copy()); + } + + #[test] + pub fn test_chunk_type_is_unsafe_to_copy() { + let chunk = ChunkType::from_str("RuST").unwrap(); + assert!(!chunk.is_safe_to_copy()); + } + + #[test] + pub fn test_valid_chunk_is_valid() { + let chunk = ChunkType::from_str("RuSt").unwrap(); + assert!(chunk.is_valid()); + } + + #[test] + pub fn test_invalid_chunk_is_valid() { + let chunk = ChunkType::from_str("Rust").unwrap(); + assert!(!chunk.is_valid()); + + let chunk = ChunkType::from_str("Ru1t"); + assert!(chunk.is_err()); + } + + #[test] + pub fn test_chunk_type_string() { + let chunk = ChunkType::from_str("RuSt").unwrap(); + assert_eq!(&chunk.to_string(), "RuSt"); + } + + #[test] + pub fn test_chunk_type_trait_impls() { + let chunk_type_1: ChunkType = TryFrom::try_from([82, 117, 83, 116]).unwrap(); + let chunk_type_2: ChunkType = FromStr::from_str("RuSt").unwrap(); + let _chunk_string = format!("{}", chunk_type_1); + let _are_chunks_equal = chunk_type_1 == chunk_type_2; + } +} diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..daec155 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,12 @@ +mod args; +mod chunk; +mod chunk_type; +mod commands; +mod png; + +pub type Error = Box; +pub type Result = std::result::Result; + +fn main() -> Result<()> { + todo!() +} diff --git a/src/png.rs b/src/png.rs new file mode 100644 index 0000000..e69de29