summaryrefslogtreecommitdiff
path: root/crates/renderer/src/overlay/tcg.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/renderer/src/overlay/tcg.rs')
-rw-r--r--crates/renderer/src/overlay/tcg.rs122
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))
+}