diff options
Diffstat (limited to 'crates/renderer/src/overlay/tcg.rs')
| -rw-r--r-- | crates/renderer/src/overlay/tcg.rs | 122 |
1 files changed, 110 insertions, 12 deletions
diff --git a/crates/renderer/src/overlay/tcg.rs b/crates/renderer/src/overlay/tcg.rs index 7361e86..ece01e8 100644 --- a/crates/renderer/src/overlay/tcg.rs +++ b/crates/renderer/src/overlay/tcg.rs @@ -1,6 +1,8 @@ +use sha2::Digest; use teleia::*; -use std::{cell::RefCell, io::Write, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; +use std::fmt::Write as _; use redis::Commands; use image::EncodableLayout; @@ -129,7 +131,7 @@ impl RenderedCardSlot { struct ImageWrite { buf: Rc<RefCell<Vec<u8>>>, } -impl Write for ImageWrite { +impl std::io::Write for ImageWrite { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.buf.borrow_mut().write(buf) } @@ -138,6 +140,7 @@ impl Write for ImageWrite { struct ImageEncoder { frames: u32, frames_left: u32, + frame_hashes: Vec<u8>, buf: Rc<RefCell<Vec<u8>>>, writer: png::Writer<ImageWrite>, } @@ -164,12 +167,14 @@ impl ImageEncoder { Some(Self { frames, frames_left: frames, + frame_hashes: Vec::new(), buf, writer, }) } fn write_frame(&mut self, pixels: &[u8]) { if self.frames_left > 0 { + self.frame_hashes.extend_from_slice(sha2::Sha256::digest(pixels).as_slice()); let _ = self.writer.write_image_data(&pixels); self.frames_left -= 1; } @@ -177,10 +182,10 @@ impl ImageEncoder { fn is_finished(&self) -> bool { self.frames_left == 0 } - fn finish(self) -> Option<Vec<u8>> { + fn finish(self) -> Option<(Vec<u8>, Vec<u8>)> { if self.is_finished() { self.writer.finish().expect("failed to finish"); - Some(self.buf.replace(Vec::new())) + Some((self.buf.replace(Vec::new()), self.frame_hashes)) } else { None } } } @@ -283,15 +288,24 @@ impl Marquee { } } fn upload_card(ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State, - c: &Card, buf: &[u8] + c: &Card, buf: &[u8], hashes: &[u8], ) -> Erm<()> { - let with_meta = web_image_meta::png::add_text_chunk( - buf, "lcolonqtcg", &c.encoded, + let mut signed = Vec::from(c.encoded.as_bytes()); + signed.extend(hashes); + let signature = sign(&mut ost.redis_conn, &signed)?; + let mut res = "+SIGNED+".to_owned(); + for s in signature { + write!(res, "{:02x}", s)?; + } + res.push_str("+"); + res.push_str(&c.encoded); + let with_signature = web_image_meta::png::add_text_chunk( + buf, "lcolonqtcg", &res, )?; let uuid = uuid::Uuid::new_v4(); - let _: () = ost.redis_conn.hset("tcg:cards", uuid.to_string(), &with_meta)?; + let () = ost.redis_conn.hset("tcg:cards", uuid.to_string(), &with_signature)?; let inventory_key = format!("tcg-inventory:{}", c.owner_id); - let _: () = ost.redis_conn.lpush(inventory_key, uuid.to_string())?; + let () = ost.redis_conn.lpush(inventory_key, uuid.to_string())?; Ok(()) } pub fn render(&mut self, @@ -299,9 +313,9 @@ impl Marquee { renderer: &CardRenderer, ) { for s in self.slots.iter_mut() { - if let Some(b) = s.encoder.take_if(|e| e.is_finished()).and_then(|enc| enc.finish()) { + if let Some((b, h)) = s.encoder.take_if(|e| e.is_finished()).and_then(|enc| enc.finish()) { if let Some(c) = &s.card.card { - let _ = Self::upload_card(ctx, st, ost, &c, &b).expect("failed to upload"); + let _ = Self::upload_card(ctx, st, ost, &c, &b, &h).expect("failed to upload"); } } } @@ -554,7 +568,7 @@ impl overlay::Overlay for Overlay { fn reset(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State) -> Erm<()> { Ok(()) } - fn handle_binary(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State, msg: &fig::BinaryMessage) -> Erm<()> { + fn handle_binary(&mut self, ctx: &context::Context, st: &mut state::State, ost: &mut overlay::State, msg: &net::fig::BinaryMessage) -> Erm<()> { match &*msg.event { b"overlay tcg generate" => { let res: Erm<()> = (|| { @@ -621,3 +635,87 @@ impl overlay::Overlay for Overlay { Ok(()) } } + +pub fn sign(conn: &mut redis::Connection, data: &[u8]) -> Erm<Vec<u8>> { + let mut secret: Vec<u8> = conn.get("tcg:secret")?; + secret.extend_from_slice(data); + Ok(Vec::from(sha2::Sha256::digest(&secret).as_slice())) +} + +pub fn validate_card(png: &[u8]) -> Erm<bool> { + let decoder = png::Decoder::new(png); + let mut reader = decoder.read_info()?; + let mut buf = vec![0; reader.output_buffer_size()]; + let mut frame_hashes = Vec::new(); + while let Ok(f) = reader.next_frame(&mut buf) { + frame_hashes.extend_from_slice(sha2::Sha256::digest(&buf).as_slice()); + } + reader.finish()?; + for tc in reader.info().uncompressed_latin1_text.iter() { + if tc.keyword == "lcolonqtcg" { + eprintln!("{}", tc.text); + let (sig, meta) = if let Some(suffix) = tc.text.strip_prefix("+SIGNED+") { + suffix.split_once("+").unwrap_or(("", suffix)) + } else { + ("", &*tc.text) + }; + let mut to_sign = Vec::from(meta); + to_sign.extend_from_slice(&frame_hashes); + eprintln!("to_sign: {:?}", to_sign); + let signed = { + let redis = redis::Client::open("redis://shiro").unwrap(); + let mut redis_conn = redis.get_connection().unwrap(); + sign(&mut redis_conn, &to_sign)? + }; + let mut computed_sig = String::new(); + for s in signed { + write!(computed_sig, "{:02x}", s)?; + } + eprintln!("attached signature: {}", sig); + eprintln!("computed signature: {}", computed_sig); + return Ok(sig == computed_sig); + } + } + Ok(false) +} + +pub fn repair_card(png: &[u8]) -> Erm<Vec<u8>> { + let decoder = png::Decoder::new(png); + let mut reader = decoder.read_info()?; + let mut buf = vec![0; reader.output_buffer_size()]; + let mut frame_hashes = Vec::new(); + while let Ok(f) = reader.next_frame(&mut buf) { + frame_hashes.extend_from_slice(sha2::Sha256::digest(&buf).as_slice()); + } + reader.finish()?; + for tc in reader.info().uncompressed_latin1_text.iter() { + if tc.keyword == "lcolonqtcg" { + let (sig, meta) = if let Some(suffix) = tc.text.strip_prefix("+SIGNED+") { + suffix.split_once("+").unwrap_or(("", suffix)) + } else { + ("", &*tc.text) + }; + let mut to_sign = Vec::from(meta); + to_sign.extend_from_slice(&frame_hashes); + eprintln!("to_sign: {:?}", to_sign); + let signed = { + let redis = redis::Client::open("redis://shiro").unwrap(); + let mut redis_conn = redis.get_connection().unwrap(); + sign(&mut redis_conn, &to_sign)? + }; + let mut computed_sig = String::new(); + for s in signed { + write!(computed_sig, "{:02x}", s)?; + } + if sig != computed_sig { + eprintln!("repairing, new signature: {}", computed_sig); + eprintln!("old signature: {}", sig); + let clean = web_image_meta::png::clean_chunks(png)?; + return Ok(web_image_meta::png::add_text_chunk( + &clean, "lcolonqtcg", &format!("+SIGNED+{}+{}", computed_sig, meta), + )?); + } + } + } + Ok(Vec::from(png)) +} |
