mirror of
https://github.com/tlsnotary/proof_viz.git
synced 2026-01-09 23:18:13 -05:00
Use data.redacted() to visualize redacted ranges
This commit is contained in:
@@ -18,6 +18,7 @@ elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
|
|||||||
webpki-roots = "0.25.2"
|
webpki-roots = "0.25.2"
|
||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
web-time = "0.2"
|
web-time = "0.2"
|
||||||
|
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
|
||||||
|
|
||||||
# tlsn-core = { git = "https://github.com/tlsnotary/tlsn", branch = "dev" }
|
# tlsn-core = { git = "https://github.com/tlsnotary/tlsn", branch = "dev" }
|
||||||
[dependencies.tlsn-core]
|
[dependencies.tlsn-core]
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use std::fmt;
|
use std::{fmt, ops::Range};
|
||||||
|
|
||||||
|
use gloo::console::log;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub const REDACTED_CHAR: char = '█';
|
|
||||||
// pub const REDACTED_CHAR: char = '🙈';
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Send,
|
Send,
|
||||||
@@ -23,116 +21,91 @@ impl fmt::Display for Direction {
|
|||||||
#[derive(Clone, PartialEq, Properties)]
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
pub struct Props {
|
pub struct Props {
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub bytes: String,
|
pub redacted_char: char,
|
||||||
|
pub bytes: Vec<u8>,
|
||||||
|
pub redacted_ranges: Vec<Range<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
//split the text in regular text parts and redacted parts
|
fn get_redacted_string(redacted_char: &char, size: usize) -> String {
|
||||||
fn split_text(text: String) -> Vec<String> {
|
if true || size < 10 {
|
||||||
let (mut parts, last_part) = text.chars().fold(
|
redacted_char.to_string().repeat(size)
|
||||||
(Vec::new(), String::new()),
|
} else {
|
||||||
|(mut acc, mut current_part), c| {
|
format! {"{}...{}", redacted_char.to_string().repeat(3), redacted_char.to_string().repeat(3)}
|
||||||
let previous_c = current_part.chars().last().unwrap_or(REDACTED_CHAR);
|
}
|
||||||
if (c == REDACTED_CHAR) == (previous_c == REDACTED_CHAR) {
|
}
|
||||||
current_part.push(c);
|
|
||||||
} else {
|
fn redactions_in_red(
|
||||||
if !current_part.is_empty() {
|
bytes: &Vec<u8>,
|
||||||
acc.push(current_part.clone());
|
redacted_ranges: &Vec<Range<usize>>,
|
||||||
}
|
redacted_char: &char,
|
||||||
current_part.clear();
|
) -> Html {
|
||||||
current_part.push(c);
|
if redacted_ranges.is_empty() {
|
||||||
}
|
return Html::from(String::from_utf8(bytes.to_vec()).unwrap());
|
||||||
(acc, current_part)
|
}
|
||||||
|
|
||||||
|
// create ranges for non redacted parts and store last redacted position
|
||||||
|
let (non_redacted_ranges, last_redacted_position) = redacted_ranges.iter().fold(
|
||||||
|
(Vec::new(), 0), // (Accumulator vector, last redacted position)
|
||||||
|
|(mut acc, last_end), range| {
|
||||||
|
acc.push(Range {
|
||||||
|
start: last_end,
|
||||||
|
end: range.start,
|
||||||
|
});
|
||||||
|
(acc, range.end)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if !last_part.is_empty() {
|
|
||||||
parts.push(last_part);
|
|
||||||
}
|
|
||||||
parts
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redactions_in_red(text: String) -> Html {
|
// interweave the redacted and non-redacted ranges
|
||||||
let parts = split_text(text);
|
let all_ranges = non_redacted_ranges
|
||||||
|
.into_iter()
|
||||||
//color redacted parts in red
|
.zip(redacted_ranges.iter())
|
||||||
let html: Html = parts
|
.flat_map(|(non_redacted, redacted)| {
|
||||||
.iter()
|
vec![
|
||||||
.map(|part| match part {
|
(non_redacted.start, non_redacted.end, false),
|
||||||
x if x.starts_with(REDACTED_CHAR) => Html::from_html_unchecked(AttrValue::from(
|
(redacted.start, redacted.end, true),
|
||||||
format!("<span style=\"color:red;\">{}</span>", x),
|
]
|
||||||
)),
|
|
||||||
_ => Html::from(part),
|
|
||||||
})
|
})
|
||||||
.collect();
|
.chain(std::iter::once((
|
||||||
|
last_redacted_position,
|
||||||
|
bytes.len(),
|
||||||
|
false,
|
||||||
|
))); // Handle remaining non-redacted part
|
||||||
|
|
||||||
html
|
let html_nodes = all_ranges
|
||||||
|
.map(|(start, end, is_redacted)| {
|
||||||
|
if is_redacted {
|
||||||
|
Html::from_html_unchecked(AttrValue::from(format!(
|
||||||
|
"<span style=\"color:red;\">{}</span>",
|
||||||
|
get_redacted_string(redacted_char, end - start)
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Html::from(String::from_utf8_lossy(&bytes[start..end]))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
{ for html_nodes }
|
||||||
|
</>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
pub fn RedactedBytesComponent(props: &Props) -> Html {
|
pub fn RedactedBytesComponent(props: &Props) -> Html {
|
||||||
let Props { direction, bytes } = props;
|
let Props {
|
||||||
|
direction,
|
||||||
let redacted_transcript = bytes.replace('\0', REDACTED_CHAR.to_string().as_str());
|
redacted_char,
|
||||||
|
bytes,
|
||||||
|
redacted_ranges,
|
||||||
|
} = props;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<details class="p-4 w-5/6" open={true}>
|
<details class="p-4 w-5/6" open={true}>
|
||||||
<summary><b>{"Bytes "}{direction}{": " }</b></summary>
|
<summary><b>{"Bytes "}{direction}{": " }</b></summary>
|
||||||
<div class="bg-black text-white p-4 rounded-md overflow-x-auto">
|
<div class="bg-black text-white p-4 rounded-md overflow-x-auto">
|
||||||
<pre>{redactions_in_red(redacted_transcript)}</pre>
|
<pre>{redactions_in_red(bytes, redacted_ranges, redacted_char)}</pre>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn redacted(length: usize) -> String {
|
|
||||||
'█'.to_string().repeat(length)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_split_text_starts_with_redacted() {
|
|
||||||
let foobar = String::from("foobar");
|
|
||||||
let text = format!("{}{}", redacted(8), &foobar);
|
|
||||||
let result1 = split_text(text.to_owned());
|
|
||||||
assert_eq!(result1, vec![redacted(8), foobar]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_split_text_no_redactions() {
|
|
||||||
let text = "NoRedactedParts";
|
|
||||||
let result = split_text(text.to_owned());
|
|
||||||
assert_eq!(result, vec![text]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_split_text_ends_with_redaction() {
|
|
||||||
let foobar = String::from("foobar");
|
|
||||||
let text = format!("{}{}", &foobar, redacted(8));
|
|
||||||
let result = split_text(text);
|
|
||||||
assert_eq!(result, vec![foobar, redacted(8)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_split_text_multiple() {
|
|
||||||
let text = format!("foobar {} test {} end", redacted(8), redacted(1));
|
|
||||||
let result = split_text(text);
|
|
||||||
assert_eq!(
|
|
||||||
result,
|
|
||||||
vec![
|
|
||||||
"foobar ",
|
|
||||||
redacted(8).as_str(),
|
|
||||||
" test ",
|
|
||||||
redacted(1).as_str(),
|
|
||||||
" end"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_split_text_empty() {
|
|
||||||
let result5 = split_text(String::from(""));
|
|
||||||
assert_eq!(result5, Vec::<String>::new());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
extern crate base64;
|
extern crate base64;
|
||||||
|
use std::ops::Range;
|
||||||
use std::str;
|
use std::str;
|
||||||
use web_time::Duration;
|
use web_time::Duration;
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ use crate::components::content_iframe::ContentIFrame;
|
|||||||
use crate::components::redacted_bytes_component::Direction;
|
use crate::components::redacted_bytes_component::Direction;
|
||||||
use crate::components::redacted_bytes_component::RedactedBytesComponent;
|
use crate::components::redacted_bytes_component::RedactedBytesComponent;
|
||||||
|
|
||||||
|
const REDACTED_CHAR: char = '█'; // 'X'. '🙈';
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct Props {
|
pub struct Props {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -85,9 +88,11 @@ pub fn ViewFile(props: &Props) -> Html {
|
|||||||
sent.set_redacted(b'\0');
|
sent.set_redacted(b'\0');
|
||||||
recv.set_redacted(b'\0');
|
recv.set_redacted(b'\0');
|
||||||
|
|
||||||
let bytes_send = String::from_utf8(sent.data().to_vec()).unwrap();
|
let redacted_ranges_send: Vec<Range<usize>> =
|
||||||
|
sent.redacted().clone().iter_ranges().collect();
|
||||||
let bytes_received = String::from_utf8(recv.data().to_vec()).unwrap();
|
let bytes_recv = String::from_utf8(recv.data().to_vec()).unwrap();
|
||||||
|
let redacted_ranges_recv: Vec<Range<usize>> =
|
||||||
|
recv.redacted().clone().iter_ranges().collect();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="p-4 flex flex-col justify-center items-center w-full">
|
<div class="p-4 flex flex-col justify-center items-center w-full">
|
||||||
@@ -106,11 +111,12 @@ pub fn ViewFile(props: &Props) -> Html {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RedactedBytesComponent direction={Direction::Send} bytes={bytes_send} />
|
<RedactedBytesComponent direction={Direction::Send} redacted_char={REDACTED_CHAR} bytes={sent.data().to_vec()} redacted_ranges={redacted_ranges_send} />
|
||||||
|
|
||||||
<ContentIFrame bytes={bytes_received.clone()} />
|
<ContentIFrame bytes={bytes_recv.clone()} />
|
||||||
|
|
||||||
|
<RedactedBytesComponent direction={Direction::Received} redacted_char={REDACTED_CHAR} bytes={recv.data().to_vec()} redacted_ranges={redacted_ranges_recv} />
|
||||||
|
|
||||||
<RedactedBytesComponent direction={Direction::Received} bytes={bytes_received} />
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user