diff --git a/.gitignore b/.gitignore
index 9ecdc1e833..ce16eaafbf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,8 @@ tools/ido_recomp/* binary
ctx.c
graphs/
*.c.m2c
+tools/decomp-permuter/
+tools/mips_to_c/
# Assets
*.png
diff --git a/tools/decomp-permuter/.github/workflows/systray.yml b/tools/decomp-permuter/.github/workflows/systray.yml
deleted file mode 100644
index b815d64cc3..0000000000
--- a/tools/decomp-permuter/.github/workflows/systray.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-name: Systray
-
-on:
- push:
- branches: [ main ]
- paths:
- - 'src/net/cmd/systray/*'
- pull_request:
- branches: [ main ]
- paths:
- - 'src/net/cmd/systray/*'
-
-jobs:
-
- build:
- name: Build on ${{ matrix.os }}
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- include:
- - os: ubuntu-16.04
- binary: permuter-systray-linux
- - os: windows-latest
- binary: permuter-systray.exe
- - os: macos-latest
- binary: permuter-systray-macos
- steps:
- - uses: actions/checkout@main
-
- - name: Install gtk3
- if: ${{ matrix.os == 'ubuntu-16.04' }}
- run: sudo apt-get install libgtk-3-dev libappindicator3-dev
-
- - name: Setup Go environment
- uses: actions/setup-go@v2.1.3
-
- - name: Build
- run: go build -o ${{ matrix.binary }} -ldflags "-s -w" tray.go
- working-directory: src/net/cmd/systray/
-
- - name: Upload artifact
- uses: actions/upload-artifact@v2
- with:
- name: ${{ matrix.binary }}
- path: src/net/cmd/systray/${{ matrix.binary }}
diff --git a/tools/decomp-permuter/.gitignore b/tools/decomp-permuter/.gitignore
deleted file mode 100644
index 2dcae2143e..0000000000
--- a/tools/decomp-permuter/.gitignore
+++ /dev/null
@@ -1,11 +0,0 @@
-*.o
-*.s
-*.c
-*.py[cod]
-.mypy_cache/
-.cache/
-__pycache__/
-!test/*.c
-/nonmatchings
-.vscode/
-pah.conf
diff --git a/tools/decomp-permuter/.gitrepo b/tools/decomp-permuter/.gitrepo
deleted file mode 100644
index 270e3df564..0000000000
--- a/tools/decomp-permuter/.gitrepo
+++ /dev/null
@@ -1,12 +0,0 @@
-; DO NOT EDIT (unless you know what you are doing)
-;
-; This subdirectory is a git "subrepo", and this file is maintained by the
-; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
-;
-[subrepo]
- remote = https://github.com/simonlindholm/decomp-permuter.git
- branch = main
- commit = a20bac9422b6d8adbf7c06473c2ae3c3fee16be5
- parent = 2668eec556c01fa2f4c16a203c93c208dc03e639
- method = merge
- cmdver = 0.4.3
diff --git a/tools/decomp-permuter/.pre-commit-config.yaml b/tools/decomp-permuter/.pre-commit-config.yaml
deleted file mode 100644
index 6695f71ac9..0000000000
--- a/tools/decomp-permuter/.pre-commit-config.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-repos:
-- repo: https://github.com/psf/black
- rev: 20.8b1
- hooks:
- - id: black
- language_version: python3.6
diff --git a/tools/decomp-permuter/LICENSE b/tools/decomp-permuter/LICENSE
deleted file mode 100644
index ce6aab3a31..0000000000
--- a/tools/decomp-permuter/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2019 Simon Lindholm and contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/tools/decomp-permuter/README.md b/tools/decomp-permuter/README.md
deleted file mode 100644
index eb31c9d652..0000000000
--- a/tools/decomp-permuter/README.md
+++ /dev/null
@@ -1,120 +0,0 @@
-# Decomp permuter
-
-Automatically permutes C files to better match a target binary. The permuter has two modes of operation:
-- Random: purely at random, introduce temporary variables for values, change types, put statements on the same line...
-- Manual: test all combinations of user-specified variations, using macros like `PERM_GENERAL(a = b ? c : d;, if (b) a = c; else a = d;)` to try both specified alternatives.
-
-The modes can also be combined, by using the `PERM_RANDOMIZE` macro.
-
-[
](https://asciinema.org/a/232846)
-
-The main target for the tool is MIPS code compiled by old compilers (IDO, possibly GCC).
-Getting it to work on other architectures shouldn't be too hard, however.
-https://github.com/laqieer/decomp-permuter-arm has an ARM port.
-
-## Usage
-
-`./permuter.py directory/` runs the permuter; see below for the meaning of the directory.
-Pass `-h` to see possible flags. `-j` is suggested (enables multi-threaded mode).
-
-You'll first need to install a couple of prerequisites: `python3 -m pip install pycparser pynacl toml` (also `dataclasses` if on Python 3.6 or below)
-
-The permuter expects as input one or more directory containing:
- - a .c file with a single function,
- - a .o file to match,
- - a .sh file that compiles the .c file.
-
-For projects with a properly configured makefile, you should be able to set these up by running
-```
-./import.py
-```
-where file.c contains the function to be permuted, and file.s is its assembly in a self-contained file.
-Otherwise, see USAGE.md for more details.
-
-For projects using Ninja instead of Make, add a `permuter_settings.toml` in the root or `tools/` directory of the project:
-```toml
-build_system = "ninja"
-```
-Then `import.py` should work as expected if `build.ninja` is at the root of the project.
-
-The .c file may be modified with any of the following macros which affect manual permutation:
-
-- `PERM_GENERAL(a, b, ...)` expands to any of `a`, `b`, ...
-- `PERM_VAR(a, b)` sets the meta-variable `a` to `b`, `PERM_VAR(a)` expands to the meta-variable `a`.
-- `PERM_RANDOMIZE(code)` expands to `code`, but allows randomization within that region. Multiple regions may be specified.
-- `PERM_LINESWAP(lines)` expands to a permutation of the ordered set of non-whitespace lines (split by `\n`). Each line must contain zero or more complete C statements. (For incomplete statements use `PERM_LINESWAP_TEXT`, which is slower because it has to repeatedly parse C code.)
-- `PERM_INT(lo, hi)` expands to an integer between `lo` and `hi` (which must be constants).
-- `PERM_IGNORE(code)` expands to `code`, without passing it through the C parser library (pycparser)/randomizer. This can be used to avoid parse errors for non-standard C, e.g. `asm` blocks.
-- `PERM_PRETEND(code)` expands to `code` for the purpose of the C parser/randomizer, but gets removed afterwards. This can be used together with `PERM_IGNORE` to enable the permuter to deal with input it isn't designed for (e.g. inline functions, C++, non-code).
-- `PERM_ONCE([key,] code)` expands to either `code` or to nothing, such that each unique key gets expanded exactly once. `key` defaults to `code`. For example, `PERM_ONCE(a;) b; PERM_ONCE(a;)` expands to either `a; b;` or `b; a;`.
-
-Arguments are split by a commas, exluding commas inside parenthesis. `(,)` is a special escape sequence that resolves to `,`.
-
-Nested macros are allowed, so e.g.
-```
-PERM_VAR(delayed, )
-PERM_GENERAL(stmt;, PERM_VAR(delayed, stmt;))
-...
-PERM_VAR(delayed)
-```
-is an alternative way of writing `PERM_ONCE`.
-
-## permuter@home
-
-The permuter supports a distributed mode, where people can donate processor power to your permuter runs to speed them up.
-To use this, pass `-J` when running `permuter.py` and follow the instructions.
-You will need to be granted access by someone who is already connected to a permuter network.
-
-To allow others to use your computer for permuter runs, do the following:
-
-- install Docker (used for sandboxing and to ensure a consistent environment)
-- if on Linux, add yourself to the Docker group: `sudo usermod -aG docker $USER`
-- install required packages: `python3 -m pip install docker`
-- open a terminal, and run `./pah.py run-server` to start the server.
- There are a few required arguments (e.g. how many cores to use), see `--help` for more details.
-
-Please be aware that being in the Docker group implies (password-less) sudo rights.
-You can avoid that for your personal account by running the permuter under a separate user.
-Unfortunately, there is currently no way to run a sandboxed permuter server without sudo rights. 😢
-
-Anyone who is granted access to permuter@home can run a server.
-
-To set up a new permuter network, see [src/net/controller/README.md](./src/net/controller/README.md).
-
-## FAQ
-
-**What do the scores mean?** The scores are computed by taking diffs of objdump'd .o
-files, and giving different penalties for lines that are the same/use the same
-instruction/are reordered/don't match at all. 0 means the function matches fully.
-Stack positions are ignored unless --stack-diffs is passed (but beware that the
-permuter is currently quite bad at resolving stack differences). For more details,
-see scorer.py. It's far from a perfect system, and should probably be tweaked to
-look at e.g. the register diff graph.
-
-**What sort of non-matchings are the permuter good at?** It's generally best towards
-the end, when mostly regalloc changes remain. If there are reorderings or functional
-changes, it's often easy to resolve those by hand, and neither the scorer nor the
-randomizer tends to play well with them.
-
-**Should I use this instead of trying to match code by hand?** No, but it can be a good
-complement. PERM macros can be used to quickly test lots of variations of a function at
-once, in cases where there are interactions between several parts of a function.
-The randomization mode often finds lots of nonsensical changes that improve regalloc
-"by accident"; it's up to you to pick out the ones that look sensible. If none do,
-it can still be useful to know which parts of the function need to be changed to get the
-code nearer to matching. Having made one of the improvements, and the function can then be
-permuted again, to find further possible improvements.
-
-## Helping out
-
-There's tons of room for helping out with the permuter!
-Many more randomization passes could be added, the scoring function is far from optimal,
-the permuter could be made easier to use, etc. etc. The GitHub Issues list has some ideas.
-
-Ideally, `mypy permuter.py` and `./run-tests.sh` should succeed with no errors, and files
-formatted with `black`. To setup a pre-commit hook for black, run:
-```
-pip install pre-commit black
-pre-commit install
-```
-PRs that skip this are still welcome, however.
diff --git a/tools/decomp-permuter/USAGE.md b/tools/decomp-permuter/USAGE.md
deleted file mode 100644
index d73b3a8980..0000000000
--- a/tools/decomp-permuter/USAGE.md
+++ /dev/null
@@ -1,25 +0,0 @@
-This file describes how to manually set up a directory for use with the permuter.
-**You probably don't need to do this!** In normal circumstances, `./import.py`
-does all this for you. See README.md for more details.
-
-* create a directory that will contain all of the input files for the invokation
-* put a compile command into `/compile.sh` (see e.g. `compile_example.sh`; it will be invoked as `./compile.sh input.c -o output.o`)
-* `gcc -E -P -I header_dir -D'__attribute__(x)=' orig_c_file.c > /base.c`
-* `python3 strip_other_fns.py /base.c func_name`
-* put asm for `func_name` into `/target.s`, with the following header:
-
-```asm
-.set noat
-.set noreorder
-.set gp=64
-.macro glabel label
- .global \label
- .type \label, @function
- \label:
-.endm
-```
-* `mips-linux-gnu-as -march=vr4300 -mabi=32 /target.s -o /target.o`
-* optional sanity checks:
- - `/compile.sh /base.c -o /base.o`
- - `./diff.sh /target.o /base.o`
-* `./permuter.py `
diff --git a/tools/decomp-permuter/compile_example.sh b/tools/decomp-permuter/compile_example.sh
deleted file mode 100755
index f87bdfa18d..0000000000
--- a/tools/decomp-permuter/compile_example.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-mips-linux-gnu-gcc -O2 "$@"
diff --git a/tools/decomp-permuter/diff.sh b/tools/decomp-permuter/diff.sh
deleted file mode 100755
index 20ae8bba08..0000000000
--- a/tools/decomp-permuter/diff.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-
-if [[ $# < 2 ]]; then
- echo "Usage: $0 orig.o new.o [flags]"
- exit 1
-fi
-
-if [ ! -f $1 -o ! -f $2 ]; then
- echo Source files not readable
- exit 1
-fi
-
-INPUT1="$1"
-INPUT2="$2"
-shift
-shift
-wdiff -n <(python3 ./src/objdump.py "$INPUT1" "$@") <(python3 ./src/objdump.py "$INPUT2" "$@") | colordiff | less -Ric
diff --git a/tools/decomp-permuter/import.py b/tools/decomp-permuter/import.py
deleted file mode 100755
index 9a314621cf..0000000000
--- a/tools/decomp-permuter/import.py
+++ /dev/null
@@ -1,808 +0,0 @@
-#!/usr/bin/env python3
-# usage: ./import.py path/to/file.c path/to/asm.s [make flags]
-import argparse
-from collections import defaultdict
-import json
-import os
-import platform
-import re
-import shlex
-import shutil
-import subprocess
-import sys
-import toml
-from typing import Callable, Dict, List, Match, Mapping, Optional, Pattern, Set, Tuple
-import urllib.request
-import urllib.parse
-
-from src import ast_util
-from src.compiler import Compiler
-from src.error import CandidateConstructionFailure
-
-is_macos = platform.system() == "Darwin"
-
-
-def homebrew_gcc_cpp() -> str:
- lookup_paths = ["/usr/local/bin", "/opt/homebrew/bin"]
-
- for lookup_path in lookup_paths:
- try:
- return max(f for f in os.listdir(lookup_path) if f.startswith("cpp-"))
- except ValueError:
- pass
-
- print(
- "Error while looking up in " + ":".join(lookup_paths) + " for cpp- executable"
- )
- sys.exit(1)
-
-
-cpp_cmd = homebrew_gcc_cpp() if is_macos else "cpp"
-make_cmd = "gmake" if is_macos else "make"
-
-ASM_PRELUDE: str = """
-.set noat
-.set noreorder
-.set gp=64
-.macro glabel label
- .global \label
- .type \label, @function
- \label:
-.endm
-"""
-
-DEFAULT_AS_CMDLINE: List[str] = ["mips-linux-gnu-as", "-march=vr4300", "-mabi=32"]
-
-CPP: List[str] = [cpp_cmd, "-P", "-undef"]
-
-STUB_FN_MACROS: List[str] = [
- "-D_Static_assert(x, y)=",
- "-D__attribute__(x)=",
- "-DGLOBAL_ASM(...)=",
-]
-
-SETTINGS_FILES = ["permuter_settings.toml", "tools/permuter_settings.toml"]
-
-
-def formatcmd(cmdline: List[str]) -> str:
- return " ".join(shlex.quote(arg) for arg in cmdline)
-
-
-def parse_asm(asm_file: str) -> Tuple[str, str]:
- func_name = None
- asm_lines = []
- try:
- with open(asm_file, encoding="utf-8") as f:
- cur_section = ".text"
- for line in f:
- if line.strip().startswith(".section"):
- cur_section = line.split()[1]
- elif line.strip() in [
- ".text",
- ".rdata",
- ".rodata",
- ".late_rodata",
- ".bss",
- ".data",
- ]:
- cur_section = line.strip()
- if cur_section == ".text":
- if func_name is None and line.strip().startswith("glabel "):
- func_name = line.split()[1]
- asm_lines.append(line)
- except OSError as e:
- print("Could not open assembly file:", e, file=sys.stderr)
- sys.exit(1)
-
- if func_name is None:
- print(
- "Missing function name in assembly file! The file should start with 'glabel function_name'.",
- file=sys.stderr,
- )
- sys.exit(1)
-
- if not re.fullmatch(r"[a-zA-Z0-9_$]+", func_name):
- print(f"Bad function name: {func_name}", file=sys.stderr)
- sys.exit(1)
-
- return func_name, "".join(asm_lines)
-
-
-def create_directory(func_name: str) -> str:
- os.makedirs(f"nonmatchings/", exist_ok=True)
- ctr = 0
- while True:
- ctr += 1
- dirname = f"{func_name}-{ctr}" if ctr > 1 else func_name
- dirname = f"nonmatchings/{dirname}"
- try:
- os.mkdir(dirname)
- return dirname
- except FileExistsError:
- pass
-
-
-def find_root_dir(filename: str, pattern: List[str]) -> Optional[str]:
- old_dirname = None
- dirname = os.path.abspath(os.path.dirname(filename))
-
- while dirname and (not old_dirname or len(dirname) < len(old_dirname)):
- for fname in pattern:
- if os.path.isfile(os.path.join(dirname, fname)):
- return dirname
- old_dirname = dirname
- dirname = os.path.dirname(dirname)
-
- return None
-
-
-def fixup_build_command(
- parts: List[str], ignore_part: str
-) -> Tuple[List[str], Optional[List[str]]]:
- res = []
- skip_count = 0
- assembler = None
- for part in parts:
- if skip_count > 0:
- skip_count -= 1
- continue
- if part in ["-MF", "-o"]:
- skip_count = 1
- continue
- if part == ignore_part:
- continue
- res.append(part)
-
- try:
- ind0 = min(
- i
- for i, arg in enumerate(res)
- if any(
- cmd in arg
- for cmd in ["asm_processor", "asm-processor", "preprocess.py"]
- )
- )
- ind1 = res.index("--", ind0 + 1)
- ind2 = res.index("--", ind1 + 1)
- assembler = res[ind1 + 1 : ind2]
- res = res[ind0 + 1 : ind1] + res[ind2 + 1 :]
- except ValueError:
- pass
-
- return res, assembler
-
-
-def find_build_command_line(
- root_dir: str, c_file: str, make_flags: List[str], build_system: str
-) -> Tuple[List[str], List[str]]:
- if build_system == "make":
- build_invocation = [
- make_cmd,
- "--always-make",
- "--dry-run",
- "--debug=j",
- "PERMUTER=1",
- ] + make_flags
- elif build_system == "ninja":
- build_invocation = ["ninja", "-t", "commands"] + make_flags
- else:
- print("Unknown build system '" + build_system + "'.")
- sys.exit(1)
-
- rel_c_file = os.path.relpath(c_file, root_dir)
- debug_output = (
- subprocess.check_output(build_invocation, cwd=root_dir)
- .decode("utf-8")
- .split("\n")
- )
-
- output = []
- close_match = False
-
- assembler = DEFAULT_AS_CMDLINE
- for line in debug_output:
- while "//" in line:
- line = line.replace("//", "/")
- while "/./" in line:
- line = line.replace("/./", "/")
- if rel_c_file not in line:
- continue
-
- close_match = True
- parts = shlex.split(line)
-
- # extract actual command from 'bash -c "..."'
- if parts[0] == "bash" and "-c" in parts:
- for part in parts:
- if rel_c_file in part:
- parts = shlex.split(part)
- break
-
- if rel_c_file not in parts:
- continue
- if "-o" not in parts:
- continue
- if "-fsyntax-only" in parts:
- continue
- cmdline, asmproc_assembler = fixup_build_command(parts, rel_c_file)
- if asmproc_assembler:
- assembler = asmproc_assembler
- output.append(cmdline)
-
- if not output:
- close_extra = (
- "\n(Found one possible candidate, but didn't match due to "
- "either spaces in paths, having -fsyntax-only, or missing an -o flag.)"
- if close_match
- else ""
- )
- print(
- "Failed to find compile command from build script output. "
- f"Please ensure running '{' '.join(build_invocation)}' "
- f"contains a line with the string '{rel_c_file}'.{close_extra}",
- file=sys.stderr,
- )
- sys.exit(1)
-
- if len(output) > 1:
- output_lines = "\n".join(map(formatcmd, output))
- print(
- f"Error: found multiple compile commands for {rel_c_file}:\n{output_lines}\n"
- f"Please modify the build script such that '{' '.join(build_invocation)}' "
- "produces a single compile command.",
- file=sys.stderr,
- )
- sys.exit(1)
-
- return output[0], assembler
-
-
-PreserveMacros = Tuple[Pattern[str], Callable[[str], str]]
-
-
-def build_preserve_macros(
- cwd: str, preserve_regex: Optional[str], settings: Mapping[str, object]
-) -> Optional[PreserveMacros]:
-
- subdata = settings.get("preserve_macros", {})
- assert isinstance(subdata, dict)
- regexes = []
- for regex, value in subdata.items():
- assert isinstance(value, str)
- regexes.append((re.compile(f"^(?:{regex})$"), value))
-
- if preserve_regex == "" or (preserve_regex is None and not regexes):
- return None
-
- if preserve_regex is None:
- global_regex_text = "(?:" + ")|(?:".join(subdata.keys()) + ")"
- else:
- global_regex_text = preserve_regex
- global_regex = re.compile(f"^(?:{global_regex_text})$")
-
- def type_fn(macro: str) -> str:
- for regex, value in regexes:
- if regex.match(macro):
- return value
- return "int"
-
- return global_regex, type_fn
-
-
-def preprocess_c_with_macros(
- cpp_command: List[str], cwd: str, preserve_macros: PreserveMacros
-) -> Tuple[str, List[str]]:
- """Import C file, preserving function macros. Subroutine of import_c_file.
-
- Returns the source code and a list of preserved macros."""
-
- preserve_regex, preserve_type_fn = preserve_macros
-
- # Start by running 'cpp' in a mode that just processes ifdefs and includes.
- source = subprocess.check_output(
- cpp_command + ["-dD", "-fdirectives-only"], cwd=cwd, encoding="utf-8"
- )
-
- # Modify function macros that match preserved names so the preprocessor
- # doesn't touch them, and at the same time normalize their syntax. Some
- # of these instances may be in comments, but that's fine.
- def repl(match: Match[str]) -> str:
- name = match.group(1)
- after = "(" if match.group(2) == "(" else " "
- if preserve_regex.match(name):
- return f"_permuter define {name}{after}"
- else:
- return f"#define {name}{after}"
-
- source = re.sub(
- r"^\s*#\s*define\s+([a-zA-Z0-9_]+)([ \t\(]|$)",
- repl,
- source,
- flags=re.MULTILINE,
- )
-
- # Get rid of auto-inserted macros which the second cpp invocation will
- # warn about.
- source = re.sub(r"^#define __STDC_.*\n", "", source, flags=re.MULTILINE)
-
- # Now, run the preprocessor again for real.
- source = subprocess.check_output(
- CPP + STUB_FN_MACROS, cwd=cwd, encoding="utf-8", input=source
- )
-
- # Finally, find all function-like defines that we hid (some might have
- # been comments, so we couldn't do this before), and construct fake
- # function declarations for them in a specially demarcated section of
- # the file. When the compiler runs, this section will be replaced by
- # the real defines and the preprocessor invoked once more.
- late_defines = []
- lines = []
- graph = defaultdict(set)
- reg_token = re.compile(r"[a-zA-Z0-9_]+")
- for line in source.splitlines():
- is_macro = line.startswith("_permuter define ")
- params = []
- if is_macro:
- ind1 = line.find("(")
- ind2 = line.find(" ", len("_permuter define "))
- ind = min(ind1, ind2)
- if ind == -1:
- ind = len(line) if ind1 == ind2 == -1 else max(ind1, ind2)
- before = line[:ind]
- after = line[ind:]
- name = before.split()[2]
- late_defines.append((name, after))
- if after.startswith("("):
- params = [w.strip() for w in after[1 : after.find(")")].split(",")]
- else:
- lines.append(line)
- name = ""
- for m in reg_token.finditer(line):
- name2 = m.group(0)
- has_wildcard = False
- if is_macro and name2 not in params:
- wcbefore = line[: m.start()].rstrip().endswith("##")
- wcafter = line[m.end() :].lstrip().startswith("##")
- if wcbefore or wcafter:
- graph[name].add(name2 + "*")
- has_wildcard = True
- if not has_wildcard:
- graph[name].add(name2)
-
- # Prune away (recursively) unused macros, for cleanliness.
- used_anywhere = set()
- used_by_nonmacro = graph[""]
- queue = [""]
- while queue:
- name = queue.pop()
- if name not in used_anywhere:
- used_anywhere.add(name)
- if name.endswith("*"):
- wildcard = name[:-1]
- for name2 in graph:
- if wildcard in name2:
- queue.extend(graph[name2])
- else:
- queue.extend(graph[name])
-
- def get_decl(name: str, after: str) -> str:
- typ = preserve_type_fn(name)
- if after.startswith("("):
- return f"{typ} {name}();"
- else:
- return f"extern {typ} {name};"
-
- used_macros = [name for (name, after) in late_defines if name in used_by_nonmacro]
-
- return (
- "\n".join(
- ["#pragma _permuter latedefine start"]
- + [
- f"#pragma _permuter define {name}{after}"
- for (name, after) in late_defines
- if name in used_anywhere
- ]
- + [
- get_decl(name, after)
- for (name, after) in late_defines
- if name in used_by_nonmacro
- ]
- + ["#pragma _permuter latedefine end"]
- + lines
- + [""]
- ),
- used_macros,
- )
-
-
-def import_c_file(
- compiler: List[str],
- cwd: str,
- in_file: str,
- preserve_macros: Optional[PreserveMacros],
-) -> str:
- """Preprocess a C file into permuter-usable source.
-
- Prints preserved macros as a side effect.
-
- Returns source for base.c and compilable (macro-expanded) source."""
- in_file = os.path.relpath(in_file, cwd)
- include_next = 0
- cpp_command = CPP + [in_file, "-D__sgi", "-D_LANGUAGE_C", "-DNON_MATCHING"]
-
- for arg in compiler:
- if include_next > 0:
- include_next -= 1
- cpp_command.append(arg)
- continue
- if arg in ["-D", "-U", "-I"]:
- cpp_command.append(arg)
- include_next = 1
- continue
- if (
- arg.startswith("-D")
- or arg.startswith("-U")
- or arg.startswith("-I")
- or arg in ["-nostdinc"]
- ):
- cpp_command.append(arg)
-
- try:
- if preserve_macros is None:
- # Simple codepath, should work even if the more complex one breaks.
- source = subprocess.check_output(
- cpp_command + STUB_FN_MACROS, cwd=cwd, encoding="utf-8"
- )
- macros: List[str] = []
- else:
- source, macros = preprocess_c_with_macros(cpp_command, cwd, preserve_macros)
-
- except subprocess.CalledProcessError as e:
- print(
- "Failed to preprocess input file, when running command:\n"
- + formatcmd(e.cmd),
- file=sys.stderr,
- )
- sys.exit(1)
-
- if macros:
- macro_str = "macros: " + ", ".join(macros)
- else:
- macro_str = "no macros"
- print(f"Preserving {macro_str}. Use --preserve-macros='' to override.")
-
- return source
-
-
-def prune_source(
- source: str, should_prune: bool, func_name: str
-) -> Tuple[str, Optional[str]]:
- """Normalize the source by round-tripping it through pycparser, and
- optionally reduce it to a smaller version that includes only the imported
- function and functions/struct/variables that it uses.
-
- Returns (source, compilable_source)."""
- try:
- ast = ast_util.parse_c(source, from_import=True)
- orig_fn, _ = ast_util.extract_fn(ast, func_name)
- if should_prune:
- try:
- ast_util.prune_ast(orig_fn, ast)
- source = ast_util.to_c_raw(ast)
- except Exception:
- print(
- "Source minimization failed! "
- "You could try --no-prune as a workaround."
- )
- raise
- return source, ast_util.to_c(ast, from_import=True)
- except CandidateConstructionFailure as e:
- print(e.message)
- if should_prune and "PERM_" in source:
- print(
- "Please put in PERM macros after import, otherwise source "
- "minimization does not work."
- )
- else:
- print("Proceeding anyway, but expect errors when permuting!")
- return source, None
-
-
-def prune_and_separate_context(
- source: str, should_prune: bool, func_name: str
-) -> Tuple[str, str]:
- """Normalize the source by round-tripping it through pycparser, optionally
- reduce it to a smaller version that includes only the imported function and
- functions/struct/variables that it uses, and split the result into source
- for the function itself, and the rest of the file (the "context").
-
- Returns (source, context)."""
- try:
- ast = ast_util.parse_c(source, from_import=True)
- orig_fn, ind = ast_util.extract_fn(ast, func_name)
- if should_prune:
- try:
- ind = ast_util.prune_ast(orig_fn, ast)
- except Exception:
- print(
- "Source minimization failed! "
- "You could try --no-prune as a workaround."
- )
- raise
- del ast.ext[ind]
- source = ast_util.to_c(orig_fn, from_import=True)
- context = ast_util.to_c(ast, from_import=True)
- return source, context
- except CandidateConstructionFailure as e:
- print(e.message)
- print("Unable to split context from source.")
- print("Proceeding anyway, but expect compile errors!")
- return ast_util.process_pragmas(source), ""
-
-
-def get_decompme_compiler_name(
- compiler: List[str], settings: Mapping[str, object], api_base: str
-) -> str:
- decompme_settings = settings.get("decompme", {})
- assert isinstance(decompme_settings, dict)
- compiler_mappings = decompme_settings.get("compilers", {})
- assert isinstance(compiler_mappings, dict)
-
- compiler_path = compiler[0]
-
- for path, compiler_name in compiler_mappings.items():
- assert isinstance(compiler_name, str)
- if path == compiler_path:
- return compiler_name
-
- try:
- with urllib.request.urlopen(f"{api_base}/api/compilers") as f:
- json_data = json.load(f)
- available = json_data["compiler_ids"]
- if not isinstance(available, list):
- raise Exception("compiler_ids must be a list")
- if not all(isinstance(name, str) for name in available):
- raise Exception("compiler_ids must be a list of strings")
- except Exception as e:
- print(f"Failed to request available compilers from decomp.me:\n{e}")
-
- print()
- print(
- f'Unable to map compiler path "{compiler_path}" to something '
- "decomp.me understands."
- )
- trail = "permuter_settings.toml, where ... is one of: " + ", ".join(available)
- if compiler_mappings:
- print(
- "Please add an entry:\n\n"
- f'"{compiler_path}" = "..."\n\n'
- f"to the [decompme.compilers] section of {trail}"
- )
- else:
- print(
- "Please add an section:\n\n"
- "[decompme.compilers]\n"
- f'"{compiler_path}" = "..."\n\n'
- f"to {trail}"
- )
- sys.exit(1)
-
-
-def finalize_compile_command(cmdline: List[str]) -> str:
- quoted = [arg if arg == "|" else shlex.quote(arg) for arg in cmdline]
- ind = (quoted + ["|"]).index("|")
- return " ".join(quoted[:ind] + ['"$INPUT"'] + quoted[ind:] + ["-o", '"$OUTPUT"'])
-
-
-def get_compiler_flags(cmdline: List[str]) -> str:
- flags = [b for a, b in zip(cmdline, cmdline[1:]) if a != "|" and b != "|"]
- return " ".join(shlex.quote(flag) for flag in flags)
-
-
-def write_compile_command(compiler: List[str], cwd: str, out_file: str) -> None:
-
- with open(out_file, "w", encoding="utf-8") as f:
- f.write("#!/usr/bin/env bash\n")
- f.write('INPUT="$(realpath "$1")"\n')
- f.write('OUTPUT="$(realpath "$3")"\n')
- f.write(f"cd {shlex.quote(cwd)}\n")
- f.write(finalize_compile_command(compiler))
- os.chmod(out_file, 0o755)
-
-
-def write_asm(asm_cont: str, out_file: str) -> None:
- with open(out_file, "w", encoding="utf-8") as f:
- f.write(ASM_PRELUDE)
- f.write(asm_cont)
-
-
-def compile_asm(assembler: List[str], cwd: str, in_file: str, out_file: str) -> None:
- in_file = os.path.abspath(in_file)
- out_file = os.path.abspath(out_file)
- cmdline = assembler + [in_file, "-o", out_file]
- try:
- subprocess.check_call(cmdline, cwd=cwd)
- except subprocess.CalledProcessError:
- print(
- f"Failed to assemble .s file, command line:\n{formatcmd(cmdline)}",
- file=sys.stderr,
- )
- sys.exit(1)
-
-
-def compile_base(compile_script: str, source: str, c_file: str, out_file: str) -> None:
- if "PERM_" in source:
- print(
- "Cannot test-compile imported code because it contains PERM macros. "
- "It is recommended to put in PERM macros after import."
- )
- return
- escaped_c_file = json.dumps(c_file)
- source = "#line 1 " + escaped_c_file + "\n" + source
- compiler = Compiler(compile_script, show_errors=True)
- o_file = compiler.compile(source)
- if o_file:
- shutil.move(o_file, out_file)
- else:
- print("Warning: failed to compile .c file.")
-
-
-def write_to_file(cont: str, filename: str) -> None:
- with open(filename, "w", encoding="utf-8") as f:
- f.write(cont)
-
-
-def main() -> None:
- parser = argparse.ArgumentParser(
- description="""Import a function for use with the permuter.
- Will create a new directory nonmatchings/-/."""
- )
- parser.add_argument(
- "c_file",
- help="""File containing the function.
- Assumes that the file can be built with 'make' to create an .o file.""",
- )
- parser.add_argument(
- "asm_file",
- help="""File containing assembly for the function.
- Must start with 'glabel ' and contain no other functions.""",
- )
- parser.add_argument(
- "make_flags",
- nargs="*",
- help="Arguments to pass to 'make'. PERMUTER=1 will always be passed.",
- )
- parser.add_argument(
- "--keep", action="store_true", help="Keep the directory on error."
- )
- settings_files = ", ".join(SETTINGS_FILES[:-1]) + " or " + SETTINGS_FILES[-1]
- parser.add_argument(
- "--preserve-macros",
- metavar="REGEX",
- dest="preserve_macros_regex",
- help=f"""Regex for which macros to preserve, or empty string for no macros.
- By default, this is read from {settings_files} in a parent directory of
- the imported file. Type information is also read from this file.""",
- )
- parser.add_argument(
- "--no-prune",
- dest="prune",
- action="store_false",
- help="""Don't minimize the source to keep only the imported function and
- functions/struct/variables that it uses. Normally this behavior is
- useful to make the permuter faster, but in cases where unrelated code
- affects the generated assembly asm it can be necessary to turn off.
- Note that regardless of this setting the permuter always removes all
- other functions by replacing them with declarations.""",
- )
- parser.add_argument(
- "--decompme",
- dest="decompme",
- action="store_true",
- help="""Upload the function to decomp.me to share with other people,
- instead of importing.""",
- )
- args = parser.parse_args()
-
- root_dir = find_root_dir(
- args.c_file, SETTINGS_FILES + ["Makefile", "makefile", "build.ninja"]
- )
-
- if not root_dir:
- print(f"Can't find root dir of project!", file=sys.stderr)
- sys.exit(1)
-
- settings: Mapping[str, object] = {}
- for filename in SETTINGS_FILES:
- filename = os.path.join(root_dir, filename)
- if os.path.exists(filename):
- with open(filename) as f:
- settings = toml.load(f)
- break
-
- build_system = settings.get("build_system", "make")
- compiler = settings.get("compiler_command")
- assembler = settings.get("assembler_command")
- make_flags = args.make_flags
-
- func_name, asm_cont = parse_asm(args.asm_file)
- print(f"Function name: {func_name}")
-
- if compiler or assembler:
- assert isinstance(compiler, str)
- assert isinstance(assembler, str)
- assert settings.get("build_system") is None
-
- compiler = shlex.split(compiler)
- assembler = shlex.split(assembler)
- else:
- assert isinstance(build_system, str)
- compiler, assembler = find_build_command_line(
- root_dir, args.c_file, make_flags, build_system
- )
-
- print(f"Compiler: {formatcmd(compiler)} {{input}} -o {{output}}")
- print(f"Assembler: {formatcmd(assembler)} {{input}} -o {{output}}")
-
- preserve_macros = build_preserve_macros(
- root_dir, args.preserve_macros_regex, settings
- )
- source = import_c_file(compiler, root_dir, args.c_file, preserve_macros)
-
- if args.decompme:
- api_base = os.environ.get("DECOMPME_API_BASE", "https://decomp.me")
- compiler_name = get_decompme_compiler_name(compiler, settings, api_base)
- source, context = prune_and_separate_context(source, args.prune, func_name)
- print("Uploading...")
- try:
- post_data = urllib.parse.urlencode(
- {
- "target_asm": asm_cont,
- "context": context,
- "source_code": source,
- "compiler": compiler_name,
- "compiler_flags": get_compiler_flags(compiler),
- }
- ).encode("ascii")
- with urllib.request.urlopen(f"{api_base}/api/scratch", post_data) as f:
- resp = f.read()
- json_data: Dict[str, str] = json.loads(resp)
- if "slug" in json_data:
- slug = json_data["slug"]
- print(f"https://decomp.me/scratch/{slug}")
- else:
- error = json_data.get("error", resp)
- print(f"Server error: {error}")
- except Exception as e:
- print(e)
- return
-
- source, compilable_source = prune_source(source, args.prune, func_name)
-
- dirname = create_directory(func_name)
- base_c_file = f"{dirname}/base.c"
- base_o_file = f"{dirname}/base.o"
- target_s_file = f"{dirname}/target.s"
- target_o_file = f"{dirname}/target.o"
- compile_script = f"{dirname}/compile.sh"
- func_name_file = f"{dirname}/function.txt"
-
- try:
- write_to_file(source, base_c_file)
- write_to_file(func_name, func_name_file)
- write_compile_command(compiler, root_dir, compile_script)
- write_asm(asm_cont, target_s_file)
- compile_asm(assembler, root_dir, target_s_file, target_o_file)
- if compilable_source is not None:
- compile_base(compile_script, compilable_source, base_c_file, base_o_file)
- except:
- if not args.keep:
- print(f"\nDeleting directory {dirname} (run with --keep to preserve it).")
- shutil.rmtree(dirname)
- raise
-
- print(f"\nDone. Imported into {dirname}")
-
-
-if __name__ == "__main__":
- main()
diff --git a/tools/decomp-permuter/mypy.ini b/tools/decomp-permuter/mypy.ini
deleted file mode 100644
index 2d21c54b76..0000000000
--- a/tools/decomp-permuter/mypy.ini
+++ /dev/null
@@ -1,27 +0,0 @@
-[mypy]
-check_untyped_defs = True
-disallow_any_generics = False
-disallow_incomplete_defs = True
-disallow_subclassing_any = True
-disallow_untyped_calls = True
-disallow_untyped_decorators = True
-disallow_untyped_defs = True
-no_implicit_optional = True
-warn_redundant_casts = True
-warn_return_any = True
-warn_unused_ignores = True
-mypy_path = stubs
-python_version = 3.7
-files = import.py, pah.py, permuter.py, src/net/evaluator.py
-
-[mypy-nacl.*]
-ignore_missing_imports = True
-
-[mypy-pystray.*]
-ignore_missing_imports = True
-
-[mypy-docker.*]
-ignore_missing_imports = True
-
-[mypy-PIL.*]
-ignore_missing_imports = True
diff --git a/tools/decomp-permuter/pah.py b/tools/decomp-permuter/pah.py
deleted file mode 100755
index 0adbb369f4..0000000000
--- a/tools/decomp-permuter/pah.py
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env python3
-from src.net.cmd.main import main
-
-main()
diff --git a/tools/decomp-permuter/permuter.py b/tools/decomp-permuter/permuter.py
deleted file mode 100755
index 8433c82180..0000000000
--- a/tools/decomp-permuter/permuter.py
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env python3
-from src.main import main
-
-if __name__ == "__main__":
- main()
diff --git a/tools/decomp-permuter/permuter_settings_example.toml b/tools/decomp-permuter/permuter_settings_example.toml
deleted file mode 100644
index 46523f3315..0000000000
--- a/tools/decomp-permuter/permuter_settings_example.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Optional configuration file for import.py. Put it in the root or in tools/
-# of the repo you are importing from.
-
-build_system = "ninja"
-
-[preserve_macros]
-"g[DS]P.*" = "void"
-"gDma.*" = "void"
-"_SHIFTL" = "unsigned int"
diff --git a/tools/decomp-permuter/run-tests.sh b/tools/decomp-permuter/run-tests.sh
deleted file mode 100755
index eff96637e3..0000000000
--- a/tools/decomp-permuter/run-tests.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-python3 -m unittest discover -s test/
-# python3 -m pytest test/
diff --git a/tools/decomp-permuter/sort_cands.sh b/tools/decomp-permuter/sort_cands.sh
deleted file mode 100755
index 2f30a84692..0000000000
--- a/tools/decomp-permuter/sort_cands.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-if [[ $1 < 2 ]]; then
- echo "Usage: $0 output_dir"
- echo "Ex: $0 nonmatchings/func_80000000"
- exit 1
-fi
-if [[ ! -d $1 ]]; then
- echo "Argument must be a directory"
- exit 1
-fi
-
-find $1 -name score.txt -exec echo -n {}\ \; -exec cat {} \; | sort -rnk2
diff --git a/tools/decomp-permuter/src/__init__.py b/tools/decomp-permuter/src/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tools/decomp-permuter/src/ast_types.py b/tools/decomp-permuter/src/ast_types.py
deleted file mode 100644
index 9eb21c28bf..0000000000
--- a/tools/decomp-permuter/src/ast_types.py
+++ /dev/null
@@ -1,303 +0,0 @@
-"""Functions and classes for dealing with types in a C AST.
-
-They make a number of simplifying assumptions:
- - const and volatile doesn't matter.
- - arithmetic promotes all int-like types to 'int'.
- - no two variables can have the same name, even across functions.
-
-For the purposes of the randomizer these restrictions are acceptable."""
-
-from dataclasses import dataclass, field
-from typing import Union, Dict, Set, List
-
-from pycparser import c_ast
-from pycparser.c_ast import ArrayDecl, TypeDecl, PtrDecl, FuncDecl, IdentifierType
-
-Type = Union[PtrDecl, ArrayDecl, TypeDecl, FuncDecl]
-SimpleType = Union[PtrDecl, TypeDecl]
-
-StructUnion = Union[c_ast.Struct, c_ast.Union]
-
-
-@dataclass
-class TypeMap:
- typedefs: Dict[str, Type] = field(default_factory=dict)
- fn_ret_types: Dict[str, Type] = field(default_factory=dict)
- var_types: Dict[str, Type] = field(default_factory=dict)
- struct_defs: Dict[str, StructUnion] = field(default_factory=dict)
-
-
-def basic_type(name: Union[str, List[str]]) -> TypeDecl:
- names = [name] if isinstance(name, str) else name
- idtype = IdentifierType(names=names)
- return TypeDecl(declname=None, quals=[], type=idtype)
-
-
-def pointer(type: Type) -> Type:
- return PtrDecl(quals=[], type=type)
-
-
-def resolve_typedefs(type: Type, typemap: TypeMap) -> Type:
- while (
- isinstance(type, TypeDecl)
- and isinstance(type.type, IdentifierType)
- and len(type.type.names) == 1
- and type.type.names[0] in typemap.typedefs
- ):
- type = typemap.typedefs[type.type.names[0]]
- return type
-
-
-def pointer_decay(type: Type, typemap: TypeMap) -> SimpleType:
- real_type = resolve_typedefs(type, typemap)
- if isinstance(real_type, ArrayDecl):
- return PtrDecl(quals=[], type=real_type.type)
- if isinstance(real_type, FuncDecl):
- return PtrDecl(quals=[], type=type)
- if isinstance(real_type, TypeDecl) and isinstance(real_type.type, c_ast.Enum):
- return basic_type("int")
- assert not isinstance(
- type, (ArrayDecl, FuncDecl)
- ), "resolve_typedefs can't hide arrays/functions"
- return type
-
-
-def get_decl_type(decl: c_ast.Decl) -> Type:
- """For a Decl that declares a variable (and not just a struct/union/enum),
- return its type."""
- assert decl.name is not None
- assert isinstance(decl.type, (PtrDecl, ArrayDecl, FuncDecl, TypeDecl))
- return decl.type
-
-
-def deref_type(type: Type, typemap: TypeMap) -> Type:
- type = resolve_typedefs(type, typemap)
- assert isinstance(type, (ArrayDecl, PtrDecl)), "dereferencing non-pointer"
- return type.type
-
-
-def struct_member_type(struct: StructUnion, field_name: str, typemap: TypeMap) -> Type:
- if not struct.decls:
- assert (
- struct.name in typemap.struct_defs
- ), f"Accessing field {field_name} of undefined struct {struct.name}"
- struct = typemap.struct_defs[struct.name]
- assert struct.decls, "struct_defs never points to an incomplete type"
- for decl in struct.decls:
- if isinstance(decl, c_ast.Decl):
- if decl.name == field_name:
- return get_decl_type(decl)
- if decl.name == None and isinstance(decl.type, (c_ast.Struct, c_ast.Union)):
- try:
- return struct_member_type(decl.type, field_name, typemap)
- except AssertionError:
- pass
-
- assert False, f"No field {field_name} in struct {struct.name}"
-
-
-def expr_type(node: c_ast.Node, typemap: TypeMap) -> Type:
- def rec(sub_expr: c_ast.Node) -> Type:
- return expr_type(sub_expr, typemap)
-
- if isinstance(node, c_ast.Assignment):
- return rec(node.lvalue)
- if isinstance(node, c_ast.StructRef):
- lhs_type = rec(node.name)
- if node.type == "->":
- lhs_type = deref_type(lhs_type, typemap)
- struct_type = resolve_typedefs(lhs_type, typemap)
- assert isinstance(struct_type, TypeDecl)
- assert isinstance(
- struct_type.type, (c_ast.Struct, c_ast.Union)
- ), f"struct deref of non-struct {struct_type.declname}"
- return struct_member_type(struct_type.type, node.field.name, typemap)
- if isinstance(node, c_ast.Cast):
- return node.to_type.type
- if isinstance(node, c_ast.Constant):
- if node.type == "string":
- return pointer(basic_type("char"))
- if node.type == "char":
- return basic_type("int")
- return basic_type(node.type.split(" "))
- if isinstance(node, c_ast.ID):
- return typemap.var_types[node.name]
- if isinstance(node, c_ast.UnaryOp):
- if node.op in ["p++", "p--", "++", "--"]:
- return rec(node.expr)
- if node.op == "&":
- return pointer(rec(node.expr))
- if node.op == "*":
- subtype = rec(node.expr)
- return deref_type(subtype, typemap)
- if node.op in ["-", "+"]:
- subtype = pointer_decay(rec(node.expr), typemap)
- if allowed_basic_type(subtype, typemap, ["double"]):
- return basic_type("double")
- if allowed_basic_type(subtype, typemap, ["float"]):
- return basic_type("float")
- if node.op in ["sizeof", "-", "+", "~", "!"]:
- return basic_type("int")
- assert False, f"unknown unary op {node.op}"
- if isinstance(node, c_ast.BinaryOp):
- lhs_type = pointer_decay(rec(node.left), typemap)
- rhs_type = pointer_decay(rec(node.right), typemap)
- if node.op in [">>", "<<"]:
- return lhs_type
- if node.op in ["<", "<=", ">", ">=", "==", "!=", "&&", "||"]:
- return basic_type("int")
- if node.op in "&|^%":
- return basic_type("int")
- real_lhs = resolve_typedefs(lhs_type, typemap)
- real_rhs = resolve_typedefs(rhs_type, typemap)
- if node.op in "+-":
- lptr = isinstance(real_lhs, PtrDecl)
- rptr = isinstance(real_rhs, PtrDecl)
- if lptr or rptr:
- if lptr and rptr:
- assert node.op != "+", "pointer + pointer"
- return basic_type("int")
- if lptr:
- return lhs_type
- assert node.op == "+", "int - pointer"
- return rhs_type
- if node.op in "*/+-":
- assert isinstance(real_lhs, TypeDecl)
- assert isinstance(real_rhs, TypeDecl)
- assert isinstance(real_lhs.type, IdentifierType)
- assert isinstance(real_rhs.type, IdentifierType)
- if "double" in real_lhs.type.names + real_rhs.type.names:
- return basic_type("double")
- if "float" in real_lhs.type.names + real_rhs.type.names:
- return basic_type("float")
- return basic_type("int")
- if isinstance(node, c_ast.FuncCall):
- expr = node.name
- if isinstance(expr, c_ast.ID):
- if expr.name not in typemap.fn_ret_types:
- raise Exception(f"Called function {expr.name} is missing a prototype")
- return typemap.fn_ret_types[expr.name]
- else:
- fptr_type = resolve_typedefs(rec(expr), typemap)
- if isinstance(fptr_type, PtrDecl):
- fptr_type = fptr_type.type
- fptr_type = resolve_typedefs(fptr_type, typemap)
- assert isinstance(fptr_type, FuncDecl), "call to non-function"
- return fptr_type.type
- if isinstance(node, c_ast.ExprList):
- return rec(node.exprs[-1])
- if isinstance(node, c_ast.ArrayRef):
- subtype = rec(node.name)
- return deref_type(subtype, typemap)
- if isinstance(node, c_ast.TernaryOp):
- return rec(node.iftrue)
- assert False, f"Unknown expression node type: {node}"
-
-
-def decayed_expr_type(expr: c_ast.Node, typemap: TypeMap) -> SimpleType:
- return pointer_decay(expr_type(expr, typemap), typemap)
-
-
-def same_type(
- type1: Type, type2: Type, typemap: TypeMap, allow_similar: bool = False
-) -> bool:
- while True:
- type1 = resolve_typedefs(type1, typemap)
- type2 = resolve_typedefs(type2, typemap)
- if isinstance(type1, ArrayDecl) and isinstance(type2, ArrayDecl):
- type1 = type1.type
- type2 = type2.type
- continue
- if isinstance(type1, PtrDecl) and isinstance(type2, PtrDecl):
- type1 = type1.type
- type2 = type2.type
- continue
- if isinstance(type1, TypeDecl) and isinstance(type2, TypeDecl):
- sub1 = type1.type
- sub2 = type2.type
- if isinstance(sub1, c_ast.Struct) and isinstance(sub2, c_ast.Struct):
- return sub1.name == sub2.name
- if isinstance(sub1, c_ast.Union) and isinstance(sub2, c_ast.Union):
- return sub1.name == sub2.name
- if (
- allow_similar
- and isinstance(sub1, (IdentifierType, c_ast.Enum))
- and isinstance(sub2, (IdentifierType, c_ast.Enum))
- ):
- # All int-ish types are similar (except void, but whatever)
- return True
- if isinstance(sub1, c_ast.Enum) and isinstance(sub2, c_ast.Enum):
- return sub1.name == sub2.name
- if isinstance(sub1, IdentifierType) and isinstance(sub2, IdentifierType):
- return sorted(sub1.names) == sorted(sub2.names)
- return False
-
-
-def allowed_basic_type(
- type: SimpleType, typemap: TypeMap, allowed_types: List[str]
-) -> bool:
- """Check if a type resolves to a basic type with one of the allowed_types
- keywords in it."""
- base_type = resolve_typedefs(type, typemap)
- if not isinstance(base_type, c_ast.TypeDecl):
- return False
- if not isinstance(base_type.type, c_ast.IdentifierType):
- return False
- if all(x not in base_type.type.names for x in allowed_types):
- return False
- return True
-
-
-def build_typemap(ast: c_ast.FileAST) -> TypeMap:
- ret = TypeMap()
- for item in ast.ext:
- if isinstance(item, c_ast.Typedef):
- ret.typedefs[item.name] = item.type
- if isinstance(item, c_ast.FuncDef):
- assert item.decl.name is not None, "cannot define anonymous function"
- assert isinstance(item.decl.type, FuncDecl)
- ret.fn_ret_types[item.decl.name] = item.decl.type.type
- if isinstance(item, c_ast.Decl) and isinstance(item.type, FuncDecl):
- assert item.name is not None, "cannot define anonymous function"
- ret.fn_ret_types[item.name] = item.type.type
- defined_function_decls: Set[c_ast.Decl] = set()
-
- class Visitor(c_ast.NodeVisitor):
- def visit_Struct(self, struct: c_ast.Struct) -> None:
- if struct.decls and struct.name is not None:
- ret.struct_defs[struct.name] = struct
- # Do not visit decls of this struct
-
- def visit_Union(self, union: c_ast.Union) -> None:
- if union.decls and union.name is not None:
- ret.struct_defs[union.name] = union
- # Do not visit decls of this union
-
- def visit_Decl(self, decl: c_ast.Decl) -> None:
- if decl.name is not None:
- ret.var_types[decl.name] = get_decl_type(decl)
- if not isinstance(decl.type, FuncDecl) or decl in defined_function_decls:
- # Do not visit declarations in parameter lists of functions
- # other than our own.
- self.visit(decl.type)
-
- def visit_Enumerator(self, enumerator: c_ast.Enumerator) -> None:
- ret.var_types[enumerator.name] = basic_type("int")
-
- def visit_FuncDef(self, fn: c_ast.FuncDef) -> None:
- if fn.decl.name is not None:
- ret.var_types[fn.decl.name] = get_decl_type(fn.decl)
- defined_function_decls.add(fn.decl)
- self.generic_visit(fn)
-
- Visitor().visit(ast)
- return ret
-
-
-def set_decl_name(decl: c_ast.Decl) -> None:
- name = decl.name
- assert name is not None
- type = get_decl_type(decl)
- while not isinstance(type, TypeDecl):
- type = type.type
- type.declname = name
diff --git a/tools/decomp-permuter/src/ast_util.py b/tools/decomp-permuter/src/ast_util.py
deleted file mode 100644
index 91716fb97c..0000000000
--- a/tools/decomp-permuter/src/ast_util.py
+++ /dev/null
@@ -1,505 +0,0 @@
-from base64 import b64decode
-from collections import defaultdict
-import copy
-from dataclasses import dataclass
-from random import Random
-import re
-from typing import Any, Callable, Dict, List, Optional, Set, Tuple, TYPE_CHECKING, Union
-
-from pycparser import CParser, c_ast as ca, c_generator
-from pycparser.plyparser import ParseError
-
-from .error import CandidateConstructionFailure
-from .ast_types import SimpleType, set_decl_name
-
-
-@dataclass
-class Indices:
- starts: Dict[ca.Node, int]
- ends: Dict[ca.Node, int]
-
-
-Block = Union[ca.Compound, ca.Case, ca.Default]
-if TYPE_CHECKING:
- # ca.Expression and ca.Statement don't actually exist, they live only in
- # the stubs file.
- Expression = ca.Expression
- Statement = ca.Statement
-else:
- Expression = Statement = None
-
-
-def to_c_raw(node: ca.Node) -> str:
- source: str = c_generator.CGenerator().visit(node)
- return source
-
-
-def to_c(node: ca.Node, *, from_import: bool = False) -> str:
- source = to_c_raw(node) if from_import else PatchedCGenerator().visit(node)
- return process_pragmas(source)
-
-
-def process_pragmas(source: str) -> str:
- if "#pragma" not in source:
- return source
- lines = source.split("\n")
- out: List[str] = []
- same_line = 0
- ignore = 0
- for line in lines:
- stripped = line.strip()
- if stripped.startswith("#pragma _permuter "):
- # Expand permuter pragmas to nothing, by default. Still, keep one
- # output line per input line to preserve line numbers for import.py
- # error messages.
- line = ""
-
- stripped = stripped[len("#pragma _permuter ") :]
- if stripped == "sameline start":
- same_line += 1
- elif stripped == "sameline end":
- same_line -= 1
- elif stripped == "latedefine start":
- ignore += 1
- elif stripped == "latedefine end":
- assert ignore > 0, "mismatched ignore pragmas"
- ignore -= 1
- elif stripped.startswith("define "):
- assert ignore > 0, "define pragma must be within latedefine block"
- line = "#" + stripped
- elif stripped.startswith("b64literal "):
- line = b64decode(stripped.split(" ", 1)[1]).decode("utf-8")
- elif ignore > 0:
- # Ignore non-pragma lines within latedefine section
- line = ""
-
- if not same_line:
- line += "\n"
- elif line and out and not out[-1].endswith("\n"):
- line = " " + line.lstrip()
- out.append(line)
- assert same_line == 0
- assert ignore == 0, "unbalanced ignore pragmas"
- return "".join(out).rstrip() + "\n"
-
-
-class PatchedCGenerator(c_generator.CGenerator):
- """Like a CGenerator, except it keeps else if's prettier despite
- the terrible things we've done to them in normalize_ast."""
-
- def visit_If(self, n: ca.If) -> str:
- n2 = n
- if (
- n.iffalse
- and isinstance(n.iffalse, ca.Compound)
- and n.iffalse.block_items
- and len(n.iffalse.block_items) == 1
- and isinstance(n.iffalse.block_items[0], ca.If)
- ):
- n2 = ca.If(cond=n.cond, iftrue=n.iftrue, iffalse=n.iffalse.block_items[0])
- return super().visit_If(n2) # type: ignore
-
-
-def extract_fn(ast: ca.FileAST, fn_name: str) -> Tuple[ca.FuncDef, int]:
- ret = []
- for i, node in enumerate(ast.ext):
- if isinstance(node, ca.FuncDef):
- if node.decl.name == fn_name:
- ret.append((node, i))
- else:
- node = node.decl
- ast.ext[i] = node
- if isinstance(node, ca.Decl) and isinstance(node.type, ca.FuncDecl):
- node.funcspec = [spec for spec in node.funcspec if spec != "static"]
- if len(ret) == 0:
- raise CandidateConstructionFailure(f"Function {fn_name} not found in base.c.")
- if len(ret) > 1:
- raise CandidateConstructionFailure(
- f"Found multiple copies of function {fn_name} in base.c."
- )
- return ret[0]
-
-
-def parse_c(source: str, *, from_import: bool = False) -> ca.FileAST:
- try:
- parser = CParser()
- return parser.parse(source, "")
- except ParseError as e:
- msg = str(e)
- position, msg = msg.split(": ", 1)
- parts = position.split(":")
- if len(parts) >= 2:
- lineno = int(parts[1])
- posstr = f" at approximately line {lineno}"
- if len(parts) >= 3:
- posstr += f", column {parts[2]}"
- if not from_import:
- posstr += " (after PERM expansion)"
- try:
- line = source.split("\n")[lineno - 1].rstrip()
- posstr += "\n\n" + line
- except IndexError:
- posstr += "(out of bounds?)"
- else:
- posstr = ""
- raise CandidateConstructionFailure(
- f"Syntax error in base.c.\n{msg}{posstr}"
- ) from None
-
-
-def compute_node_indices(top_node: ca.Node) -> Indices:
- starts: Dict[ca.Node, int] = {}
- ends: Dict[ca.Node, int] = {}
- cur_index = 1
-
- class Visitor(ca.NodeVisitor):
- def generic_visit(self, node: ca.Node) -> None:
- nonlocal cur_index
- assert node not in starts, "nodes should only appear once in AST"
- starts[node] = cur_index
- cur_index += 2
- super().generic_visit(node)
- ends[node] = cur_index
- cur_index += 2
-
- Visitor().visit(top_node)
- return Indices(starts, ends)
-
-
-def equal_ast(a: ca.Node, b: ca.Node) -> bool:
- def equal(a: Any, b: Any) -> bool:
- if type(a) != type(b):
- return False
- if a is None:
- return b is None
- if isinstance(a, list):
- assert isinstance(b, list)
- if len(a) != len(b):
- return False
- for i in range(len(a)):
- if not equal(a[i], b[i]):
- return False
- return True
- if isinstance(a, (int, str)):
- return bool(a == b)
- assert isinstance(a, ca.Node)
- for name in a.__slots__[:-2]: # type: ignore
- if not equal(getattr(a, name), getattr(b, name)):
- return False
- return True
-
- return equal(a, b)
-
-
-def is_lvalue(expr: Expression) -> bool:
- if isinstance(expr, (ca.ID, ca.StructRef, ca.ArrayRef)):
- return True
- if isinstance(expr, ca.UnaryOp):
- return expr.op == "*"
- return False
-
-
-def is_effectful(expr: Expression) -> bool:
- found = False
-
- class Visitor(ca.NodeVisitor):
- def visit_UnaryOp(self, node: ca.UnaryOp) -> None:
- nonlocal found
- if node.op in ["p++", "p--", "++", "--"]:
- found = True
- else:
- self.generic_visit(node.expr)
-
- def visit_FuncCall(self, _: ca.Node) -> None:
- nonlocal found
- found = True
-
- def visit_Assignment(self, _: ca.Node) -> None:
- nonlocal found
- found = True
-
- Visitor().visit(expr)
- return found
-
-
-def get_block_stmts(block: Block, force: bool) -> List[Statement]:
- if isinstance(block, ca.Compound):
- ret = block.block_items or []
- if force and not block.block_items:
- block.block_items = ret
- else:
- ret = block.stmts or []
- if force and not block.stmts:
- block.stmts = ret
- return ret
-
-
-def insert_decl(
- fn: ca.FuncDef, var: str, type: SimpleType, random: Optional[Random] = None
-) -> None:
- type = copy.deepcopy(type)
- decl = ca.Decl(
- name=var, quals=[], storage=[], funcspec=[], type=type, init=None, bitsize=None
- )
- set_decl_name(decl)
- assert fn.body.block_items, "Non-empty function"
- for index, stmt in enumerate(fn.body.block_items):
- if not isinstance(stmt, ca.Decl):
- break
- else:
- index = len(fn.body.block_items)
-
- if random:
- index = random.randint(0, index)
- fn.body.block_items[index:index] = [decl]
-
-
-def insert_statement(block: Block, index: int, stmt: Statement) -> None:
- stmts = get_block_stmts(block, True)
- stmts[index:index] = [stmt]
-
-
-def brace_nested_blocks(stmt: Statement) -> None:
- def brace(stmt: Statement) -> Block:
- if isinstance(stmt, (ca.Compound, ca.Case, ca.Default)):
- return stmt
- return ca.Compound([stmt])
-
- if isinstance(stmt, (ca.For, ca.While, ca.DoWhile)):
- stmt.stmt = brace(stmt.stmt)
- elif isinstance(stmt, ca.If):
- stmt.iftrue = brace(stmt.iftrue)
- if stmt.iffalse:
- stmt.iffalse = brace(stmt.iffalse)
- elif isinstance(stmt, ca.Switch):
- stmt.stmt = brace(stmt.stmt)
- elif isinstance(stmt, ca.Label):
- brace_nested_blocks(stmt.stmt)
-
-
-def has_nested_block(node: ca.Node) -> bool:
- return isinstance(
- node,
- (
- ca.Compound,
- ca.For,
- ca.While,
- ca.DoWhile,
- ca.If,
- ca.Switch,
- ca.Case,
- ca.Default,
- ),
- )
-
-
-def for_nested_blocks(stmt: Statement, callback: Callable[[Block], None]) -> None:
- def invoke(stmt: Statement) -> None:
- assert isinstance(
- stmt, (ca.Compound, ca.Case, ca.Default)
- ), "brace_nested_blocks should have turned nested statements into blocks"
- callback(stmt)
-
- if isinstance(stmt, ca.Compound):
- invoke(stmt)
- elif isinstance(stmt, (ca.For, ca.While, ca.DoWhile)):
- invoke(stmt.stmt)
- elif isinstance(stmt, ca.If):
- if stmt.iftrue:
- invoke(stmt.iftrue)
- if stmt.iffalse:
- invoke(stmt.iffalse)
- elif isinstance(stmt, ca.Switch):
- invoke(stmt.stmt)
- elif isinstance(stmt, (ca.Case, ca.Default)):
- invoke(stmt)
- elif isinstance(stmt, ca.Label):
- for_nested_blocks(stmt.stmt, callback)
-
-
-def normalize_ast(fn: ca.FuncDef, ast: ca.FileAST) -> None:
- """Add braces to all ifs/fors/etc., to make it easier to insert statements."""
-
- def rec(block: Block) -> None:
- stmts = get_block_stmts(block, False)
- for stmt in stmts:
- brace_nested_blocks(stmt)
- for_nested_blocks(stmt, rec)
-
- rec(fn.body)
-
-
-def prune_ast(fn: ca.FuncDef, ast: ca.FileAST) -> int:
- """Prune away unnecessary parts of the AST, to reduce overhead from serialization
- and from the compiler's C parser."""
-
- # Create a GC graph that maps names of declarations and enumerators to indices
- # in ast.ext, as well an initial list of GC roots, consisting of everything
- # that isn't a Decl and or Typedef.
- edges: Dict[str, List[int]] = defaultdict(list)
- gc_roots: List[int] = []
- can_fwd_declare_typedef: Set[str] = set()
- can_fwd_declare_tagged: Set[str] = set()
-
- def add_type_edges(
- tp: Union["ca.Type", ca.Struct, ca.Union, ca.Enum], i: int
- ) -> None:
- while isinstance(tp, (ca.PtrDecl, ca.ArrayDecl)):
- tp = tp.type
- if isinstance(tp, ca.FuncDecl):
- return
- inner_type = tp.type if isinstance(tp, ca.TypeDecl) else tp
- if isinstance(inner_type, ca.IdentifierType):
- return
- if inner_type.name:
- edges[inner_type.name].append(i)
- if isinstance(inner_type, ca.Enum) and inner_type.values:
- for value in inner_type.values.enumerators:
- edges[value.name].append(i)
- if isinstance(inner_type, (ca.Struct, ca.Union)) and inner_type.decls:
- for decl in inner_type.decls:
- if isinstance(decl, ca.Decl):
- add_type_edges(decl.type, i)
-
- for i in range(len(ast.ext)):
- item = ast.ext[i]
- if isinstance(item, ca.Decl) and not item.init:
- # (Exclude declarations with initializers, since taking function
- # pointers can affect regalloc on IDO.)
- if item.name:
- edges[item.name].append(i)
- if isinstance(item.type, (ca.Struct, ca.Union, ca.Enum)) and item.type.name:
- can_fwd_declare_tagged.add(item.type.name)
- add_type_edges(item.type, i)
- elif isinstance(item, ca.Typedef):
- edges[item.name].append(i)
- if isinstance(item.type, ca.TypeDecl) and isinstance(
- item.type.type, (ca.Struct, ca.Union, ca.Enum)
- ):
- can_fwd_declare_typedef.add(item.name)
- add_type_edges(item.type, i)
- elif isinstance(item, ca.Pragma) and "GLOBAL_ASM" in item.string:
- pass
- else:
- gc_roots.append(i)
-
- mentioned_ids: Set[str] = set()
-
- class IdVisitor(ca.NodeVisitor):
- def visit_Pragma(self, node: ca.Pragma) -> None:
- for token in re.findall(r"[a-zA-Z0-9_$]+", node.string):
- mentioned_ids.add(token)
-
- def visit_ID(self, node: ca.ID) -> None:
- mentioned_ids.add(node.name)
-
- IdVisitor().visit(ast)
-
- # Do the GC as a DFS traversal of the graph. Visiting a node searches its
- # AST for all kinds of mentioned IDs, and adds more nodes to the stack
- # using the edges we found before.
- gc_todo: List[int] = gc_roots
- need_fwd_decl_typedef: List[str] = []
- need_fwd_decl_tagged: List[str] = []
-
- def add_name(name: str) -> None:
- if name in edges:
- gc_todo.extend(edges[name])
- del edges[name]
-
- class Visitor(ca.NodeVisitor):
- def visit_Pragma(self, node: ca.Pragma) -> None:
- for token in re.findall(r"[a-zA-Z0-9_$]+", node.string):
- add_name(token)
-
- def visit_ID(self, node: ca.ID) -> None:
- add_name(node.name)
-
- def visit_IdentifierType(self, node: ca.IdentifierType) -> None:
- for name in node.names:
- add_name(name)
-
- def visit_Enum(self, node: ca.Enum) -> None:
- if node.name and not node.values:
- add_name(node.name)
- self.generic_visit(node)
-
- def visit_Struct(self, node: ca.Struct) -> None:
- if node.name and not node.decls:
- add_name(node.name)
- self.generic_visit(node)
-
- def visit_Union(self, node: ca.Union) -> None:
- if node.name and not node.decls:
- add_name(node.name)
- self.generic_visit(node)
-
- def visit_PtrDecl(self, node: ca.PtrDecl) -> None:
- # For pointer declarations which haven't been accessed, forward
- # declarations suffice.
- if (
- isinstance(node.type, ca.TypeDecl)
- and node.type.declname
- and node.type.declname not in mentioned_ids
- ):
- tp = node.type.type
- if isinstance(tp, ca.IdentifierType):
- if all(name in can_fwd_declare_typedef for name in tp.names):
- need_fwd_decl_typedef.extend(tp.names)
- return
- elif tp.name and tp.name in can_fwd_declare_tagged:
- if not (tp.values if isinstance(tp, ca.Enum) else tp.decls):
- need_fwd_decl_tagged.append(tp.name)
- return
- self.generic_visit(node)
-
- def visit_TypeDecl(self, node: ca.TypeDecl) -> None:
- if node.declname:
- add_name(node.declname)
- self.generic_visit(node)
-
- keep_exts: Set[int] = set()
- while gc_todo:
- i = gc_todo.pop()
- if i not in keep_exts:
- keep_exts.add(i)
- Visitor().visit(ast.ext[i])
-
- temp_id = 0
-
- def fwd_declare(tp: Union[ca.Struct, ca.Union, ca.Enum]) -> None:
- nonlocal temp_id
- if not tp.name:
- temp_id += 1
- tp.name = f"_PermuterTemp{temp_id}"
- if isinstance(tp, (ca.Struct, ca.Union)):
- tp.decls = None
- elif isinstance(tp, ca.Enum):
- tp.values = None
- else:
- assert False
-
- new_ext = []
-
- for i, item in enumerate(ast.ext):
- if i in keep_exts:
- pass
- elif isinstance(item, ca.Typedef) and item.name in need_fwd_decl_typedef:
- assert item.name in can_fwd_declare_typedef
- assert isinstance(item.type, ca.TypeDecl)
- assert isinstance(item.type.type, (ca.Struct, ca.Union, ca.Enum))
- fwd_declare(item.type.type)
- elif (
- isinstance(item, ca.Decl)
- and isinstance(item.type, (ca.Struct, ca.Union, ca.Enum))
- and item.type.name
- and item.type.name in need_fwd_decl_tagged
- ):
- assert item.type.name in can_fwd_declare_tagged
- fwd_declare(item.type)
- else:
- continue
- new_ext.append(item)
-
- ast.ext = new_ext
- return ast.ext.index(fn)
diff --git a/tools/decomp-permuter/src/candidate.py b/tools/decomp-permuter/src/candidate.py
deleted file mode 100644
index 5504b3d3dd..0000000000
--- a/tools/decomp-permuter/src/candidate.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import copy
-from dataclasses import dataclass, field
-import functools
-from typing import Optional, Tuple
-
-from pycparser import c_ast as ca
-
-from .compiler import Compiler
-from .randomizer import Randomizer
-from .scorer import Scorer
-from .perm.perm import EvalState
-from .perm.ast import apply_ast_perms
-from .helpers import try_remove
-from .profiler import Profiler
-from . import ast_util
-
-
-@dataclass
-class CandidateResult:
- """Represents the result of scoring a candidate, and is sent from child to
- parent processes, or server to client with p@h."""
-
- score: int
- hash: Optional[str]
- source: Optional[str]
- profiler: Optional[Profiler] = None
-
-
-@dataclass
-class Candidate:
- """
- Represents a AST candidate created from a source which can be randomized
- (possibly multiple times), compiled, and scored.
- """
-
- ast: ca.FileAST
-
- fn_index: int
- rng_seed: int
- randomizer: Randomizer
- score_value: Optional[int] = field(init=False, default=None)
- score_hash: Optional[str] = field(init=False, default=None)
- _cache_source: Optional[str] = field(init=False, default=None)
-
- @staticmethod
- @functools.lru_cache(maxsize=16)
- def _cached_shared_ast(
- source: str, fn_name: str
- ) -> Tuple[ca.FuncDef, int, ca.FileAST]:
- ast = ast_util.parse_c(source)
- orig_fn, fn_index = ast_util.extract_fn(ast, fn_name)
- ast_util.normalize_ast(orig_fn, ast)
- return orig_fn, fn_index, ast
-
- @staticmethod
- def from_source(
- source: str, eval_state: EvalState, fn_name: str, rng_seed: int
- ) -> "Candidate":
- # Use the same AST for all instances of the same original source, but
- # with the target function deeply copied. Since we never change the
- # AST outside of the target function, this is fine, and it saves us
- # performance (deepcopy is really slow).
- orig_fn, fn_index, ast = Candidate._cached_shared_ast(source, fn_name)
- ast = copy.copy(ast)
- ast.ext = copy.copy(ast.ext)
- fn_copy = copy.deepcopy(orig_fn)
- ast.ext[fn_index] = fn_copy
- apply_ast_perms(fn_copy, eval_state)
- return Candidate(
- ast=ast,
- fn_index=fn_index,
- rng_seed=rng_seed,
- randomizer=Randomizer(rng_seed),
- )
-
- def randomize_ast(self) -> None:
- self.randomizer.randomize(self.ast, self.fn_index)
- self._cache_source = None
-
- def get_source(self) -> str:
- if self._cache_source is None:
- self._cache_source = ast_util.to_c(self.ast)
- return self._cache_source
-
- def compile(self, compiler: Compiler, show_errors: bool = False) -> Optional[str]:
- source: str = self.get_source()
- return compiler.compile(source, show_errors=show_errors)
-
- def score(self, scorer: Scorer, o_file: Optional[str]) -> CandidateResult:
- self.score_value = None
- self.score_hash = None
- try:
- self.score_value, self.score_hash = scorer.score(o_file)
- finally:
- if o_file:
- try_remove(o_file)
- return CandidateResult(
- score=self.score_value, hash=self.score_hash, source=self.get_source()
- )
diff --git a/tools/decomp-permuter/src/compiler.py b/tools/decomp-permuter/src/compiler.py
deleted file mode 100644
index 284ed316ba..0000000000
--- a/tools/decomp-permuter/src/compiler.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from typing import Optional
-import tempfile
-import subprocess
-
-from .helpers import try_remove
-
-
-class Compiler:
- def __init__(self, compile_cmd: str, *, show_errors: bool) -> None:
- self.compile_cmd = compile_cmd
- self.show_errors = show_errors
-
- def compile(self, source: str, *, show_errors: bool = False) -> Optional[str]:
- """Try to compile a piece of C code. Returns the filename of the resulting .o
- temp file if it succeeds."""
- show_errors = show_errors or self.show_errors
- with tempfile.NamedTemporaryFile(
- prefix="permuter", suffix=".c", mode="w", delete=False
- ) as f:
- c_name = f.name
- f.write(source)
-
- with tempfile.NamedTemporaryFile(
- prefix="permuter", suffix=".o", delete=False
- ) as f2:
- o_name = f2.name
-
- try:
- stderr = 2 if show_errors else subprocess.DEVNULL
- subprocess.check_call(
- [self.compile_cmd, c_name, "-o", o_name],
- stdout=stderr,
- stderr=stderr,
- )
- except subprocess.CalledProcessError:
- if not show_errors:
- try_remove(c_name)
- try_remove(o_name)
- return None
- except KeyboardInterrupt:
- # If Ctrl+C happens during this call, make a best effort in
- # removing the .c and .o files. This is totally racy, but oh well...
- try_remove(c_name)
- try_remove(o_name)
- raise
-
- try_remove(c_name)
- return o_name
diff --git a/tools/decomp-permuter/src/error.py b/tools/decomp-permuter/src/error.py
deleted file mode 100644
index a3f53985f1..0000000000
--- a/tools/decomp-permuter/src/error.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from dataclasses import dataclass
-
-
-@dataclass
-class ServerError(Exception):
- message: str
-
-
-@dataclass
-class CandidateConstructionFailure(Exception):
- message: str
diff --git a/tools/decomp-permuter/src/helpers.py b/tools/decomp-permuter/src/helpers.py
deleted file mode 100644
index 5daab76055..0000000000
--- a/tools/decomp-permuter/src/helpers.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-from typing import NoReturn
-
-
-def plural(n: int, noun: str) -> str:
- s = "s" if n != 1 else ""
- return f"{n} {noun}{s}"
-
-
-def exception_to_string(e: object) -> str:
- return str(e) or e.__class__.__name__
-
-
-def static_assert_unreachable(x: NoReturn) -> NoReturn:
- raise Exception("Unreachable! " + repr(x))
-
-
-def try_remove(path: str) -> None:
- try:
- os.remove(path)
- except FileNotFoundError:
- pass
diff --git a/tools/decomp-permuter/src/main.py b/tools/decomp-permuter/src/main.py
deleted file mode 100644
index 22320bbe2a..0000000000
--- a/tools/decomp-permuter/src/main.py
+++ /dev/null
@@ -1,654 +0,0 @@
-import argparse
-from dataclasses import dataclass, field
-import itertools
-import multiprocessing
-from multiprocessing import Queue
-import os
-import queue
-import sys
-import threading
-import time
-from typing import (
- Callable,
- Dict,
- Iterable,
- Iterator,
- List,
- Optional,
- Tuple,
-)
-
-from .candidate import CandidateResult
-from .compiler import Compiler
-from .error import CandidateConstructionFailure
-from .helpers import plural, static_assert_unreachable
-from .net.client import start_client
-from .net.core import ServerError, connect, enable_debug_mode, MAX_PRIO, MIN_PRIO
-from .permuter import (
- EvalError,
- EvalResult,
- Feedback,
- Finished,
- Message,
- NeedMoreWork,
- Permuter,
- Task,
- WorkDone,
-)
-from .preprocess import preprocess
-from .printer import Printer
-from .profiler import Profiler
-from .scorer import Scorer
-
-# The probability that the randomizer continues transforming the output it
-# generated last time.
-DEFAULT_RAND_KEEP_PROB = 0.6
-
-
-@dataclass
-class Options:
- directories: List[str]
- show_errors: bool = False
- show_timings: bool = False
- print_diffs: bool = False
- stack_differences: bool = False
- abort_exceptions: bool = False
- better_only: bool = False
- best_only: bool = False
- quiet: bool = False
- stop_on_zero: bool = False
- keep_prob: float = DEFAULT_RAND_KEEP_PROB
- force_seed: Optional[str] = None
- threads: int = 1
- use_network: bool = False
- network_debug: bool = False
- network_priority: float = 1.0
-
-
-def restricted_float(lo: float, hi: float) -> Callable[[str], float]:
- def convert(x: str) -> float:
- try:
- ret = float(x)
- except ValueError:
- raise argparse.ArgumentTypeError(f"invalid float value: '{x}'")
-
- if ret < lo or ret > hi:
- raise argparse.ArgumentTypeError(
- f"value {x} is out of range (must be between {lo} and {hi})"
- )
- return ret
-
- return convert
-
-
-@dataclass
-class EvalContext:
- options: Options
- printer: Printer = field(default_factory=Printer)
- iteration: int = 0
- errors: int = 0
- overall_profiler: Profiler = field(default_factory=Profiler)
- permuters: List[Permuter] = field(default_factory=list)
-
-
-def write_candidate(perm: Permuter, result: CandidateResult) -> None:
- """Write the candidate's C source and score to the next output directory"""
- ctr = 0
- while True:
- ctr += 1
- try:
- output_dir = os.path.join(perm.dir, f"output-{result.score}-{ctr}")
- os.mkdir(output_dir)
- break
- except FileExistsError:
- pass
- source = result.source
- assert source is not None, "Permuter._need_to_send_source is wrong!"
- with open(os.path.join(output_dir, "source.c"), "x", encoding="utf-8") as f:
- f.write(source)
- with open(os.path.join(output_dir, "score.txt"), "x", encoding="utf-8") as f:
- f.write(f"{result.score}\n")
- with open(os.path.join(output_dir, "diff.txt"), "x", encoding="utf-8") as f:
- f.write(perm.diff(source) + "\n")
- print(f"wrote to {output_dir}")
-
-
-def post_score(
- context: EvalContext, permuter: Permuter, result: EvalResult, who: Optional[str]
-) -> bool:
- if isinstance(result, EvalError):
- if result.exc_str is not None:
- context.printer.print(
- "internal permuter failure.", permuter, who, keep_progress=True
- )
- print(result.exc_str)
- if result.seed is not None:
- seed_str = str(result.seed[1])
- if result.seed[0] != 0:
- seed_str = f"{result.seed[0]},{seed_str}"
- print(f"To reproduce the failure, rerun with: --seed {seed_str}")
- if context.options.abort_exceptions:
- sys.exit(1)
- else:
- return False
-
- if context.options.print_diffs:
- assert result.source is not None, "Permuter._need_to_send_source is wrong"
- print()
- print(permuter.diff(result.source))
- input("Press any key to continue...")
-
- profiler = result.profiler
- score_value = result.score
-
- if profiler is not None:
- for stattype in profiler.time_stats:
- context.overall_profiler.add_stat(stattype, profiler.time_stats[stattype])
-
- context.iteration += 1
- if score_value == permuter.scorer.PENALTY_INF:
- disp_score = "inf"
- context.errors += 1
- else:
- disp_score = str(score_value)
- timings = ""
- if context.options.show_timings:
- timings = " \t" + context.overall_profiler.get_str_stats()
- status_line = f"iteration {context.iteration}, {context.errors} errors, score = {disp_score}{timings}"
-
- if permuter.should_output(result):
- former_best = permuter.best_score
- permuter.record_result(result)
- if score_value < former_best:
- color = "\u001b[32;1m"
- msg = f"found new best score! ({score_value} vs {permuter.base_score})"
- elif score_value == former_best:
- color = "\u001b[32;1m"
- msg = f"tied best score! ({score_value} vs {permuter.base_score})"
- elif score_value < permuter.base_score:
- color = "\u001b[33m"
- msg = f"found a better score! ({score_value} vs {permuter.base_score})"
- else:
- color = "\u001b[33m"
- msg = f"found different asm with same score ({score_value})"
- context.printer.print(msg, permuter, who, color=color)
-
- write_candidate(permuter, result)
- if not context.options.quiet:
- context.printer.progress(status_line)
- return score_value == 0
-
-
-def cycle_seeds(permuters: List[Permuter]) -> Iterable[Tuple[int, int]]:
- """
- Return all possible (permuter index, seed) pairs, cycling over permuters.
- If a permuter is randomized, it will keep repeating seeds infinitely.
- """
- iterators: List[Iterator[Tuple[int, int]]] = []
- for perm_ind, permuter in enumerate(permuters):
- it = permuter.seed_iterator()
- iterators.append(zip(itertools.repeat(perm_ind), it))
-
- i = 0
- while iterators:
- i %= len(iterators)
- item = next(iterators[i], None)
- if item is None:
- del iterators[i]
- i -= 1
- else:
- yield item
- i += 1
-
-
-def multiprocess_worker(
- permuters: List[Permuter],
- input_queue: "Queue[Task]",
- output_queue: "Queue[Feedback]",
-) -> None:
- try:
- while True:
- # Read a work item from the queue. If none is immediately available,
- # tell the main thread to fill the queues more, and then block on
- # the queue.
- queue_item: Task
- try:
- queue_item = input_queue.get(block=False)
- except queue.Empty:
- output_queue.put((NeedMoreWork(), -1, None))
- queue_item = input_queue.get()
- if isinstance(queue_item, Finished):
- output_queue.put((queue_item, -1, None))
- output_queue.close()
- break
- permuter_index, seed = queue_item
- permuter = permuters[permuter_index]
- result = permuter.try_eval_candidate(seed)
- if isinstance(result, CandidateResult) and permuter.should_output(result):
- permuter.record_result(result)
- output_queue.put((WorkDone(permuter_index, result), -1, None))
- output_queue.put((NeedMoreWork(), -1, None))
- except KeyboardInterrupt:
- # Don't clutter the output with stack traces; Ctrl+C is the expected
- # way to quit and sends KeyboardInterrupt to all processes.
- # A heartbeat thing here would be good but is too complex.
- # Don't join the queue background thread -- thread joins in relation
- # to KeyboardInterrupt usually result in deadlocks.
- input_queue.cancel_join_thread()
- output_queue.cancel_join_thread()
-
-
-def run(options: Options) -> List[int]:
- last_time = time.time()
- try:
-
- def heartbeat() -> None:
- nonlocal last_time
- last_time = time.time()
-
- return run_inner(options, heartbeat)
- except KeyboardInterrupt:
- if time.time() - last_time > 5:
- print()
- print("Aborting stuck process.")
- raise
- print()
- print("Exiting.")
- sys.exit(0)
-
-
-def run_inner(options: Options, heartbeat: Callable[[], None]) -> List[int]:
- print("Loading...")
-
- context = EvalContext(options)
-
- force_seed: Optional[int] = None
- force_rng_seed: Optional[int] = None
- if options.force_seed:
- seed_parts = list(map(int, options.force_seed.split(",")))
- force_rng_seed = seed_parts[-1]
- force_seed = 0 if len(seed_parts) == 1 else seed_parts[0]
-
- name_counts: Dict[str, int] = {}
- for i, d in enumerate(options.directories):
- heartbeat()
- compile_cmd = os.path.join(d, "compile.sh")
- target_o = os.path.join(d, "target.o")
- base_c = os.path.join(d, "base.c")
- for fname in [compile_cmd, target_o, base_c]:
- if not os.path.isfile(fname):
- print(f"Missing file {fname}", file=sys.stderr)
- sys.exit(1)
- if not os.stat(compile_cmd).st_mode & 0o100:
- print(f"{compile_cmd} must be marked executable.", file=sys.stderr)
- sys.exit(1)
-
- fn_name: Optional[str] = None
- try:
- with open(os.path.join(d, "function.txt"), encoding="utf-8") as f:
- fn_name = f.read().strip()
- except FileNotFoundError:
- pass
-
- if fn_name:
- print(f"{base_c} ({fn_name})")
- else:
- print(base_c)
-
- compiler = Compiler(compile_cmd, show_errors=options.show_errors)
- scorer = Scorer(target_o, stack_differences=options.stack_differences)
- c_source = preprocess(base_c)
-
- try:
- permuter = Permuter(
- d,
- fn_name,
- compiler,
- scorer,
- base_c,
- c_source,
- force_seed=force_seed,
- force_rng_seed=force_rng_seed,
- keep_prob=options.keep_prob,
- need_profiler=options.show_timings,
- need_all_sources=options.print_diffs,
- show_errors=options.show_errors,
- best_only=options.best_only,
- better_only=options.better_only,
- )
- except CandidateConstructionFailure as e:
- print(e.message, file=sys.stderr)
- sys.exit(1)
-
- context.permuters.append(permuter)
- name_counts[permuter.fn_name] = name_counts.get(permuter.fn_name, 0) + 1
- print()
-
- if not context.permuters:
- print("No permuters!")
- return []
-
- for permuter in context.permuters:
- if name_counts[permuter.fn_name] > 1:
- permuter.unique_name += f" ({permuter.dir})"
- print(f"[{permuter.unique_name}] base score = {permuter.best_score}")
-
- found_zero = False
- if options.threads == 1 and not options.use_network:
- # Simple single-threaded mode. This is not technically needed, but
- # makes the permuter easier to debug.
- for permuter_index, seed in cycle_seeds(context.permuters):
- heartbeat()
- permuter = context.permuters[permuter_index]
- result = permuter.try_eval_candidate(seed)
- if post_score(context, permuter, result, None):
- found_zero = True
- if options.stop_on_zero:
- break
- else:
- seed_iterators: List[Optional[Iterator[int]]] = [
- permuter.seed_iterator()
- for perm_ind, permuter in enumerate(context.permuters)
- ]
- seed_iterators_remaining = len(seed_iterators)
- next_iterator_index = 0
-
- # Create queues.
- worker_task_queue: "Queue[Task]" = Queue()
- feedback_queue: "Queue[Feedback]" = Queue()
-
- # Connect to network and create client threads and queues.
- net_conns: "List[Tuple[threading.Thread, Queue[Task]]]" = []
- if options.use_network:
- print("Connecting to permuter@home...")
- if options.network_debug:
- enable_debug_mode()
- first_stats: Optional[Tuple[int, int, float]] = None
- for perm_index in range(len(context.permuters)):
- try:
- port = connect()
- except (EOFError, ServerError) as e:
- print("Error:", e)
- sys.exit(1)
- thread, queue, stats = start_client(
- port,
- context.permuters[perm_index],
- perm_index,
- feedback_queue,
- options.network_priority,
- )
- net_conns.append((thread, queue))
- if first_stats is None:
- first_stats = stats
- assert first_stats is not None, "has at least one permuter"
- clients_str = plural(first_stats[0], "other client")
- servers_str = plural(first_stats[1], "server")
- cores_str = plural(int(first_stats[2]), "core")
- print(f"Connected! {servers_str} online ({cores_str}, {clients_str})")
-
- # Start local worker threads
- processes: List[multiprocessing.Process] = []
- for i in range(options.threads):
- p = multiprocessing.Process(
- target=multiprocess_worker,
- args=(context.permuters, worker_task_queue, feedback_queue),
- )
- p.start()
- processes.append(p)
-
- active_workers = len(processes)
-
- if not active_workers and not net_conns:
- print("No workers available! Exiting.")
- sys.exit(1)
-
- def process_finish(finish: Finished, source: int) -> None:
- nonlocal active_workers
-
- if finish.reason:
- permuter: Optional[Permuter] = None
- if source != -1 and len(context.permuters) > 1:
- permuter = context.permuters[source]
- context.printer.print(finish.reason, permuter, None, keep_progress=True)
-
- if source == -1:
- active_workers -= 1
-
- def process_result(work: WorkDone, who: Optional[str]) -> bool:
- permuter = context.permuters[work.perm_index]
- return post_score(context, permuter, work.result, who)
-
- def get_task(perm_index: int) -> Optional[Tuple[int, int]]:
- nonlocal next_iterator_index, seed_iterators_remaining
- if perm_index == -1:
- while seed_iterators_remaining > 0:
- task = get_task(next_iterator_index)
- next_iterator_index += 1
- next_iterator_index %= len(seed_iterators)
- if task is not None:
- return task
- else:
- it = seed_iterators[perm_index]
- if it is not None:
- seed = next(it, None)
- if seed is None:
- seed_iterators[perm_index] = None
- seed_iterators_remaining -= 1
- else:
- return (perm_index, seed)
- return None
-
- # Feed the task queue with work and read from results queue.
- # We generally match these up one-by-one to avoid overfilling queues,
- # but workers can ask us to add more tasks into the system if they run
- # out of work. (This will happen e.g. at the very beginning, when the
- # queues are empty.)
- while seed_iterators_remaining > 0:
- heartbeat()
- feedback, source, who = feedback_queue.get()
- if isinstance(feedback, Finished):
- process_finish(feedback, source)
- elif isinstance(feedback, Message):
- context.printer.print(feedback.text, None, who, keep_progress=True)
- elif isinstance(feedback, WorkDone):
- if process_result(feedback, who):
- # Found score 0!
- found_zero = True
- if options.stop_on_zero:
- break
- elif isinstance(feedback, NeedMoreWork):
- task = get_task(source)
- if task is not None:
- if source == -1:
- worker_task_queue.put(task)
- else:
- net_conns[source][1].put(task)
- else:
- static_assert_unreachable(feedback)
-
- # Signal workers to stop.
- for i in range(active_workers):
- worker_task_queue.put(Finished())
-
- for conn in net_conns:
- conn[1].put(Finished())
-
- # Await final results.
- while active_workers > 0 or net_conns:
- heartbeat()
- feedback, source, who = feedback_queue.get()
- if isinstance(feedback, Finished):
- process_finish(feedback, source)
- elif isinstance(feedback, Message):
- context.printer.print(feedback.text, None, who, keep_progress=True)
- elif isinstance(feedback, WorkDone):
- if not (options.stop_on_zero and found_zero):
- if process_result(feedback, who):
- found_zero = True
- elif isinstance(feedback, NeedMoreWork):
- pass
- else:
- static_assert_unreachable(feedback)
-
- # Wait for workers to finish.
- for p in processes:
- p.join()
-
- # Wait for network connections to close (currently does not happen).
- for conn in net_conns:
- conn[0].join()
-
- if found_zero:
- print("\nFound zero score! Exiting.")
- return [permuter.best_score for permuter in context.permuters]
-
-
-def main() -> None:
- multiprocessing.freeze_support()
- sys.setrecursionlimit(10000)
-
- # Ideally we would do:
- # multiprocessing.set_start_method("spawn")
- # here, to make multiprocessing behave the same across operating systems.
- # However, that means that arguments to Process are passed across using
- # pickling, which mysteriously breaks with pycparser...
- # (AttributeError: 'CParser' object has no attribute 'p_abstract_declarator_opt')
- # So, for now we live with the defaults, which make multiprocessing work on Linux,
- # where it uses fork and doesn't pickle arguments, and break on Windows. Sigh.
-
- parser = argparse.ArgumentParser(
- description="Randomly permute C files to better match a target binary."
- )
- parser.add_argument(
- "directories",
- nargs="+",
- metavar="directory",
- help="Directory containing base.c, target.o and compile.sh. Multiple directories may be given.",
- )
- parser.add_argument(
- "--show-errors",
- dest="show_errors",
- action="store_true",
- help="Display compiler error/warning messages, and keep .c files for failed compiles.",
- )
- parser.add_argument(
- "--show-timings",
- dest="show_timings",
- action="store_true",
- help="Display the time taken by permuting vs. compiling vs. scoring.",
- )
- parser.add_argument(
- "--print-diffs",
- dest="print_diffs",
- action="store_true",
- help="Instead of compiling generated sources, display diffs against a base version.",
- )
- parser.add_argument(
- "--abort-exceptions",
- dest="abort_exceptions",
- action="store_true",
- help="Stop execution when an internal permuter exception occurs.",
- )
- parser.add_argument(
- "--better-only",
- dest="better_only",
- action="store_true",
- help="Only report scores better than the base.",
- )
- parser.add_argument(
- "--best-only",
- dest="best_only",
- action="store_true",
- help="Only report ties or new high scores.",
- )
- parser.add_argument(
- "--stop-on-zero",
- dest="stop_on_zero",
- action="store_true",
- help="Stop after producing an output with score 0.",
- )
- parser.add_argument(
- "--quiet",
- dest="quiet",
- action="store_true",
- help="Don't print a status line with the number of iterations.",
- )
- parser.add_argument(
- "--stack-diffs",
- dest="stack_differences",
- action="store_true",
- help="Take stack differences into account when computing the score.",
- )
- parser.add_argument(
- "--keep-prob",
- dest="keep_prob",
- metavar="PROB",
- type=restricted_float(0.0, 1.0),
- default=DEFAULT_RAND_KEEP_PROB,
- help="""Continue randomizing the previous output with the given probability
- (float in 0..1, default %(default)s).""",
- )
- parser.add_argument("--seed", dest="force_seed", type=str, help=argparse.SUPPRESS)
- parser.add_argument(
- "-j",
- dest="threads",
- type=int,
- default=0,
- help="Number of own threads to use (default: 1 without -J, 0 with -J).",
- )
- parser.add_argument(
- "-J",
- dest="use_network",
- action="store_true",
- help="Harness extra compute power through cyberspace (permuter@home).",
- )
- parser.add_argument(
- "--pah-debug",
- dest="network_debug",
- action="store_true",
- help="Enable debug prints for permuter@home.",
- )
- parser.add_argument(
- "--priority",
- dest="network_priority",
- metavar="PRIORITY",
- type=restricted_float(MIN_PRIO, MAX_PRIO),
- default=1.0,
- help=f"""Proportion of server resources to use when multiple people
- are using -J at the same time.
- Defaults to 1.0, meaning resources are split equally, but can be
- set to any value within [{MIN_PRIO}, {MAX_PRIO}].
- Each server runs with a priority threshold, which defaults to 0.1,
- below which they will not run permuter jobs at all.""",
- )
-
- args = parser.parse_args()
-
- threads = args.threads
- if not threads and not args.use_network:
- threads = 1
-
- options = Options(
- directories=args.directories,
- show_errors=args.show_errors,
- show_timings=args.show_timings,
- print_diffs=args.print_diffs,
- abort_exceptions=args.abort_exceptions,
- better_only=args.better_only,
- best_only=args.best_only,
- quiet=args.quiet,
- stack_differences=args.stack_differences,
- stop_on_zero=args.stop_on_zero,
- keep_prob=args.keep_prob,
- force_seed=args.force_seed,
- threads=threads,
- use_network=args.use_network,
- network_debug=args.network_debug,
- network_priority=args.network_priority,
- )
-
- run(options)
-
-
-if __name__ == "__main__":
- main()
diff --git a/tools/decomp-permuter/src/net/__init__.py b/tools/decomp-permuter/src/net/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tools/decomp-permuter/src/net/client.py b/tools/decomp-permuter/src/net/client.py
deleted file mode 100644
index 1a3442c325..0000000000
--- a/tools/decomp-permuter/src/net/client.py
+++ /dev/null
@@ -1,272 +0,0 @@
-from multiprocessing import Queue
-import re
-import threading
-from typing import Optional, Tuple
-import zlib
-
-from ..candidate import CandidateResult
-from ..helpers import exception_to_string
-from ..permuter import (
- EvalError,
- EvalResult,
- Feedback,
- FeedbackItem,
- Finished,
- Message,
- NeedMoreWork,
- Permuter,
- Task,
- WorkDone,
-)
-from ..profiler import Profiler
-from .core import (
- PermuterData,
- SocketPort,
- json_prop,
- permuter_data_to_json,
-)
-
-
-def _profiler_from_json(obj: dict) -> Profiler:
- ret = Profiler()
- for key in obj:
- assert isinstance(key, str), "json properties are strings"
- stat = Profiler.StatType[key]
- time = json_prop(obj, key, float)
- ret.add_stat(stat, time)
- return ret
-
-
-def _result_from_json(obj: dict, source: Optional[str]) -> EvalResult:
- if "error" in obj:
- return EvalError(exc_str=json_prop(obj, "error", str), seed=None)
-
- profiler: Optional[Profiler] = None
- if "profiler" in obj:
- profiler = _profiler_from_json(json_prop(obj, "profiler", dict))
- return CandidateResult(
- score=json_prop(obj, "score", int),
- hash=json_prop(obj, "hash", str) if "hash" in obj else None,
- source=source,
- profiler=profiler,
- )
-
-
-def _make_script_portable(source: str) -> str:
- """Parse a shell script and get rid of the machine-specific parts that
- import.py introduces. The resulting script must be run in an environment
- that has the right binaries in its $PATH, and with a current working
- directory similar to where import.py found its target's make root."""
- lines = []
- for line in source.split("\n"):
- if re.match("cd '?/", line):
- # Skip cd's to absolute directory paths. Note that shlex quotes
- # its argument with ' if it contains spaces/single quotes.
- continue
- if re.match("'?/", line):
- quote = "'" if line[0] == "'" else ""
- ind = line.find(quote + " ")
- if ind == -1:
- ind = len(line)
- else:
- ind += len(quote)
- lastind = line.rfind("/", 0, ind)
- assert lastind != -1
- # Emit a call to "which" as the first part, to ensure the called
- # binary still sees an absolute path. qemu-irix requires this,
- # for some reason.
- line = "$(which " + quote + line[lastind + 1 : ind] + ")" + line[ind:]
- lines.append(line)
- return "\n".join(lines)
-
-
-def make_portable_permuter(permuter: Permuter) -> PermuterData:
- with open(permuter.scorer.target_o, "rb") as f:
- target_o_bin = f.read()
-
- with open(permuter.compiler.compile_cmd, "r") as f2:
- compile_script = _make_script_portable(f2.read())
-
- return PermuterData(
- base_score=permuter.base_score,
- base_hash=permuter.base_hash,
- fn_name=permuter.fn_name,
- filename=permuter.source_file,
- keep_prob=permuter.keep_prob,
- need_profiler=permuter.need_profiler,
- stack_differences=permuter.scorer.stack_differences,
- compile_script=compile_script,
- source=permuter.source,
- target_o_bin=target_o_bin,
- )
-
-
-class Connection:
- _port: SocketPort
- _permuter_data: PermuterData
- _perm_index: int
- _task_queue: "Queue[Task]"
- _feedback_queue: "Queue[Feedback]"
-
- def __init__(
- self,
- port: SocketPort,
- permuter_data: PermuterData,
- perm_index: int,
- task_queue: "Queue[Task]",
- feedback_queue: "Queue[Feedback]",
- ) -> None:
- self._port = port
- self._permuter_data = permuter_data
- self._perm_index = perm_index
- self._task_queue = task_queue
- self._feedback_queue = feedback_queue
-
- def _send_permuter(self) -> None:
- data = self._permuter_data
- self._port.send_json(permuter_data_to_json(data))
- self._port.send(zlib.compress(data.source.encode("utf-8")))
- self._port.send(zlib.compress(data.target_o_bin))
-
- def _feedback(self, feedback: FeedbackItem, server_nick: Optional[str]) -> None:
- self._feedback_queue.put((feedback, self._perm_index, server_nick))
-
- def _receive_one(self) -> bool:
- """Receive a result/progress message and send it on. Returns true if
- more work should be requested."""
- msg = self._port.receive_json()
- msg_type = json_prop(msg, "type", str)
- if msg_type == "need_work":
- return True
-
- server_nick = json_prop(msg, "server", str)
- if msg_type == "init_done":
- base_hash = json_prop(msg, "hash", str)
- my_base_hash = self._permuter_data.base_hash
- text = "connected"
- if base_hash != my_base_hash:
- text += " (note: mismatching hash)"
- self._feedback(Message(text), server_nick)
- return True
-
- if msg_type == "init_failed":
- text = "failed to initialize: " + json_prop(msg, "reason", str)
- self._feedback(Message(text), server_nick)
- return False
-
- if msg_type == "disconnect":
- self._feedback(Message("disconnected"), server_nick)
- return False
-
- if msg_type == "result":
- source: Optional[str] = None
- if msg.get("has_source") == True:
- # Source is sent separately, compressed, since it can be
- # large (hundreds of kilobytes is not uncommon).
- compressed_source = self._port.receive()
- try:
- source = zlib.decompress(compressed_source).decode("utf-8")
- except Exception as e:
- text = "failed to decompress: " + exception_to_string(e)
- self._feedback(Message(text), server_nick)
- return True
- try:
- result = _result_from_json(msg, source)
- self._feedback(WorkDone(self._perm_index, result), server_nick)
- except Exception as e:
- text = "failed to parse result message: " + exception_to_string(e)
- self._feedback(Message(text), server_nick)
- return True
-
- raise ValueError(f"Invalid message type {msg_type}")
-
- def run(self) -> None:
- finish_reason: Optional[str] = None
- try:
- self._send_permuter()
- self._port.receive_json()
-
- finished = False
-
- # Main loop: send messages from the queue on to the server, and
- # vice versa. Currently we are being lazy and alternate between
- # sending and receiving; this is nicely simple and keeps us on a
- # single thread, however it could cause deadlocks if the server
- # receiver stops reading because we aren't reading fast enough.
- while True:
- if not self._receive_one():
- continue
- self._feedback(NeedMoreWork(), None)
-
- # Read a task and send it on, unless there are no more tasks.
- if not finished:
- task = self._task_queue.get()
- if isinstance(task, Finished):
- # We don't have a way of indicating to the server that
- # all is done: the server currently doesn't track
- # outstanding work so it doesn't know when to close
- # the connection. (Even with this fixed we'll have the
- # problem that servers may disconnect, losing work, so
- # the task never truly finishes. But it might work well
- # enough in practice.)
- finished = True
- else:
- work = {
- "type": "work",
- "work": {
- "seed": task[1],
- },
- }
- self._port.send_json(work)
-
- except EOFError:
- finish_reason = "disconnected from permuter@home"
-
- except Exception as e:
- errmsg = exception_to_string(e)
- finish_reason = f"permuter@home error: {errmsg}"
-
- finally:
- self._feedback(Finished(reason=finish_reason), None)
- self._port.shutdown()
- self._port.close()
-
-
-def start_client(
- port: SocketPort,
- permuter: Permuter,
- perm_index: int,
- feedback_queue: "Queue[Feedback]",
- priority: float,
-) -> "Tuple[threading.Thread, Queue[Task], Tuple[int, int, float]]":
- port.send_json(
- {
- "method": "connect_client",
- "priority": priority,
- }
- )
- obj = port.receive_json()
- if "error" in obj:
- err = json_prop(obj, "error", str)
- # TODO use another exception type
- raise Exception(f"Failed to connect: {err}")
- num_servers = json_prop(obj, "servers", int)
- num_clients = json_prop(obj, "clients", int)
- num_cores = json_prop(obj, "cores", float)
- permuter_data = make_portable_permuter(permuter)
- task_queue: "Queue[Task]" = Queue()
-
- conn = Connection(
- port,
- permuter_data,
- perm_index,
- task_queue,
- feedback_queue,
- )
-
- thread = threading.Thread(target=conn.run, daemon=True)
- thread.start()
-
- stats = (num_clients, num_servers, num_cores)
-
- return thread, task_queue, stats
diff --git a/tools/decomp-permuter/src/net/cmd/__init__.py b/tools/decomp-permuter/src/net/cmd/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tools/decomp-permuter/src/net/cmd/base.py b/tools/decomp-permuter/src/net/cmd/base.py
deleted file mode 100644
index 492044350b..0000000000
--- a/tools/decomp-permuter/src/net/cmd/base.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import abc
-from argparse import ArgumentParser, Namespace
-
-
-class Command(abc.ABC):
- command: str
- help: str
-
- @staticmethod
- @abc.abstractmethod
- def add_arguments(parser: ArgumentParser) -> None:
- ...
-
- @staticmethod
- @abc.abstractmethod
- def run(args: Namespace) -> None:
- ...
diff --git a/tools/decomp-permuter/src/net/cmd/icons/notok.ico b/tools/decomp-permuter/src/net/cmd/icons/notok.ico
deleted file mode 100644
index 2825d0d9c9..0000000000
Binary files a/tools/decomp-permuter/src/net/cmd/icons/notok.ico and /dev/null differ
diff --git a/tools/decomp-permuter/src/net/cmd/icons/ok.ico b/tools/decomp-permuter/src/net/cmd/icons/ok.ico
deleted file mode 100644
index bc0b0534b1..0000000000
Binary files a/tools/decomp-permuter/src/net/cmd/icons/ok.ico and /dev/null differ
diff --git a/tools/decomp-permuter/src/net/cmd/icons/okthink.ico b/tools/decomp-permuter/src/net/cmd/icons/okthink.ico
deleted file mode 100644
index d3a8eacf84..0000000000
Binary files a/tools/decomp-permuter/src/net/cmd/icons/okthink.ico and /dev/null differ
diff --git a/tools/decomp-permuter/src/net/cmd/main.py b/tools/decomp-permuter/src/net/cmd/main.py
deleted file mode 100644
index 9d1e4db1c7..0000000000
--- a/tools/decomp-permuter/src/net/cmd/main.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from argparse import ArgumentParser, RawDescriptionHelpFormatter
-import sys
-
-from ..core import ServerError, enable_debug_mode
-from .run_server import RunServerCommand
-from .setup import SetupCommand
-from .ping import PingCommand
-from .vouch import VouchCommand
-
-
-def main() -> None:
- try:
- # We currently sometimes log stuff to stdout, so it's preferable if it's
- # line-buffered even when redirected to a non-tty (e.g. when running a
- # permuter server as a systemd service). This is supported by Python 3.7
- # and up.
- sys.stdout.reconfigure(line_buffering=True) # type: ignore
- except Exception:
- pass
-
- parser = ArgumentParser(
- description="permuter@home - run the permuter across the Internet!\n\n"
- "To use p@h as a client, just pass -J when running the permuter. "
- "This script is\nonly necessary for configuration or when running a server.",
- formatter_class=RawDescriptionHelpFormatter,
- )
-
- commands = [
- PingCommand,
- RunServerCommand,
- SetupCommand,
- VouchCommand,
- ]
-
- parser.add_argument(
- "--debug",
- dest="debug",
- action="store_true",
- help="Enable debug logging.",
- )
-
- subparsers = parser.add_subparsers(metavar="")
- for command in commands:
- subparser = subparsers.add_parser(
- command.command,
- help=command.help,
- description=command.help,
- )
- command.add_arguments(subparser)
- subparser.set_defaults(subcommand_handler=command.run)
-
- args = parser.parse_args()
- if args.debug:
- enable_debug_mode()
-
- if "subcommand_handler" in args:
- try:
- args.subcommand_handler(args)
- except EOFError as e:
- print("Network error:", e)
- sys.exit(1)
- except ServerError as e:
- print("Error:", e.message)
- sys.exit(1)
- else:
- parser.print_help()
-
-
-if __name__ == "__main__":
- main()
diff --git a/tools/decomp-permuter/src/net/cmd/ping.py b/tools/decomp-permuter/src/net/cmd/ping.py
deleted file mode 100644
index e49f1e6825..0000000000
--- a/tools/decomp-permuter/src/net/cmd/ping.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from argparse import ArgumentParser, Namespace
-import time
-
-from ...helpers import plural
-from ..core import connect, json_prop
-from .base import Command
-
-
-class PingCommand(Command):
- command = "ping"
- help = "Check server connectivity."
-
- @staticmethod
- def add_arguments(parser: ArgumentParser) -> None:
- pass
-
- @staticmethod
- def run(args: Namespace) -> None:
- run_ping()
-
-
-def run_ping() -> None:
- port = connect()
- t0 = time.time()
- port.send_json({"method": "ping"})
- msg = port.receive_json()
- rtt = (time.time() - t0) * 1000
- print(f"Connected successfully! Round-trip time: {rtt:.1f} ms")
- servers_str = plural(json_prop(msg, "servers", int), "server")
- clients_str = plural(json_prop(msg, "clients", int), "client")
- cores_str = plural(int(json_prop(msg, "cores", float)), "core")
- print(f"{servers_str} online ({cores_str}, {clients_str})")
diff --git a/tools/decomp-permuter/src/net/cmd/run_server.py b/tools/decomp-permuter/src/net/cmd/run_server.py
deleted file mode 100644
index 429ddb1b6b..0000000000
--- a/tools/decomp-permuter/src/net/cmd/run_server.py
+++ /dev/null
@@ -1,616 +0,0 @@
-from argparse import ArgumentParser, Namespace
-import base64
-from dataclasses import dataclass
-from enum import Enum
-from functools import partial
-import json
-import os
-import platform
-import queue
-import random
-import shutil
-from subprocess import Popen, PIPE
-import sys
-import time
-import threading
-import traceback
-from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
-
-from ...helpers import static_assert_unreachable
-from ..core import CancelToken, ServerError, read_config
-from ..server import (
- Client,
- Config,
- IoActivity,
- IoConnect,
- IoDisconnect,
- IoImmediateDisconnect,
- IoReconnect,
- IoServerFailed,
- IoShutdown,
- IoUserRemovePermuter,
- IoWorkDone,
- PermuterHandle,
- Server,
- ServerOptions,
-)
-from .base import Command
-from .util import ask
-
-
-class RunServerCommand(Command):
- command = "run-server"
- help = """Run a permuter server, allowing anyone with access to the central
- server to run sandboxed permuter jobs on your machine. Requires docker."""
-
- @staticmethod
- def add_arguments(parser: ArgumentParser) -> None:
- parser.add_argument(
- "--cores",
- dest="num_cores",
- metavar="CORES",
- type=float,
- required=True,
- help="Number of cores to use (float).",
- )
- parser.add_argument(
- "--memory",
- dest="max_memory_gb",
- metavar="MEMORY_GB",
- type=float,
- required=True,
- help="""Restrict the sandboxed process to the given amount of memory in
- gigabytes (float). If this limit is hit, the permuter will crash
- horribly, but at least your system won't lock up.""",
- )
- parser.add_argument(
- "--systray",
- dest="systray",
- action="store_true",
- help="""Make the server controllable through the system tray.""",
- )
- parser.add_argument(
- "--min-priority",
- dest="min_priority",
- metavar="PRIORITY",
- type=float,
- default=0.1,
- help="""Only accept jobs from clients who pass --priority with a number
- higher or equal to this value. (default: %(default)s)""",
- )
-
- @staticmethod
- def run(args: Namespace) -> None:
- options = ServerOptions(
- num_cores=args.num_cores,
- max_memory_gb=args.max_memory_gb,
- min_priority=args.min_priority,
- )
-
- server_main(options, args.systray)
-
-
-class SystrayState:
- def server_reconnecting(self) -> None:
- pass
-
- def server_connected(self) -> None:
- pass
-
- def server_failed(self, graceful: bool, message: Optional[str] = None) -> None:
- pass
-
- def connect(self, handle: PermuterHandle, nickname: str, fn_name: str) -> None:
- pass
-
- def disconnect(self, handle: PermuterHandle) -> None:
- pass
-
- def work_done(self, handle: PermuterHandle, is_improvement: bool) -> None:
- pass
-
- def stop(self) -> None:
- pass
-
-
-@dataclass
-class Permuter:
- nickname: str
- fn_name: str
- iterations: int = 0
- improvements: int = 0
- last_systray_update: float = 0.0
- slot: "Optional[ClientSlot]" = None
-
-
-@dataclass
-class ClientSlot:
- menu_id: int
- iterations_id: int
- improvements_id: int
- stop_id: int
- permuter: Optional[PermuterHandle] = None
-
-
-class SystrayStatus(Enum):
- CONNECTING = 0
- CONNECTED = 1
- FAILED = 2
- RECONNECTING = 3
-
-
-class RealSystrayState(SystrayState):
- _CLIENT_SLOTS = 10
- _UPDATE_INTERVAL = 2.0
- _MENU_TOOLTIP = "permuter@home"
- _permuters: Dict[PermuterHandle, Permuter]
- _onclick: Dict[int, Callable[[], None]]
- _client_slots: List[ClientSlot]
-
- def __init__(
- self,
- config: Config,
- io_queue: "queue.Queue[IoActivity]",
- ) -> None:
- self._io_queue = io_queue
- self._permuters = {}
- self._onclick = {}
- self._status = SystrayStatus.CONNECTING
- self._fail_message: Optional[str] = None
-
- def load_icon(fname: str) -> str:
- path = os.path.join(os.path.dirname(__file__), "icons", fname)
- with open(path, "rb") as f:
- data = f.read()
- return base64.b64encode(data).decode("ascii")
-
- self._icons = {
- "working": load_icon("okthink.ico"),
- "passive": load_icon("ok.ico"),
- "fail": load_icon("notok.ico"),
- }
- self._current_icon = "working"
-
- next_id = 100
-
- def add_item(
- menu: List[dict],
- title: str,
- onclick: Optional[Callable[[], None]] = None,
- *,
- submenu: Optional[List[dict]] = None,
- hidden: bool = False,
- ) -> int:
- nonlocal next_id
- next_id += 1
- obj = {
- "title": title,
- "enabled": onclick is not None or submenu is not None,
- "hidden": hidden,
- "__id": next_id,
- }
- if onclick is not None:
- self._onclick[next_id] = onclick
- if submenu is not None:
- obj["items"] = submenu
- menu.append(obj)
- return next_id
-
- menu: List[dict] = []
- self._status_id = add_item(menu, "Connecting...")
- self._client_slots = []
- for i in range(self._CLIENT_SLOTS):
- submenu: List[dict] = []
- remove_cb = partial(self._remove_permuter, i)
- self._client_slots.append(
- ClientSlot(
- iterations_id=add_item(submenu, ""),
- improvements_id=add_item(submenu, ""),
- stop_id=add_item(submenu, "Stop", remove_cb),
- menu_id=add_item(menu, "", submenu=submenu, hidden=True),
- )
- )
- self._more_id = add_item(menu, "", hidden=True)
- add_item(menu, "Quit", self._quit)
-
- try:
- path = self._setup_helper()
- self._proc = Popen(
- [path],
- stdout=PIPE,
- stdin=PIPE,
- universal_newlines=True,
- )
- assert self._proc.stdout is not None
- self._proc_stdout = self._proc.stdout
- assert self._proc.stdin is not None
- self._proc_stdin = self._proc.stdin
-
- self._send(
- {
- "icon": self._icons[self._current_icon],
- "tooltip": self._MENU_TOOLTIP,
- "items": menu,
- }
- )
-
- resp_str = self._proc_stdout.readline()
- assert resp_str
- resp = json.loads(resp_str)
- assert isinstance(resp, dict)
- assert resp.get("type") == "ready"
- except Exception:
- print("Failed to initialize systray!")
- print()
- print("See src/net/cmd/systray/README.md for details on how to set it up.")
- traceback.print_exc()
- sys.exit(1)
-
- self._read_thread = threading.Thread(target=self._read_loop, daemon=True)
- self._read_thread.start()
-
- @staticmethod
- def _setup_helper() -> str:
- fname = "permuter-systray"
- suffix = ""
- osname = sys.platform.replace("darwin", "macos")
- arch = platform.machine().replace("AMD64", "x86_64")
- if (
- osname in ("win32", "msys", "cygwin")
- or "microsoft" in platform.uname().release.lower()
- ):
- osname = "win"
- suffix = ".exe"
-
- dir = os.path.join(os.path.dirname(__file__), "systray")
- target_binary = os.path.join(dir, fname + suffix)
- if os.path.exists(target_binary):
- return target_binary
-
- prebuilt_file = f"{fname}-{osname}-{arch}{suffix}"
- prebuilt_file = os.path.join(dir, "prebuilt", prebuilt_file)
-
- print("An external helper binary is required for systray support.")
- print(
- "To build it from source (requires Go), see src/net/cmd/systray/README.md."
- )
-
- if os.path.exists(prebuilt_file):
- print("Alternatively, a pre-built binary can be used.")
- if ask("Use pre-built binary?", default=False):
- shutil.copy(prebuilt_file, target_binary)
- os.chmod(target_binary, 0o755)
- return target_binary
-
- print("Aborting.")
- sys.exit(1)
-
- def _send(self, msg: dict) -> None:
- data = json.dumps(msg)
- self._proc_stdin.write(data + "\n")
- self._proc_stdin.flush()
-
- def _update_item(
- self, id: int, title: str, *, hidden: bool = False, enabled: bool = False
- ) -> None:
- self._send(
- {
- "type": "update-item",
- "item": {
- "title": title,
- "enabled": enabled,
- "hidden": hidden,
- "__id": id,
- },
- "seq_id": -1,
- }
- )
-
- def _remove_permuter(self, slot_index: int) -> None:
- slot = self._client_slots[slot_index]
- if not slot.permuter:
- return
- handle = slot.permuter
- self._io_queue.put((None, (handle, IoUserRemovePermuter())))
-
- def _quit(self) -> None:
- self._io_queue.put((None, IoShutdown()))
-
- def _read_loop(self) -> None:
- while True:
- resp_str = self._proc_stdout.readline()
- if not resp_str:
- break
- try:
- resp = json.loads(resp_str)
- except Exception:
- raise Exception(f"Failed to parse systray JSON: {resp_str}") from None
- if resp["type"] == "clicked":
- id = resp["__id"]
- if id in self._onclick:
- self._onclick[id]()
-
- def _permuter_slot(self, perm: Permuter) -> Optional[ClientSlot]:
- for slot in self._client_slots:
- if slot.permuter is not None and self._permuters[slot.permuter] is perm:
- return slot
- return None
-
- def _update_permuter(self, perm: Permuter, slot: ClientSlot) -> None:
- self._update_item(
- slot.iterations_id,
- f"Iterations: {perm.iterations}",
- )
- self._update_item(
- slot.improvements_id,
- f"Improvements found: {perm.improvements}",
- )
-
- def _update_status(self) -> None:
- if self._status == SystrayStatus.CONNECTING:
- status = "Reconnecting..."
- icon = "working"
- elif self._status == SystrayStatus.RECONNECTING:
- status = "Disconnected, will reconnect..."
- icon = "fail"
- elif self._status == SystrayStatus.CONNECTED:
- if self._permuters:
- status = "Currently permuting:"
- icon = "working"
- else:
- status = "Not running"
- icon = "passive"
- elif self._status == SystrayStatus.FAILED:
- if self._fail_message:
- status = f"Error: {self._fail_message}"
- else:
- status = "Error occurred"
- icon = "fail"
- else:
- assert False, f"bad status {self._status}"
-
- self._update_item(self._status_id, status)
- if self._current_icon != icon:
- self._current_icon = icon
- self._send(
- {
- "type": "update-menu",
- "menu": {
- "tooltip": self._MENU_TOOLTIP,
- "icon": self._icons[icon],
- },
- }
- )
-
- def _fill_slots(self) -> None:
- has_more = False
- while True:
- key = next((k for k, p in self._permuters.items() if p.slot is None), None)
- if key is None:
- break
- chosen_slot: Optional[ClientSlot] = None
- for i in range(self._CLIENT_SLOTS - 1, -1, -1):
- slot = self._client_slots[i]
- if slot.permuter is None:
- chosen_slot = slot
- elif chosen_slot is not None:
- break
- if chosen_slot is None:
- has_more = True
- break
- perm = self._permuters[key]
- perm.slot = chosen_slot
- chosen_slot.permuter = key
- self._update_permuter(perm, chosen_slot)
- self._update_item(
- chosen_slot.menu_id, f"{perm.fn_name} ({perm.nickname})", enabled=True
- )
- self._update_item(self._more_id, "More...", hidden=not has_more)
-
- def _hide_slot(self, slot: ClientSlot) -> None:
- if slot.permuter is not None:
- self._update_item(slot.menu_id, "", hidden=True)
- slot.permuter = None
-
- def server_reconnecting(self) -> None:
- self._status = SystrayStatus.CONNECTING
- self._update_status()
-
- def server_connected(self) -> None:
- self._status = SystrayStatus.CONNECTED
- self._update_status()
-
- def server_failed(self, graceful: bool, message: Optional[str] = None) -> None:
- self._status = SystrayStatus.RECONNECTING if graceful else SystrayStatus.FAILED
- self._fail_message = message
- self._permuters = {}
- self._update_status()
- for slot in self._client_slots:
- self._hide_slot(slot)
- self._fill_slots()
-
- def connect(self, handle: PermuterHandle, nickname: str, fn_name: str) -> None:
- perm = Permuter(nickname, fn_name)
- self._permuters[handle] = perm
- self._fill_slots()
- self._update_status()
-
- def disconnect(self, handle: PermuterHandle) -> None:
- slot = self._permuters[handle].slot
- del self._permuters[handle]
- self._update_status()
- if slot:
- self._hide_slot(slot)
- self._fill_slots()
-
- def work_done(self, handle: PermuterHandle, is_improvement: bool) -> None:
- perm = self._permuters[handle]
- perm.iterations += 1
- if is_improvement:
- perm.improvements += 1
- if perm.slot and time.time() > perm.last_systray_update + self._UPDATE_INTERVAL:
- perm.last_systray_update = time.time()
- self._update_permuter(perm, perm.slot)
-
- def stop(self) -> None:
- try:
- self._send({"type": "exit"})
- except BrokenPipeError:
- # The systray process may have been killed by Ctrl+C.
- pass
- self._proc.wait()
- self._read_thread.join()
-
-
-class Reconnector:
- _RESET_BACKOFF_AFTER_UPTIME: float = 60.0
- _RANDOM_ADDEND_MAX: float = 60.0
- _BACKOFF_MULTIPLIER: float = 2.0
- _INITIAL_DELAY: float = 5.0
-
- _io_queue: "queue.Queue[IoActivity]"
- _reconnect_token: CancelToken
- _reconnect_delay: float
- _reconnect_timer: Optional[threading.Timer]
- _start_time: float
- _stop_time: float
-
- def __init__(self, io_queue: "queue.Queue[IoActivity]") -> None:
- self._io_queue = io_queue
- self._reconnect_token = CancelToken()
- self._reconnect_delay = self._INITIAL_DELAY
- self._reconnect_timer = None
- self._start_time = self._stop_time = time.time()
-
- def mark_start(self) -> None:
- self._start_time = time.time()
-
- def mark_stop(self) -> None:
- self._stop_time = time.time()
-
- def stop(self) -> None:
- self._reconnect_token.cancelled = True
- if self._reconnect_timer is not None:
- self._reconnect_timer.cancel()
- self._reconnect_timer.join()
- self._reconnect_timer = None
-
- def reconnect_eventually(self) -> int:
- if self._stop_time - self._start_time > self._RESET_BACKOFF_AFTER_UPTIME:
- delay = self._reconnect_delay = self._INITIAL_DELAY
- else:
- delay = self._reconnect_delay
- self._reconnect_delay = (
- self._reconnect_delay * self._BACKOFF_MULTIPLIER
- + random.uniform(1.0, self._RANDOM_ADDEND_MAX)
- )
- token = CancelToken()
- self._reconnect_token = token
- self._reconnect_timer = threading.Timer(
- delay, lambda: self._io_queue.put((token, IoReconnect()))
- )
- self._reconnect_timer.daemon = True
- self._reconnect_timer.start()
- return int(delay)
-
-
-def main_loop(
- io_queue: "queue.Queue[IoActivity]",
- server: Server,
- systray: SystrayState,
-) -> None:
- reconnector = Reconnector(io_queue)
- handle_clients: Dict[PermuterHandle, Client] = {}
- while True:
- token, activity = io_queue.get()
- if token and token.cancelled:
- continue
-
- if not isinstance(activity, tuple):
- if isinstance(activity, IoShutdown):
- break
-
- elif isinstance(activity, IoReconnect):
- print("reconnecting...")
- try:
- systray.server_reconnecting()
- reconnector.mark_start()
- server.start()
- systray.server_connected()
- except EOFError:
- delay = reconnector.reconnect_eventually()
- print(f"failed again, reconnecting in {delay} seconds...")
- systray.server_failed(True)
- except ServerError as e:
- print("failed!", e.message)
- systray.server_failed(False, e.message)
- except Exception:
- print("failed!")
- traceback.print_exc()
- systray.server_failed(False)
-
- elif isinstance(activity, IoServerFailed):
- if activity.message:
- print("Server error:", activity.message)
- print("disconnected from permuter@home")
- server.stop()
- reconnector.mark_stop()
- systray.server_failed(activity.graceful, activity.message)
-
- if activity.graceful:
- delay = reconnector.reconnect_eventually()
- print(f"will reconnect in {delay} seconds...")
-
- else:
- static_assert_unreachable(activity)
-
- else:
- handle, msg = activity
-
- if isinstance(msg, IoConnect):
- client = msg.client
- handle_clients[handle] = client
- systray.connect(handle, client.nickname, msg.fn_name)
- print(f"[{client.nickname}] connected ({msg.fn_name})")
-
- elif isinstance(msg, IoDisconnect):
- systray.disconnect(handle)
- nickname = handle_clients[handle].nickname
- del handle_clients[handle]
- print(f"[{nickname}] {msg.reason}")
-
- elif isinstance(msg, IoImmediateDisconnect):
- print(f"[{msg.client.nickname}] {msg.reason}")
-
- elif isinstance(msg, IoWorkDone):
- # TODO: statistics
- systray.work_done(handle, msg.is_improvement)
-
- elif isinstance(msg, IoUserRemovePermuter):
- server.remove_permuter(handle)
-
- else:
- static_assert_unreachable(msg)
-
-
-def server_main(options: ServerOptions, use_systray: bool) -> None:
- io_queue: "queue.Queue[IoActivity]" = queue.Queue()
- config = read_config()
-
- systray: SystrayState
- if use_systray:
- systray = RealSystrayState(config, io_queue)
- else:
- systray = SystrayState()
-
- try:
- server = Server(options, config, io_queue)
- server.start()
-
- try:
- systray.server_connected()
- main_loop(io_queue, server, systray)
- finally:
- server.stop()
- finally:
- systray.stop()
diff --git a/tools/decomp-permuter/src/net/cmd/setup.py b/tools/decomp-permuter/src/net/cmd/setup.py
deleted file mode 100644
index 8238ded2b1..0000000000
--- a/tools/decomp-permuter/src/net/cmd/setup.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from argparse import ArgumentParser, Namespace
-import base64
-import os
-import random
-import string
-import sys
-from typing import Optional
-
-from nacl.public import SealedBox
-from nacl.signing import SigningKey, VerifyKey
-
-from .base import Command
-from ..core import connect, read_config, sign_with_magic, write_config
-from .util import ask
-
-
-class SetupCommand(Command):
- command = "setup"
- help = """Set up permuter@home. This will require someone else to grant you
- access to the central server."""
-
- @staticmethod
- def add_arguments(parser: ArgumentParser) -> None:
- pass
-
- @staticmethod
- def run(args: Namespace) -> None:
- _run_initial_setup()
-
-
-def _random_name() -> str:
- return "".join(random.choice(string.ascii_lowercase) for _ in range(5))
-
-
-def _run_initial_setup() -> None:
- config = read_config()
- signing_key: Optional[SigningKey] = config.signing_key
- if not signing_key or not ask("Keep previous secret key", default=True):
- signing_key = SigningKey.generate()
- config.signing_key = signing_key
- write_config(config)
- verify_key = signing_key.verify_key
-
- nickname: Optional[str] = config.initial_setup_nickname
- if not nickname or not ask(f"Keep previous nickname [{nickname}]", default=True):
- default_nickname = os.environ.get("USER") or _random_name()
- nickname = (
- input(f"Nickname [default: {default_nickname}]: ") or default_nickname
- )
- config.initial_setup_nickname = nickname
- write_config(config)
-
- signed_nickname = sign_with_magic(b"NAME", signing_key, nickname.encode("utf-8"))
-
- vouch_data = verify_key.encode() + signed_nickname
- vouch_text = base64.b64encode(vouch_data).decode("utf-8")
- print("Ask someone to run the following command:")
- print(f"./pah.py vouch {vouch_text}")
- print()
- print("They should give you a token back in return. Paste that here:")
- inp = input().strip()
-
- try:
- token = base64.b64decode(inp.encode("utf-8"))
- data = SealedBox(signing_key.to_curve25519_private_key()).decrypt(token)
- config.server_address = data[32:].decode("utf-8")
- config.server_verify_key = VerifyKey(data[:32])
- config.initial_setup_nickname = None
- except Exception:
- print("Invalid token!")
- sys.exit(1)
-
- print(f"Server: {config.server_address}")
- print("Testing connection...")
-
- port = connect(config)
- port.send_json({"method": "ping"})
- port.receive_json()
-
- try:
- write_config(config)
- except Exception as e:
- print("Failed to write config:", e)
- sys.exit(1)
-
- print("permuter@home successfully set up!")
diff --git a/tools/decomp-permuter/src/net/cmd/systray/.gitignore b/tools/decomp-permuter/src/net/cmd/systray/.gitignore
deleted file mode 100644
index 49f8d190ce..0000000000
--- a/tools/decomp-permuter/src/net/cmd/systray/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-permuter-systray
-permuter-systray.exe
diff --git a/tools/decomp-permuter/src/net/cmd/systray/LICENSE b/tools/decomp-permuter/src/net/cmd/systray/LICENSE
deleted file mode 100644
index dc9823bd1e..0000000000
--- a/tools/decomp-permuter/src/net/cmd/systray/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2017 Zack Young
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/tools/decomp-permuter/src/net/cmd/systray/README.md b/tools/decomp-permuter/src/net/cmd/systray/README.md
deleted file mode 100644
index 5dee2f87ea..0000000000
--- a/tools/decomp-permuter/src/net/cmd/systray/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# systray
-
-This directory contains a Go application that shows a system tray, which the Python code interacts with.
-
-It is a fork of https://github.com/felixhao28/systray-portable.
-
-To build it:
-
-- install Go
-- if on Linux, install dependencies: `libgtk-3-dev`, `libappindicator3-dev`
-- run `go build`
-
-If on Windows, this needs to be done *outside* of WSL.
diff --git a/tools/decomp-permuter/src/net/cmd/systray/go.mod b/tools/decomp-permuter/src/net/cmd/systray/go.mod
deleted file mode 100644
index 4abd8814ce..0000000000
--- a/tools/decomp-permuter/src/net/cmd/systray/go.mod
+++ /dev/null
@@ -1,7 +0,0 @@
-module permuter-systray
-
-go 1.15
-
-require github.com/getlantern/systray v1.1.0
-
-replace github.com/getlantern/systray v1.1.0 => github.com/simonlindholm/systray v1.1.1-0.20210502122945-b7c77212cd56
diff --git a/tools/decomp-permuter/src/net/cmd/systray/go.sum b/tools/decomp-permuter/src/net/cmd/systray/go.sum
deleted file mode 100644
index bd6cf25417..0000000000
--- a/tools/decomp-permuter/src/net/cmd/systray/go.sum
+++ /dev/null
@@ -1,4 +0,0 @@
-github.com/simonlindholm/systray v1.1.1-0.20210502122945-b7c77212cd56 h1:UZcM1HdV25CQhhJD340jxRLRGl0V11V0wIoUDKTOZMI=
-github.com/simonlindholm/systray v1.1.1-0.20210502122945-b7c77212cd56/go.mod h1:N5dpnnWiJhCxh+gXuNgDS2p5MjgcVR/TGwWuaDc4gLk=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-linux-x86_64 b/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-linux-x86_64
deleted file mode 100755
index d23a6990d3..0000000000
Binary files a/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-linux-x86_64 and /dev/null differ
diff --git a/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-macos-x86_64 b/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-macos-x86_64
deleted file mode 100755
index 4eee7ff130..0000000000
Binary files a/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-macos-x86_64 and /dev/null differ
diff --git a/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-win-x86_64.exe b/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-win-x86_64.exe
deleted file mode 100755
index 8de9fc216b..0000000000
Binary files a/tools/decomp-permuter/src/net/cmd/systray/prebuilt/permuter-systray-win-x86_64.exe and /dev/null differ
diff --git a/tools/decomp-permuter/src/net/cmd/systray/tray.go b/tools/decomp-permuter/src/net/cmd/systray/tray.go
deleted file mode 100644
index 67c387844a..0000000000
--- a/tools/decomp-permuter/src/net/cmd/systray/tray.go
+++ /dev/null
@@ -1,287 +0,0 @@
-package main
-
-import (
- "bufio"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "os"
- "os/signal"
- "reflect"
- "strings"
- "syscall"
-
- "github.com/getlantern/systray"
-)
-
-func main() {
- systray.Run(onReady, onExit)
-}
-
-func onExit() {
- os.Exit(0)
-}
-
-// Item represents an item in the menu
-type Item struct {
- Icon string `json:"icon"`
- Title string `json:"title"`
- Tooltip string `json:"tooltip"`
- Enabled bool `json:"enabled"`
- Checked bool `json:"checked"`
- Hidden bool `json:"hidden"`
- Items []Item `json:"items"`
- InternalID int `json:"__id"`
-}
-
-// Menu has an icon, title and list of items
-type Menu struct {
- Icon string `json:"icon"`
- Title string `json:"title"`
- Tooltip string `json:"tooltip"`
- Items []Item `json:"items"`
-}
-
-// Action for an item?..
-type Action struct {
- Type string `json:"type"`
- Item Item `json:"item"`
- Menu Menu `json:"menu"`
-}
-
-// ClickEvent for an click event
-type ClickEvent struct {
- Type string `json:"type"`
- InternalID int `json:"__id"`
-}
-
-func readJSON(reader *bufio.Reader, v interface{}) error {
- input, err := reader.ReadString('\n')
- if err != nil {
- return err
- }
- if len(input) < 1 {
- return fmt.Errorf("Empty line")
- }
-
- lineReader := strings.NewReader(input[0 : len(input)-1])
- if err := json.NewDecoder(lineReader).Decode(v); err != nil {
- return err
- }
-
- return nil
-}
-
-func addMenuItem(items *[]*systray.MenuItem, seqID2InternalID *[]int, internalID2SeqID *map[int]int, item *Item, parent *systray.MenuItem) {
- if item.Title == "" {
- systray.AddSeparator()
- *items = append(*items, nil)
- } else {
- var menuItem *systray.MenuItem
- if parent == nil {
- menuItem = systray.AddMenuItem(item.Title, item.Tooltip)
- } else {
- menuItem = parent.AddSubMenuItem(item.Title, item.Tooltip)
- }
- if item.Checked {
- menuItem.Check()
- } else {
- menuItem.Uncheck()
- }
- if item.Enabled {
- menuItem.Enable()
- } else {
- menuItem.Disable()
- }
- if len(item.Icon) > 0 {
- icon, err := base64.StdEncoding.DecodeString(item.Icon)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- } else {
- menuItem.SetIcon(icon)
- }
- }
- for i := 0; i < len(item.Items); i++ {
- subitem := item.Items[i]
- addMenuItem(items, seqID2InternalID, internalID2SeqID, &subitem, menuItem)
- }
- if item.Hidden {
- menuItem.Hide()
- }
- *items = append(*items, menuItem)
- }
- seqID := len(*items) - 1
- (*internalID2SeqID)[item.InternalID] = seqID
- *seqID2InternalID = append(*seqID2InternalID, item.InternalID)
-}
-
-func onReady() {
- signalChannel := make(chan os.Signal, 2)
- signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
- go func() {
- for sig := range signalChannel {
- switch sig {
- case os.Interrupt, syscall.SIGTERM:
- // handle SIGINT, SIGTERM
- fmt.Fprintln(os.Stderr, "Quit")
- systray.Quit()
- default:
- fmt.Fprintln(os.Stderr, "Unhandled signal:", sig)
- }
- }
- }()
-
- items := make([]*systray.MenuItem, 0)
- seqID2InternalID := make([]int, 0)
- internalID2SeqID := make(map[int]int)
- fmt.Println(`{"type": "ready"}`)
- reader := bufio.NewReader(os.Stdin)
-
- var menu Menu
- if err := readJSON(reader, &menu); err != nil {
- fmt.Fprintln(os.Stderr, err)
- systray.Quit()
- return
- }
-
- icon, err := base64.StdEncoding.DecodeString(menu.Icon)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- systray.Quit()
- return
- }
-
- systray.SetIcon(icon)
- systray.SetTitle(menu.Title)
- systray.SetTooltip(menu.Tooltip)
-
- updateItem := func(action Action) {
- item := action.Item
- seqID := internalID2SeqID[action.Item.InternalID]
- menuItem := items[seqID]
- if menuItem == nil {
- return
- }
- if item.Hidden {
- menuItem.Hide()
- } else {
- if item.Checked {
- menuItem.Check()
- } else {
- menuItem.Uncheck()
- }
- if item.Enabled {
- menuItem.Enable()
- } else {
- menuItem.Disable()
- }
- menuItem.SetTitle(item.Title)
- menuItem.SetTooltip(item.Tooltip)
- if len(item.Icon) > 0 {
- icon, err := base64.StdEncoding.DecodeString(item.Icon)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- } else {
- menuItem.SetIcon(icon)
- }
- }
- menuItem.Show()
- for _, child := range item.Items {
- seqID = internalID2SeqID[child.InternalID]
- items[seqID].Show()
- }
- }
- }
-
- updateMenu := func(action Action) {
- m := action.Menu
- if menu.Title != m.Title {
- menu.Title = m.Title
- systray.SetTitle(menu.Title)
- }
- if menu.Icon != m.Icon && m.Icon != "" {
- menu.Icon = m.Icon
- icon, err := base64.StdEncoding.DecodeString(menu.Icon)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- } else {
- systray.SetIcon(icon)
- }
- }
- if menu.Tooltip != m.Tooltip {
- menu.Tooltip = m.Tooltip
- systray.SetTooltip(menu.Tooltip)
- }
- }
-
- update := func(action Action) {
- switch action.Type {
- case "update-item":
- updateItem(action)
- case "update-menu":
- updateMenu(action)
- case "update-item-and-menu":
- updateItem(action)
- updateMenu(action)
- case "exit":
- systray.Quit()
- }
- }
-
- for i := 0; i < len(menu.Items); i++ {
- item := menu.Items[i]
- addMenuItem(&items, &seqID2InternalID, &internalID2SeqID, &item, nil)
- }
-
- go func(reader *bufio.Reader) {
- for {
- var action Action
- if err := readJSON(reader, &action); err != nil {
- fmt.Fprintln(os.Stderr, err)
- systray.Quit()
- break
- }
- update(action)
- }
- }(reader)
-
- stdoutEnc := json.NewEncoder(os.Stdout)
- for {
- itemsCnt := 0
- for _, ch := range items {
- if ch != nil {
- itemsCnt++
- }
- }
- cases := make([]reflect.SelectCase, itemsCnt)
- caseCnt2SeqID := make([]int, len(items))
- itemsCnt = 0
- for i, ch := range items {
- if ch == nil {
- continue
- }
- cases[itemsCnt] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch.ClickedCh)}
- caseCnt2SeqID[itemsCnt] = i
- itemsCnt++
- }
-
- remaining := len(cases)
- for remaining > 0 {
- chosen, _, ok := reflect.Select(cases)
- if !ok {
- // The chosen channel has been closed, so zero out the channel to disable the case
- cases[chosen].Chan = reflect.ValueOf(nil)
- remaining--
- continue
- }
- seqID := caseCnt2SeqID[chosen]
- err := stdoutEnc.Encode(ClickEvent{
- Type: "clicked",
- InternalID: seqID2InternalID[seqID],
- })
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- }
- }
- }
-}
diff --git a/tools/decomp-permuter/src/net/cmd/util.py b/tools/decomp-permuter/src/net/cmd/util.py
deleted file mode 100644
index dfe9b47e40..0000000000
--- a/tools/decomp-permuter/src/net/cmd/util.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import sys
-
-
-def ask(msg: str, *, default: bool) -> bool:
- if default:
- msg += " (Y/n)? "
- else:
- msg += " (y/N)? "
- res = input(msg).strip().lower()
- if not res:
- return default
- if res in ["y", "yes", "n", "no"]:
- return res[0] == "y"
- print("Bad response!")
- sys.exit(1)
diff --git a/tools/decomp-permuter/src/net/cmd/vouch.py b/tools/decomp-permuter/src/net/cmd/vouch.py
deleted file mode 100644
index 82b19a8b08..0000000000
--- a/tools/decomp-permuter/src/net/cmd/vouch.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from argparse import ArgumentParser, Namespace
-import base64
-import sys
-
-from nacl.encoding import HexEncoder
-from nacl.public import SealedBox
-from nacl.signing import VerifyKey
-
-from ..core import connect, read_config, verify_with_magic
-from .base import Command
-from .util import ask
-
-
-class VouchCommand(Command):
- command = "vouch"
- help = "Give someone access to the central server."
-
- @staticmethod
- def add_arguments(parser: ArgumentParser) -> None:
- parser.add_argument(
- "magic",
- help="Opaque hex string generated by 'setup'.",
- )
-
- @staticmethod
- def run(args: Namespace) -> None:
- run_vouch(args.magic)
-
-
-def run_vouch(magic: str) -> None:
- try:
- vouch_data = base64.b64decode(magic.encode("utf-8"))
- verify_key = VerifyKey(vouch_data[:32])
- signed_nickname = vouch_data[32:]
- msg = verify_with_magic(b"NAME", verify_key, signed_nickname)
- nickname = msg.decode("utf-8")
- except Exception:
- print("Could not parse data!")
- sys.exit(1)
-
- try:
- config = read_config()
- port = connect(config)
- port.send_json(
- {
- "method": "vouch",
- "who": verify_key.encode(HexEncoder).decode("utf-8"),
- "signed_name": HexEncoder.encode(signed_nickname).decode("utf-8"),
- }
- )
- port.receive_json()
- except Exception as e:
- print(f"Error: {e}")
- sys.exit(1)
-
- if not ask(f"Grant permuter server access to {nickname}", default=True):
- return
-
- try:
- port.send_json({})
- port.receive_json()
- except Exception as e:
- print(f"Failed to grant access: {e}")
- sys.exit(1)
-
- assert config.server_address, "checked by connect"
- assert config.server_verify_key, "checked by connect"
- data = config.server_verify_key.encode() + config.server_address.encode("utf-8")
- token = SealedBox(verify_key.to_curve25519_public_key()).encrypt(data)
- print("Granted!")
- print()
- print("Send them the following token:")
- print(base64.b64encode(token).decode("utf-8"))
diff --git a/tools/decomp-permuter/src/net/controller/.gitignore b/tools/decomp-permuter/src/net/controller/.gitignore
deleted file mode 100644
index a2274adae7..0000000000
--- a/tools/decomp-permuter/src/net/controller/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-target/
-config.toml
-*.json
diff --git a/tools/decomp-permuter/src/net/controller/Cargo.lock b/tools/decomp-permuter/src/net/controller/Cargo.lock
deleted file mode 100644
index 9c9fab8c19..0000000000
--- a/tools/decomp-permuter/src/net/controller/Cargo.lock
+++ /dev/null
@@ -1,607 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-[[package]]
-name = "argh"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91792f088f87cdc7a2cfb1d617fa5ea18d7f1dc22ef0e1b5f82f3157cdc522be"
-dependencies = [
- "argh_derive",
- "argh_shared",
-]
-
-[[package]]
-name = "argh_derive"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4eb0c0c120ad477412dc95a4ce31e38f2113e46bd13511253f79196ca68b067"
-dependencies = [
- "argh_shared",
- "heck",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "argh_shared"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "781f336cc9826dbaddb9754cb5db61e64cab4f69668bd19dcc4a0394a86f4cb1"
-
-[[package]]
-name = "autocfg"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
-
-[[package]]
-name = "bitflags"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-
-[[package]]
-name = "bytes"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
-
-[[package]]
-name = "cc"
-version = "1.0.67"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "chrono"
-version = "0.4.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
-dependencies = [
- "libc",
- "num-integer",
- "num-traits",
- "time",
- "winapi",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "heck"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
-dependencies = [
- "unicode-segmentation",
-]
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "hex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
-[[package]]
-name = "instant"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "itoa"
-version = "0.4.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
-
-[[package]]
-name = "libc"
-version = "0.2.93"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
-
-[[package]]
-name = "libsodium-sys"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a685b64f837b339074115f2e7f7b431ac73681d08d75b389db7498b8892b8a58"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
-]
-
-[[package]]
-name = "lock_api"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
-dependencies = [
- "scopeguard",
-]
-
-[[package]]
-name = "log"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "memchr"
-version = "2.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
-
-[[package]]
-name = "mio"
-version = "0.7.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
-dependencies = [
- "libc",
- "log",
- "miow",
- "ntapi",
- "winapi",
-]
-
-[[package]]
-name = "miow"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "ntapi"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "num-integer"
-version = "0.1.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "num_cpus"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
-
-[[package]]
-name = "pahserver"
-version = "0.0.1"
-dependencies = [
- "argh",
- "chrono",
- "hex",
- "pin-project",
- "serde",
- "serde_json",
- "serde_tuple",
- "slotmap",
- "sodiumoxide",
- "tempfile",
- "tokio",
- "toml",
-]
-
-[[package]]
-name = "parking_lot"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
-dependencies = [
- "instant",
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
-dependencies = [
- "cfg-if",
- "instant",
- "libc",
- "redox_syscall",
- "smallvec",
- "winapi",
-]
-
-[[package]]
-name = "pin-project"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
-
-[[package]]
-name = "pkg-config"
-version = "0.3.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
-dependencies = [
- "unicode-xid",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
-dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
- "rand_hc",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
-dependencies = [
- "ppv-lite86",
- "rand_core",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
-dependencies = [
- "getrandom",
-]
-
-[[package]]
-name = "rand_hc"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
-dependencies = [
- "rand_core",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "ryu"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
-
-[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-
-[[package]]
-name = "serde"
-version = "1.0.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.64"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
-dependencies = [
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "serde_tuple"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4f025b91216f15a2a32aa39669329a475733590a015835d1783549a56d09427"
-dependencies = [
- "serde",
- "serde_tuple_macros",
-]
-
-[[package]]
-name = "serde_tuple_macros"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4076151d1a2b688e25aaf236997933c66e18b870d0369f8b248b8ab2be630d7e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "signal-hook-registry"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "slotmap"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585cd5dffe4e9e06f6dfdf66708b70aca3f781bed561f4f667b2d9c0d4559e36"
-dependencies = [
- "version_check",
-]
-
-[[package]]
-name = "smallvec"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
-
-[[package]]
-name = "sodiumoxide"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7038b67c941e23501573cb7242ffb08709abe9b11eb74bceff875bbda024a6a8"
-dependencies = [
- "libc",
- "libsodium-sys",
- "serde",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "tempfile"
-version = "3.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
-dependencies = [
- "cfg-if",
- "libc",
- "rand",
- "redox_syscall",
- "remove_dir_all",
- "winapi",
-]
-
-[[package]]
-name = "time"
-version = "0.1.43"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "tokio"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
-dependencies = [
- "autocfg",
- "bytes",
- "libc",
- "memchr",
- "mio",
- "num_cpus",
- "once_cell",
- "parking_lot",
- "pin-project-lite",
- "signal-hook-registry",
- "tokio-macros",
- "winapi",
-]
-
-[[package]]
-name = "tokio-macros"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "toml"
-version = "0.5.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "unicode-segmentation"
-version = "1.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
-
-[[package]]
-name = "version_check"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
-
-[[package]]
-name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/tools/decomp-permuter/src/net/controller/Cargo.toml b/tools/decomp-permuter/src/net/controller/Cargo.toml
deleted file mode 100644
index 40b050b0a5..0000000000
--- a/tools/decomp-permuter/src/net/controller/Cargo.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[package]
-name = "pahserver"
-version = "0.0.1"
-edition = "2018"
-resolver = "2"
-
-[dependencies]
-tokio = { version = "1", features = ["full"] }
-sodiumoxide = "0.2"
-toml = "0.5"
-serde = { version = "1.0", features = ["derive", "rc"] }
-serde_json = "1.0"
-serde_tuple = "0.5"
-hex = "0.4"
-argh = "0.1"
-tempfile = "3"
-slotmap = "1"
-pin-project = "1"
-chrono = "*"
diff --git a/tools/decomp-permuter/src/net/controller/README.md b/tools/decomp-permuter/src/net/controller/README.md
deleted file mode 100644
index 9661e6cf3d..0000000000
--- a/tools/decomp-permuter/src/net/controller/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# controller
-
-This directory contains code for the central permuter@home controller server,
-written in Rust. All p@h traffic passes through here.
-
-If you just want to run a regular p@h server, you don't need to care about this.
-
-To setup your own copy of the controller server:
-
-- Install Rust and (for the libsodium dependency) GCC.
-- Run `cargo build --release`.
-- Run `./target/release/pahserver setup --db path/to/database.json` and follow
- the instructions there. This will set the `priv_seed` part of `config.toml`, and
- set up an initial trusted client. The rest of `config.toml` can be copied from
- `config_example.toml`.
-- Set up a reverse proxy that forwards HTTPS traffic from an external port or route
- to HTTP for a port of your choice, e.g. using Nginx or Traefik.
- If applicable, configure your firewall to let the external port through.
-- Start the server with:
- ```
-./target/release/pahserver run --listen-on 0.0.0.0: --config config.toml --db path/to/database.json
-```
-and configure the system to run this at startup.
diff --git a/tools/decomp-permuter/src/net/controller/config_example.toml b/tools/decomp-permuter/src/net/controller/config_example.toml
deleted file mode 100644
index 9e09270859..0000000000
--- a/tools/decomp-permuter/src/net/controller/config_example.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-docker_image = ""
-priv_seed = "0000000000000000000000000000000000000000000000000000000000000000"
diff --git a/tools/decomp-permuter/src/net/controller/src/client.rs b/tools/decomp-permuter/src/net/controller/src/client.rs
deleted file mode 100644
index 72a486d1ba..0000000000
--- a/tools/decomp-permuter/src/net/controller/src/client.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-use std::collections::VecDeque;
-use std::sync::Arc;
-
-use serde::{Deserialize, Serialize};
-use serde_json::json;
-use tokio::sync::mpsc;
-
-use crate::db::UserId;
-use crate::flimsy_semaphore::FlimsySemaphore;
-use crate::port::{ReadPort, WritePort};
-use crate::stats;
-use crate::util::SimpleResult;
-use crate::{
- current_load, Permuter, PermuterData, PermuterId, PermuterResult, PermuterWork, ServerUpdate,
- State,
-};
-
-const MIN_PERMUTER_VERSION: u32 = 1;
-
-const CLIENT_MAX_QUEUES_SIZE: usize = 100;
-const MIN_PRIORITY: f64 = 0.001;
-const MAX_PRIORITY: f64 = 10.0;
-
-#[derive(Debug, Deserialize)]
-pub(crate) struct ConnectClientData {
- priority: f64,
-}
-
-#[derive(Deserialize)]
-#[serde(tag = "type", rename_all = "snake_case")]
-enum ClientMessage {
- Work { work: PermuterWork },
-}
-
-#[derive(Serialize)]
-struct PermuterResultMessage<'a> {
- server: String,
- #[serde(flatten)]
- update: &'a ServerUpdate,
-}
-
-async fn client_read(
- port: &mut ReadPort<'_>,
- perm_id: &PermuterId,
- semaphore: &FlimsySemaphore,
- state: &State,
-) -> SimpleResult<()> {
- loop {
- let msg = port.recv().await?;
- let msg: ClientMessage = serde_json::from_slice(&msg)?;
- let ClientMessage::Work { work } = msg;
-
- // Avoid the work and result queues growing indefinitely by restricting
- // their combined size with a semaphore.
- semaphore.acquire().await;
-
- let mut m = state.m.lock().unwrap();
- let perm = m.permuters.get_mut(perm_id).unwrap();
- if perm.work_queue.is_empty() {
- state.new_work_notification.notify_waiters();
- }
- perm.work_queue.push_back(work);
- }
-}
-
-async fn client_write(
- port: &mut WritePort<'_>,
- fn_name: &str,
- semaphore: &FlimsySemaphore,
- state: &State,
- mut result_rx: mpsc::UnboundedReceiver,
- client_id: &UserId,
-) -> SimpleResult<()> {
- loop {
- let res = result_rx.recv().await.unwrap();
- semaphore.release();
-
- match res {
- PermuterResult::NeedWork => {
- port.send_json(&json!({
- "type": "need_work",
- }))
- .await?;
- }
- PermuterResult::Result(server_id, server_name, server_update) => {
- port.send_json(&PermuterResultMessage {
- server: server_name,
- update: &server_update,
- })
- .await?;
-
- if let ServerUpdate::Result {
- compressed_source,
- ref more_props,
- ..
- } = server_update
- {
- if let Some(ref data) = compressed_source {
- port.send(data).await?;
- }
-
- let score = more_props.get("score").and_then(|score| score.as_i64());
- let outcome = if compressed_source.is_none() {
- stats::Outcome::Unhelpful
- } else if matches!(score, Some(0)) {
- stats::Outcome::Matched
- } else {
- stats::Outcome::Improved
- };
- state
- .log_stats(stats::Record::WorkDone {
- server: server_id,
- client: client_id.clone(),
- fn_name: fn_name.to_string(),
- outcome,
- })
- .await?;
- }
- }
- }
- }
-}
-
-pub(crate) async fn handle_connect_client<'a>(
- mut read_port: ReadPort<'a>,
- mut write_port: WritePort<'a>,
- who_id: UserId,
- who_name: &str,
- permuter_version: u32,
- state: &State,
- data: ConnectClientData,
-) -> SimpleResult<()> {
- if permuter_version < MIN_PERMUTER_VERSION {
- Err("Permuter version too old!")?;
- }
-
- if !(MIN_PRIORITY <= data.priority && data.priority <= MAX_PRIORITY) {
- Err("Priority out of range")?;
- }
-
- let load = current_load(state, Some(data.priority));
- write_port.send_json(&load).await?;
-
- let permuter_data = read_port.recv().await?;
- let mut permuter_data: PermuterData = serde_json::from_slice(&permuter_data)?;
- permuter_data.compressed_source = read_port.recv().await?;
- permuter_data.compressed_target_o_bin = read_port.recv().await?;
- write_port.send_json(&json!({})).await?;
-
- eprintln!(
- "[{}] start client ({}, {})",
- &who_name, &permuter_data.fn_name, data.priority
- );
-
- state
- .log_stats(stats::Record::ClientNewFunction {
- client: who_id.clone(),
- fn_name: permuter_data.fn_name.clone(),
- })
- .await?;
-
- let energy_add = 1.0 / data.priority;
- let fn_name = permuter_data.fn_name.clone();
-
- let (result_tx, result_rx) = mpsc::unbounded_channel();
- let semaphore = Arc::new(FlimsySemaphore::new(CLIENT_MAX_QUEUES_SIZE));
-
- let perm_id = {
- let mut m = state.m.lock().unwrap();
- let id = m.next_permuter_id;
- m.next_permuter_id += 1;
- m.permuters.insert(
- id,
- Permuter {
- data: permuter_data.into(),
- client_id: who_id.clone(),
- client_name: who_name.to_string(),
- work_queue: VecDeque::new(),
- result_tx: result_tx.clone(),
- semaphore: semaphore.clone(),
- priority: data.priority,
- energy_add,
- },
- );
- state.new_work_notification.notify_waiters();
- id
- };
-
- let r = tokio::try_join!(
- client_read(&mut read_port, &perm_id, &semaphore, state),
- client_write(
- &mut write_port,
- &fn_name,
- &semaphore,
- state,
- result_rx,
- &who_id
- )
- );
-
- state.m.lock().unwrap().permuters.remove(&perm_id);
- state.new_work_notification.notify_waiters();
- r?;
- Ok(())
-}
diff --git a/tools/decomp-permuter/src/net/controller/src/db.rs b/tools/decomp-permuter/src/net/controller/src/db.rs
deleted file mode 100644
index 8b608bd0dd..0000000000
--- a/tools/decomp-permuter/src/net/controller/src/db.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-use std::collections::HashMap;
-use std::convert::TryInto;
-
-use hex::FromHex;
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use serde_tuple::{Deserialize_tuple, Serialize_tuple};
-use sodiumoxide::crypto::sign;
-
-#[derive(Clone, Debug, Hash, Eq, PartialEq)]
-pub struct ByteString([u8; SIZE]);
-
-impl ByteString {
- fn to_hex(&self) -> String {
- hex::encode(&self.0)
- }
-
- fn from_hex(string: &str) -> Result, &'static str> {
- Ok(ByteString(
- Vec::from_hex(&string)
- .map_err(|_| "not a valid hex string")?
- .try_into()
- .map_err(|_| "byte string has wrong size")?,
- ))
- }
-}
-
-impl Serialize for ByteString {
- fn serialize(&self, serializer: S) -> Result
- where
- S: Serializer,
- {
- serializer.serialize_str(&self.to_hex())
- }
-}
-
-impl<'de, const SIZE: usize> Deserialize<'de> for ByteString {
- fn deserialize(deserializer: D) -> Result, D::Error>
- where
- D: Deserializer<'de>,
- {
- use serde::de::Error;
- let string = String::deserialize(deserializer)?;
- ByteString::from_hex(&string).map_err(Error::custom)
- }
-}
-
-pub type UserId = ByteString<32>;
-
-impl UserId {
- pub fn from_pubkey(key: &sign::PublicKey) -> UserId {
- ByteString(key.as_ref().try_into().unwrap())
- }
-
- pub fn to_pubkey(&self) -> sign::PublicKey {
- sign::PublicKey::from_slice(&self.0).unwrap()
- }
-}
-
-impl ByteString {
- pub fn to_seed(&self) -> sign::Seed {
- sign::Seed::from_slice(&self.0).unwrap()
- }
-}
-
-#[derive(Debug, Deserialize_tuple, Serialize_tuple)]
-pub struct Stats {
- pub iterations: u64,
- pub improvements: u64,
- pub matches: u64,
- pub functions: u64,
-}
-
-impl Default for Stats {
- fn default() -> Stats {
- Stats {
- iterations: 0,
- improvements: 0,
- matches: 0,
- functions: 0,
- }
- }
-}
-
-#[derive(Debug, Deserialize, Serialize)]
-pub struct User {
- pub trusted_by: Option,
- pub name: String,
- pub client_stats: Stats,
- pub server_stats: Stats,
-}
-
-#[derive(Debug, Deserialize, Serialize)]
-pub struct DB {
- pub users: HashMap,
- pub func_stats: HashMap,
- pub total_stats: Stats,
-}
-
-impl DB {
- pub fn func_stat(&mut self, fn_name: String) -> &mut Stats {
- self.func_stats
- .entry(fn_name)
- .or_insert_with(Stats::default)
- }
-}
diff --git a/tools/decomp-permuter/src/net/controller/src/flimsy_semaphore.rs b/tools/decomp-permuter/src/net/controller/src/flimsy_semaphore.rs
deleted file mode 100644
index b29b41fea2..0000000000
--- a/tools/decomp-permuter/src/net/controller/src/flimsy_semaphore.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use std::convert::TryInto;
-use std::sync::atomic::{AtomicIsize, Ordering};
-
-use tokio::sync::Notify;
-
-/// An unfair semaphore that allows overdrafts.
-pub struct FlimsySemaphore {
- notify: Notify,
- slots: AtomicIsize,
-}
-
-impl FlimsySemaphore {
- // Invariant: if `slots` has ever become non-positive, then if positive
- // there will be a notify token in circulation. Taking the token
- // synchronizes with a positive `slots`.
- pub fn new(limit: usize) -> FlimsySemaphore {
- FlimsySemaphore {
- notify: Notify::new(),
- slots: AtomicIsize::new(limit.try_into().unwrap()),
- }
- }
-
- pub fn acquire_ignore_limit(&self) {
- self.slots.fetch_add(-1, Ordering::Acquire);
- }
-
- pub async fn acquire(&self) {
- let mut was_woken = false;
- let mut val = self.slots.load(Ordering::Relaxed);
- loop {
- if val > 0 {
- match self.slots.compare_exchange(
- val,
- val - 1,
- Ordering::Acquire,
- Ordering::Relaxed,
- ) {
- Ok(_) => {
- if was_woken && val > 1 {
- self.notify.notify_one();
- }
- return;
- }
- Err(actually) => {
- val = actually;
- }
- }
- } else {
- self.notify.notified().await;
- was_woken = true;
- val = self.slots.load(Ordering::Relaxed);
- }
- }
- }
-
- pub fn release(&self) {
- if self.slots.fetch_add(1, Ordering::Release) == 0 {
- self.notify.notify_one();
- }
- }
-}
diff --git a/tools/decomp-permuter/src/net/controller/src/main.rs b/tools/decomp-permuter/src/net/controller/src/main.rs
deleted file mode 100644
index e50a91d531..0000000000
--- a/tools/decomp-permuter/src/net/controller/src/main.rs
+++ /dev/null
@@ -1,418 +0,0 @@
-#![allow(clippy::try_err)]
-
-use std::collections::{HashMap, VecDeque};
-use std::convert::TryInto;
-use std::default::Default;
-use std::io::ErrorKind;
-use std::sync::{Arc, Mutex};
-
-use argh::FromArgs;
-use serde::{Deserialize, Serialize};
-use serde_json::json;
-use slotmap::{new_key_type, SlotMap};
-use sodiumoxide::crypto::box_;
-use sodiumoxide::crypto::sign;
-use tokio::fs;
-use tokio::io::{AsyncReadExt, AsyncWriteExt};
-use tokio::net::tcp::{ReadHalf, WriteHalf};
-use tokio::net::{TcpListener, TcpStream};
-use tokio::sync::{mpsc, watch, Notify};
-use tokio::time;
-
-use crate::db::{ByteString, UserId};
-use crate::flimsy_semaphore::FlimsySemaphore;
-use crate::port::{ReadPort, WritePort};
-use crate::save::SaveableDB;
-use crate::util::SimpleResult;
-
-mod client;
-mod db;
-mod flimsy_semaphore;
-mod port;
-mod save;
-mod server;
-mod setup;
-mod stats;
-mod util;
-mod vouch;
-
-const HEARTBEAT_TIME: time::Duration = time::Duration::from_secs(300);
-
-#[derive(FromArgs)]
-/// The permuter@home control server.
-struct CmdOpts {
- #[argh(subcommand)]
- sub: SubCommand,
-}
-
-#[derive(FromArgs)]
-#[argh(subcommand)]
-enum SubCommand {
- RunServer(RunServerOpts),
- Setup(SetupOpts),
-}
-
-#[derive(FromArgs)]
-/// Run the permuter@home control server.
-#[argh(subcommand, name = "run")]
-struct RunServerOpts {
- /// ip:port to listen on (e.g. 0.0.0.0:1234)
- #[argh(option)]
- listen_on: String,
-
- /// path to TOML configuration file
- #[argh(option)]
- config: String,
-
- /// path to JSON database
- #[argh(option)]
- db: String,
-
- /// enable debug logging
- #[argh(switch)]
- debug: bool,
-}
-
-#[derive(FromArgs)]
-/// Setup initial database and config for permuter@home.
-#[argh(subcommand, name = "setup")]
-struct SetupOpts {
- /// path to JSON database
- #[argh(option)]
- db: String,
-}
-
-#[derive(Deserialize)]
-struct Config {
- docker_image: String,
- priv_seed: ByteString<32>,
-}
-
-#[derive(Debug, Deserialize, Serialize)]
-struct PermuterData {
- fn_name: String,
- #[serde(skip)]
- compressed_source: Vec,
- #[serde(skip)]
- compressed_target_o_bin: Vec,
- #[serde(flatten)]
- more_props: HashMap,
-}
-
-#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
-struct PermuterWork {
- seed: u64,
-}
-
-#[derive(Debug, Deserialize, Serialize)]
-#[serde(tag = "type", rename_all = "snake_case")]
-enum ServerUpdate {
- Result {
- #[serde(skip_serializing, default)]
- overhead_us: i64,
- #[serde(skip)]
- compressed_source: Option>,
- #[serde(default)]
- has_source: bool,
- #[serde(flatten)]
- more_props: HashMap,
- },
- InitDone {
- hash: String,
- },
- InitFailed {
- reason: String,
- },
- Disconnect,
-}
-
-#[derive(Debug)]
-enum PermuterResult {
- NeedWork,
- Result(UserId, String, ServerUpdate),
-}
-
-type PermuterId = u64;
-
-struct Permuter {
- data: Arc,
- client_id: UserId,
- client_name: String,
- work_queue: VecDeque,
- result_tx: mpsc::UnboundedSender,
- semaphore: Arc,
- priority: f64,
- energy_add: f64,
-}
-
-impl Permuter {
- fn send_result(&mut self, res: PermuterResult) {
- // We can't use a blocking semaphore acquire here, because we don't
- // want server sends to block on random client receives. In practice,
- // this is probably fine.
- let _ = self.result_tx.send(res);
- self.semaphore.acquire_ignore_limit();
- }
-}
-
-new_key_type! { struct ServerId; }
-
-struct ConnectedServer {
- min_priority: f64,
- num_cores: f64,
-}
-
-struct MutableState {
- servers: SlotMap,
- permuters: HashMap,
- next_permuter_id: PermuterId,
-}
-
-struct State {
- docker_image: String,
- debug: bool,
- sign_sk: sign::SecretKey,
- db: SaveableDB,
- stats_tx: mpsc::Sender,
- heartbeat_rx: watch::Receiver<()>,
- new_work_notification: Notify,
- m: Mutex,
-}
-
-impl State {
- async fn log_stats(&self, record: stats::Record) -> SimpleResult<()> {
- self.stats_tx
- .send(record)
- .await
- .map_err(|_| "stats thread died".into())
- }
-}
-
-#[derive(Deserialize)]
-#[serde(tag = "method", rename_all = "snake_case")]
-enum Request {
- Ping,
- Vouch(vouch::VouchData),
- ConnectServer(server::ConnectServerData),
- ConnectClient(client::ConnectClientData),
-}
-
-#[derive(Serialize)]
-struct Load {
- clients: usize,
- servers: usize,
- cores: f64,
-}
-
-#[tokio::main]
-async fn main() -> SimpleResult<()> {
- sodiumoxide::init().map_err(|()| "Failed to initialize cryptography library")?;
-
- let opts: CmdOpts = argh::from_env();
-
- match opts.sub {
- SubCommand::RunServer(opts) => run_server(opts).await?,
- SubCommand::Setup(opts) => setup::run_setup(opts)?,
- }
- Ok(())
-}
-
-async fn run_server(opts: RunServerOpts) -> SimpleResult<()> {
- let config: Config = toml::from_str(&fs::read_to_string(&opts.config).await?)?;
- let (_, sign_sk) = sign::keypair_from_seed(&config.priv_seed.to_seed());
-
- let (save_fut, db) = SaveableDB::open(&opts.db)?;
- tokio::spawn(async move {
- if let Err(e) = save_fut.await {
- eprintln!("Failed to save! {:?}", e);
- std::process::exit(1);
- }
- });
-
- let (stats_fut, stats_tx) = stats::stats_thread(&db);
- tokio::spawn(stats_fut);
-
- let (heartbeat_tx, heartbeat_rx) = watch::channel(());
-
- let state: &'static State = Box::leak(Box::new(State {
- docker_image: config.docker_image,
- debug: opts.debug,
- sign_sk,
- db,
- stats_tx,
- heartbeat_rx,
- new_work_notification: Notify::new(),
- m: Mutex::new(MutableState {
- servers: SlotMap::with_key(),
- permuters: HashMap::new(),
- next_permuter_id: 0,
- }),
- }));
-
- tokio::spawn(async move {
- loop {
- heartbeat_tx.send(()).expect("receiver is still alive");
- time::sleep(HEARTBEAT_TIME).await;
- }
- });
-
- let listener = TcpListener::bind(opts.listen_on).await?;
-
- loop {
- let (socket, _) = listener.accept().await?;
- tokio::spawn(async move {
- let mut who = "anonymous".to_string();
- if let Err(e) = handle_connection(socket, state, &mut who).await {
- if let Some(e) = e.downcast_ref::() {
- if matches!(
- e.kind(),
- ErrorKind::UnexpectedEof
- | ErrorKind::ConnectionReset
- | ErrorKind::TimedOut
- | ErrorKind::BrokenPipe
- ) {
- eprintln!("[{}] disconnected", &who);
- return;
- }
- }
- eprintln!("[{}] error: {:?}", &who, e);
- }
- });
- }
-}
-
-fn concat(a: &[T], b: &[T]) -> Vec {
- a.iter().chain(b).cloned().collect()
-}
-
-fn concat3(a: &[T], b: &[T], c: &[T]) -> Vec {
- a.iter().chain(b).chain(c).cloned().collect()
-}
-
-async fn handshake<'a>(
- mut rd: ReadHalf<'a>,
- mut wr: WriteHalf<'a>,
- sign_sk: &sign::SecretKey,
-) -> SimpleResult<(ReadPort<'a>, WritePort<'a>, UserId, u32)> {
- let mut buffer = [0; 4 + 32];
- rd.read_exact(&mut buffer).await?;
- let (magic, their_pk) = buffer.split_at(4);
- if magic != b"p@h0" {
- Err("Invalid protocol version")?;
- }
- let their_pk = box_::PublicKey::from_slice(&their_pk).unwrap();
-
- let (our_pk, our_sk) = box_::gen_keypair();
- let signed_data = concat3(b"HELLO:", their_pk.as_ref(), our_pk.as_ref());
- let signature = sign::sign_detached(&signed_data, &sign_sk);
- wr.write_all(&concat(our_pk.as_ref(), signature.as_ref()))
- .await?;
-
- let key = box_::precompute(&their_pk, &our_sk);
- let mut read_port = ReadPort::new(rd, &key);
- let write_port = WritePort::new(wr, &key);
-
- let reply = read_port.recv().await?;
- if reply.len() != 32 + 64 + 4 {
- Err("Failed to perform secret handshake")?;
- }
- let (client_ver_key, rest) = reply.split_at(32);
- let (client_signature, permuter_version) = rest.split_at(64);
- let client_ver_key = sign::PublicKey::from_slice(client_ver_key).unwrap();
- let client_signature = sign::Signature::from_slice(client_signature).unwrap();
- let permuter_version = u32::from_be_bytes(permuter_version.try_into().unwrap());
- let signed_data = concat(b"WORLD:", our_pk.as_ref());
- if !sign::verify_detached(&client_signature, &signed_data, &client_ver_key) {
- Err("Spoofed client signature!")?;
- }
-
- Ok((
- read_port,
- write_port,
- UserId::from_pubkey(&client_ver_key),
- permuter_version,
- ))
-}
-
-fn current_load(state: &State, priority: Option) -> Load {
- let m = state.m.lock().unwrap();
- let mut servers: usize = 0;
- let mut cores: f64 = 0.0;
- for server in m.servers.values() {
- if priority.map_or(true, |p| p >= server.min_priority) {
- servers += 1;
- cores += server.num_cores;
- }
- }
- Load {
- clients: m.permuters.len(),
- servers,
- cores,
- }
-}
-
-async fn handle_connection(
- mut socket: TcpStream,
- state: &State,
- out_name: &mut String,
-) -> SimpleResult<()> {
- let (rd, wr) = socket.split();
- let (mut read_port, mut write_port, user_id, permuter_version) =
- handshake(rd, wr, &state.sign_sk).await?;
- let name = match state.db.read(|db| {
- let user = db.users.get(&user_id)?;
- Some(user.name.clone())
- }) {
- Some(name) => name,
- None => {
- write_port.send_error("Access denied!").await?;
- Err("Unknown client!")?
- }
- };
- *out_name = name.clone();
- eprintln!("[{}] connected (v {})", &name, permuter_version);
- if state.debug {
- read_port.set_debug(&name);
- write_port.set_debug(&name);
- }
- write_port.send_json(&json!({})).await?;
-
- let request = read_port.recv().await?;
- let request: Request = serde_json::from_slice(&request)?;
- match request {
- Request::Ping => {
- eprintln!("[{}] ping", &name);
- let load = current_load(state, None);
- write_port.send_json(&load).await?;
- }
- Request::Vouch(data) => {
- vouch::handle_vouch(read_port, write_port, user_id, &name, state, data).await?;
- }
- Request::ConnectServer(data) => {
- server::handle_connect_server(
- read_port,
- write_port,
- user_id,
- &name,
- permuter_version,
- state,
- data,
- )
- .await?;
- }
- Request::ConnectClient(data) => {
- client::handle_connect_client(
- read_port,
- write_port,
- user_id,
- &name,
- permuter_version,
- state,
- data,
- )
- .await?;
- }
- };
-
- Ok(())
-}
diff --git a/tools/decomp-permuter/src/net/controller/src/port.rs b/tools/decomp-permuter/src/net/controller/src/port.rs
deleted file mode 100644
index 5e9c52eebb..0000000000
--- a/tools/decomp-permuter/src/net/controller/src/port.rs
+++ /dev/null
@@ -1,115 +0,0 @@
-use std::convert::TryInto;
-
-use chrono::Local;
-use serde::Serialize;
-use sodiumoxide::crypto::box_;
-use sodiumoxide::crypto::box_::{Nonce, PrecomputedKey};
-use tokio::io::{AsyncReadExt, AsyncWriteExt};
-use tokio::net::tcp::{ReadHalf, WriteHalf};
-
-use crate::util::SimpleResult;
-
-fn debug_print(action: &str, who: &str, msg: &[u8]) {
- let time = Local::now().format("%H:%M:%S:%f");
- if msg.len() <= 300 {
- let msg = String::from_utf8(
- msg.iter()
- .copied()
- .flat_map(std::ascii::escape_default)
- .collect(),
- )
- .unwrap();
- println!("{} debug: {} {}: {}", time, action, who, msg);
- } else {
- println!("{} debug: {} {}: {} bytes", time, action, who, msg.len());
- }
-}
-
-pub struct ReadPort<'a> {
- read_half: ReadHalf<'a>,
- key: PrecomputedKey,
- nonce: u64,
- debug_name: Option<&'a str>,
-}
-
-impl<'a> ReadPort<'a> {
- pub fn new(read_half: ReadHalf<'a>, key: &PrecomputedKey) -> Self {
- ReadPort {
- read_half,
- key: key.clone(),
- nonce: 0,
- debug_name: None,
- }
- }
-
- pub fn set_debug(&mut self, name: &'a str) {
- self.debug_name = Some(name);
- }
-
- pub async fn recv(&mut self) -> SimpleResult> {
- let len = self.read_half.read_u64().await?;
- if len >= (1 << 48) {
- Err("Unreasonable packet length")?
- }
- let mut buffer = vec![0; len.try_into()?];
- self.read_half.read_exact(&mut buffer).await?;
- let nonce = nonce_from_u64(self.nonce);
- self.nonce += 2;
- let data =
- box_::open_precomputed(&buffer, &nonce, &self.key).map_err(|()| "Failed to decrypt")?;
- if let Some(name) = self.debug_name {
- debug_print("Receive from", name, &data);
- }
- Ok(data)
- }
-}
-
-pub struct WritePort<'a> {
- write_half: WriteHalf<'a>,
- key: PrecomputedKey,
- nonce: u64,
- debug_name: Option<&'a str>,
-}
-
-impl<'a> WritePort<'a> {
- pub fn new(write_half: WriteHalf<'a>, key: &PrecomputedKey) -> Self {
- WritePort {
- write_half,
- key: key.clone(),
- nonce: 1,
- debug_name: None,
- }
- }
-
- pub fn set_debug(&mut self, name: &'a str) {
- self.debug_name = Some(name);
- }
-
- pub async fn send(&mut self, data: &[u8]) -> SimpleResult<()> {
- if let Some(name) = self.debug_name {
- debug_print("Send to", name, &data);
- }
- let nonce = nonce_from_u64(self.nonce);
- self.nonce += 2;
- let data = box_::seal_precomputed(data, &nonce, &self.key);
- self.write_half.write_u64(data.len() as u64).await?;
- self.write_half.write_all(&data).await?;
- Ok(())
- }
-
- pub async fn send_json(&mut self, value: &T) -> SimpleResult<()>
- where
- T: Serialize,
- {
- self.send(&serde_json::to_vec(value)?).await
- }
-
- pub async fn send_error(&mut self, message: &str) -> SimpleResult<()> {
- self.send_json(message).await
- }
-}
-
-fn nonce_from_u64(num: u64) -> Nonce {
- let nonce_bytes = [[0; 8], [0; 8], num.to_be_bytes()].concat();
- Nonce::from_slice(&nonce_bytes).unwrap()
-}
diff --git a/tools/decomp-permuter/src/net/controller/src/save.rs b/tools/decomp-permuter/src/net/controller/src/save.rs
deleted file mode 100644
index 883637279a..0000000000
--- a/tools/decomp-permuter/src/net/controller/src/save.rs
+++ /dev/null
@@ -1,158 +0,0 @@
-use std::future::Future;
-use std::io::Write;
-use std::path::{Path, PathBuf};
-use std::sync::{Arc, RwLock};
-use std::time::Duration;
-
-use tempfile::NamedTempFile;
-use tokio::sync::{mpsc, oneshot};
-use tokio::time::timeout;
-
-use crate::db::DB;
-use crate::util::{FutureExt, SimpleResult};
-
-const SAVE_INTERVAL: Duration = Duration::from_secs(30);
-
-enum SaveType {
- Delayed,
- Immediate(oneshot::Sender<()>),
-}
-
-struct InnerSaveableDB {
- db: DB,
- stale: bool,
- save_chan: mpsc::UnboundedSender,
-}
-
-#[derive(Clone)]
-pub struct SaveableDB(Arc>);
-
-async fn save_db_loop(
- db: SaveableDB,
- path: &Path,
- mut save_channel: mpsc::UnboundedReceiver,
-) -> SimpleResult<()> {
- loop {
- let mut done_chans = Vec::new();
- match save_channel.recv().await {
- None => return Ok(()),
- Some(SaveType::Immediate(chan)) => {
- done_chans.push(chan);
- }
- Some(SaveType::Delayed) => {
- // Wait for SAVE_INTERVAL or until we receive an Immediate save.
- let _ = timeout(SAVE_INTERVAL, async {
- loop {
- match save_channel.recv().await {
- None => {
- break;
- }
- Some(SaveType::Immediate(chan)) => {
- done_chans.push(chan);
- break;
- }
- Some(SaveType::Delayed) => {}
- };
- }
- })
- .await;
- }
- };
-
- // Clear the queue in case more messages have stacked up past an
- // Immediate. Receiver::try_recv() is temporarily dead as of tokio 1.4
- // (https://github.com/tokio-rs/tokio/issues/3350) due to a bug where
- // messages can be delayed, but in this case that doesn't matter.
- loop {
- match save_channel.recv().now_or_never().await {
- None | Some(None) => {
- break;
- }
- Some(Some(SaveType::Immediate(chan))) => {
- done_chans.push(chan);
- }
- Some(Some(SaveType::Delayed)) => {}
- };
- }
-
- // Mark the DB as non-stale, to start receiving save messages again.
- db.0.write().unwrap().stale = false;
-
- // Actually do the save, by first serializing, then atomically saving
- // the file by creating and renaming a temp file in the same directory.
- let data = db.read(|db| serde_json::to_string(&db).unwrap());
-
- let r: SimpleResult<()> = tokio::task::block_in_place(|| {
- let parent_dir = path.parent().unwrap_or_else(|| Path::new("."));
- let mut tempf = NamedTempFile::new_in(parent_dir)?;
- tempf.write_all(data.as_bytes())?;
- tempf.as_file().sync_all()?;
- tempf.persist(path)?;
- Ok(())
- });
- r?;
-
- for chan in done_chans {
- let _ = chan.send(());
- }
- }
-}
-
-impl SaveableDB {
- pub fn open(
- filename: &str,
- ) -> SimpleResult<(impl Future