Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/chatd/src/adapters/tiny.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ pub fn prove_legacy_saved_speakers() -> Result<(), String> {
messages::prove_legacy_saved_speakers()
}

/// The lab's keycap-reaction proof. See `messages::prove_keycap_reaction`.
pub fn prove_keycap_reaction() -> Result<(), String> {
messages::prove_keycap_reaction()
}

/// The lab's cache-open-without-device proof. See `messages::prove_cache_open_without_device`.
pub fn prove_cache_open_without_device() -> Result<(), String> {
messages::prove_cache_open_without_device()
Expand Down
38 changes: 35 additions & 3 deletions crates/chatd/src/adapters/tiny/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,21 @@ pub(super) fn prove_sealed_preview() -> Result<(), String> {
}
}

/// The lab's keycap-reaction proof. A keycap is taken as a reaction, and a bare digit still is not.
pub(super) fn prove_keycap_reaction() -> Result<(), String> {
for keycap in ["1\u{FE0F}\u{20E3}", "0\u{20E3}", "#\u{FE0F}\u{20E3}"] {
if !unicode_emoji(keycap) {
return Err(format!("the keycap {keycap:?} was refused as a reaction"));
}
}
for not_one in ["1", "10\u{FE0F}\u{20E3}", "1\u{FE0F}", ":one:"] {
if unicode_emoji(not_one) {
return Err(format!("{not_one:?} was taken as a reaction"));
}
}
Ok(())
}

/// The lab's sealed-preview-attachments proof. A wiped sqlite row still restores a captionless
/// image and a captioned attachment from the authenticated sealed cache. The lab sets
/// `TINYCLIENT_PROVE=sealed-preview-attachments`.
Expand Down Expand Up @@ -1887,14 +1902,17 @@ fn media_kind(kind: &str, mime: &str) -> MediaKind {
}
}

/// Unicode emoji only. A shortcode, a word, or a path is not one.
/// Unicode emoji only. A shortcode, a word, or a path is not one. A keycap is one, though it
/// starts with an ASCII digit (1️⃣ is `1`, U+FE0F, U+20E3), and tinyserver takes it.
fn unicode_emoji(emoji: &str) -> bool {
let mut textual = false;
if emoji.is_empty() {
return false;
}
for ch in emoji.chars() {
if ch.is_ascii_alphanumeric() || ch == ':' || ch == '/' {
for (at, ch) in emoji.char_indices() {
let after = &emoji[at + ch.len_utf8()..];
let keycap = ch.is_ascii_digit() && (after.starts_with('\u{20E3}') || after.starts_with("\u{FE0F}\u{20E3}"));
if !keycap && (ch.is_ascii_alphanumeric() || ch == ':' || ch == '/') {
return false;
}
if !ch.is_ascii() {
Expand Down Expand Up @@ -2881,6 +2899,20 @@ mod tests {
assert!(!unicode_emoji(""));
}

/// A keycap reaction (1️⃣) was refused when sent and dropped when another app had put one on
/// a message, because it starts with an ASCII digit.
#[test]
fn a_keycap_is_a_reaction_and_a_bare_digit_is_not() {
assert!(unicode_emoji("1\u{FE0F}\u{20E3}"));
assert!(unicode_emoji("7\u{20E3}"), "a keycap without its variation selector");
assert!(unicode_emoji("*\u{FE0F}\u{20E3}"));
assert!(!unicode_emoji("1"));
assert!(!unicode_emoji("1\u{FE0F}"), "a digit and a variation selector are not a keycap");
assert!(!unicode_emoji("10\u{FE0F}\u{20E3}"), "only the digit right before the keycap mark is part of it");
assert!(!unicode_emoji("a\u{20E3}"));
assert!(super::prove_keycap_reaction().is_ok());
}

#[test]
fn a_new_direct_event_records_the_peer_on_either_side_before_the_first_message() {
for (low, high) in [(ME, PEER), (PEER, ME)] {
Expand Down
9 changes: 9 additions & 0 deletions crates/chatd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ fn main() -> io::Result<()> {
eprintln!("prove-pending-message-identity:0");
return Ok(());
}
// The lab's keycap-reaction driver. A build without this returns to the server below and stays up.
if std::env::var("TINYCLIENT_PROVE").ok().as_deref() == Some("keycap-reaction") {
if let Err(err) = chatd::adapters::tiny::prove_keycap_reaction() {
eprintln!("keycap-reaction: {err}");
std::process::exit(1);
}
eprintln!("prove-exit:0");
return Ok(());
}
let _log = protocol::log_policy::install("chatd", protocol::log_policy::LogPolicy::from_env());
protocol::crash::install_panic_hook("chatd");
let dir = protocol::support_dir();
Expand Down
10 changes: 10 additions & 0 deletions e2e/scenarios/keycap-reaction/drive.linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
# keycap-reaction on Linux. The lab runs this with bash while ffmpeg films the display.
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
if command -v python3 >/dev/null 2>&1; then
exec python3 "$HERE/drive.py"
fi
echo "python3 is not on PATH" >&2
exit 1

10 changes: 10 additions & 0 deletions e2e/scenarios/keycap-reaction/drive.macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
# keycap-reaction on macOS. The lab runs this with bash while the helper films the app window.
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
if command -v python3 >/dev/null 2>&1; then
exec python3 "$HERE/drive.py"
fi
echo "python3 is not on PATH" >&2
exit 1

86 changes: 86 additions & 0 deletions e2e/scenarios/keycap-reaction/drive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""A keycap emoji such as 1️⃣ is taken as a Tiny reaction.

Starts this build's chatd with TINYCLIENT_PROVE=keycap-reaction. That build checks the reaction
filter the Tiny adapter applies to sent and received reactions, and exits 0 only when keycaps pass
and a bare digit does not. A build from before the fix ignores the variable and keeps serving, so
this driver fails there.

Env from the lab: E2E_OUT, E2E_T0, E2E_APP_DIR.
"""

import os
import subprocess
import sys
import time

OUT = os.environ.get("E2E_OUT", "")
T0 = float(os.environ.get("E2E_T0") or time.time())
APP = os.environ.get("E2E_APP_DIR", "")

FAILED = False
STEPS = ""


def now():
return time.time() - T0


def step(words, ok):
global FAILED
if not ok:
FAILED = True
line = '{"t": %s, "step": "%s", "ok": %s}' % (round(now(), 1), words.replace('"', "'"), "true" if ok else "false")
print(("[ok] " if ok else "[FAILED] ") + words, flush=True)
if STEPS:
with open(STEPS, "a", encoding="utf-8") as handle:
handle.write(line + "\n")


def chatd_bin(app_dir):
if sys.platform == "win32":
return os.path.join(app_dir, "chatd.exe")
if sys.platform == "darwin" and app_dir.endswith(".app"):
return os.path.join(app_dir, "Contents", "MacOS", "chatd")
return os.path.join(app_dir, "chatd")


def main():
global STEPS
if not OUT or not APP:
print("E2E_OUT and E2E_APP_DIR are required", file=sys.stderr)
return 1
os.makedirs(OUT, exist_ok=True)
STEPS = os.path.join(OUT, "steps.jsonl")
open(STEPS, "w", encoding="utf-8").close()
binary = chatd_bin(APP)
if not os.path.isfile(binary):
step("chatd is in E2E_APP_DIR", False)
return 1

data = os.path.join(OUT, "data")
os.makedirs(data, exist_ok=True)
env = os.environ.copy()
env["TINYCLIENT_DIR"] = data
env["TINYCLIENT_PROVE"] = "keycap-reaction"
env["TINYCLIENT_LOG_LEVEL"] = "info"
kwargs = {"cwd": data, "env": env, "stdin": subprocess.DEVNULL}
if sys.platform == "win32":
kwargs["creationflags"] = 0x08000000
with open(os.path.join(OUT, "chatd.out.log"), "w", encoding="utf-8") as out, open(os.path.join(OUT, "chatd.err.log"), "w", encoding="utf-8") as err:
kwargs["stdout"] = out
kwargs["stderr"] = err
proc = subprocess.Popen([binary], **kwargs)
try:
code = proc.wait(timeout=45)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=5)
code = None
step("a keycap emoji is taken as a reaction", code == 0)
return 1 if FAILED else 0


if __name__ == "__main__":
sys.exit(main())

18 changes: 18 additions & 0 deletions e2e/scenarios/keycap-reaction/drive.windows.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# keycap-reaction on Windows. The lab runs this in the console session it is already filming.
$ErrorActionPreference = 'Stop'
$drive = Join-Path $PSScriptRoot 'drive.py'
if (Get-Command py -ErrorAction SilentlyContinue) {
& py -3 $drive
exit $LASTEXITCODE
}
if (Get-Command python -ErrorAction SilentlyContinue) {
& python $drive
exit $LASTEXITCODE
}
if (Get-Command python3 -ErrorAction SilentlyContinue) {
& python3 $drive
exit $LASTEXITCODE
}
Write-Host 'python is not on PATH (tried py -3, python, python3)'
exit 1

12 changes: 12 additions & 0 deletions e2e/scenarios/keycap-reaction/scenario.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "keycap-reaction",
"title": "A keycap emoji such as 1️⃣ is taken as a Tiny reaction",
"platforms": ["windows", "linux", "macos"],
"not_applicable": {
"android": "The phone proof build does not run desktop chatd. The same reaction filter is what this desktop run checks.",
"ios": "The phone proof build does not run desktop chatd. The same reaction filter is what this desktop run checks."
},
"needs": [],
"release": false,
"max_still_seconds": 60
}
Loading