nandi/frqpublic Fork 0
98d613511f9664af4d9779a07baf26ecb2feaeea
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Three buttons that did nothing now do what they say 98d6135 · on 98d613511f9664af4d9779a07baf26ecb2feaeea · nandi · 23h ago
README.md · 89 lines · 4.3 KBmarkdown
Blame HistoryOpen raw

The Nim core

The program. It owns the state, the screens, the IRC connection and the
message signing; Flutter is a renderer over the widget tree it emits, and
dart/frq_core is the FFI binding between them.

src/frq_core.nim        the C ABI: every exported symbol, and nothing else
src/frq/ui.nim          the widget tree, in the screens' own tag vocabulary
src/frq/cells.nim       every piece of state the screens read
src/frq/reducer.nim     every event they can send, and what it does
src/frq/model.nim       what a message and a room are
src/frq/rooms.nim       the list, the overview, and the read marker
src/frq/members.nim     who is in a channel, and what the server says of them
src/frq/conn.nim        the socket, the TLS, the line framing
src/frq/ircparse.nim    the IRC wire format
src/frq/handshake.nim   CAP and the SASL exchange inside it
src/frq/atproto.nim     handle → DID → PDS → session
src/frq/crypto.nim      OpenSSL, bound: SHA-256 and Ed25519
src/frq/msgsig.nim      signing a mutation so freeq will accept it
src/frq/emoji.nim       1,884 emoji — generated, see below
src/frq/glyphs.nim      a line split into words and pictures
src/frq/clock.nim       server time in the reader's own zone
src/frq/store.nim       what survives a restart
src/frq/screens/        connect, chats, chat, discover, settings

The ABI

Strings in, strings out, and JSON where the answer is not a single string —
deliberately, against a struct-based ABI: a struct means both sides agreeing
on a memory layout, and a field added later is a version skew that segfaults
instead of failing.

Two rules, both of which cost a segfault to rediscover:

  • Every string the core returns is the caller's to free, with frq_free.
    Nim's allocator is not Dart's.
  • frq_init exists and does nothing. It used to call NimMain, and on Linux
    --app:lib already runs the module initialisers from a library constructor
    — so calling it again ran every module's top-level code a second time, which
    for conn.nim meant open() on channels that were already open.

Things that will bite

  • Nothing is shared with the socket threads. There are two — one reader,
    one writer — and they speak only in channels. recvLine(timeout) does not
    time out on a TLS socket, so a single thread cannot both wait for the server
    and notice what the client wants to say.
  • --mm:orc, not refc. refc gives each thread its own GC heap, so the
    Socket the two threads share is a ref from another heap and dereferencing
    it segfaults.
  • Every intra-package import says frq/…. import conn and
    import frq/conn name the same file by two paths and Nim compiles it twice,
    giving two sets of globals and two states.
  • emoji.nim is generated. It was tools/emoji2nim.py reading the
    ClojureDart catalogue, which is gone; regenerating it now means going back to
    Unicode's emoji-test.txt and filtering to what the Twemoji pack draws.
  • The crypto is bindings, not implementations. crypto.nim is OpenSSL
    through EVP. The RFC 8032 vectors in tests/tcrypto.nim check that this
    drives it correctly — the seed in the right place, the one-shot signing form,
    the bytes out in the right order — and not that Ed25519 is implemented
    correctly, which is OpenSSL's problem.
just test nim              # the whole suite
just test nim tircparse    # one file
just build lib             # libfrqcore.so into build/nim

What is not here

frq.profile and frq.replies were never ported and went with the
ClojureDart rather than moving: a Bluesky profile behind a nick, and asking
freeq what a collapsed msgid was. Neither had a screen in this app to appear
on.

The profile card is state without a screen: profileViewing is moved by
nothing and rendered by nothing, and frq.profile was never ported.

The emoji picker, the overview strip and the lightbox used to be in that list.
They have screens now — the picker under the message it is for, the overview
as a pane above the compose bar, the lightbox as a panel over the
conversation — so the 🙂 chip, the Overview toggle and clicking a picture all
do what they look like they do.

Untested against a real account: nobody has watched freeq accept a SASL
challenge response or a signature. The shapes are checked and the curve is
OpenSSL's, but the server has not said yes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# The Nim core

The program. It owns the state, the screens, the IRC connection and the
message signing; Flutter is a renderer over the widget tree it emits, and
`dart/frq_core` is the FFI binding between them.

```
src/frq_core.nim        the C ABI: every exported symbol, and nothing else
src/frq/ui.nim          the widget tree, in the screens' own tag vocabulary
src/frq/cells.nim       every piece of state the screens read
src/frq/reducer.nim     every event they can send, and what it does
src/frq/model.nim       what a message and a room are
src/frq/rooms.nim       the list, the overview, and the read marker
src/frq/members.nim     who is in a channel, and what the server says of them
src/frq/conn.nim        the socket, the TLS, the line framing
src/frq/ircparse.nim    the IRC wire format
src/frq/handshake.nim   CAP and the SASL exchange inside it
src/frq/atproto.nim     handle → DID → PDS → session
src/frq/crypto.nim      OpenSSL, bound: SHA-256 and Ed25519
src/frq/msgsig.nim      signing a mutation so freeq will accept it
src/frq/emoji.nim       1,884 emoji — generated, see below
src/frq/glyphs.nim      a line split into words and pictures
src/frq/clock.nim       server time in the reader's own zone
src/frq/store.nim       what survives a restart
src/frq/screens/        connect, chats, chat, discover, settings
```

## The ABI

Strings in, strings out, and JSON where the answer is not a single string —
deliberately, against a struct-based ABI: a struct means both sides agreeing
on a memory layout, and a field added later is a version skew that segfaults
instead of failing.

Two rules, both of which cost a segfault to rediscover:

* Every string the core returns is the **caller's** to free, with `frq_free`.
  Nim's allocator is not Dart's.
* `frq_init` exists and does nothing. It used to call `NimMain`, and on Linux
  `--app:lib` already runs the module initialisers from a library constructor
  — so calling it again ran every module's top-level code a second time, which
  for `conn.nim` meant `open()` on channels that were already open.

## Things that will bite

* **Nothing is shared with the socket threads.** There are two — one reader,
  one writer — and they speak only in channels. `recvLine(timeout)` does not
  time out on a TLS socket, so a single thread cannot both wait for the server
  and notice what the client wants to say.
* **`--mm:orc`, not refc.** refc gives each thread its own GC heap, so the
  `Socket` the two threads share is a ref from another heap and dereferencing
  it segfaults.
* **Every intra-package import says `frq/…`.** `import conn` and
  `import frq/conn` name the same file by two paths and Nim compiles it twice,
  giving two sets of globals and two states.
* **`emoji.nim` is generated.** It was `tools/emoji2nim.py` reading the
  ClojureDart catalogue, which is gone; regenerating it now means going back to
  Unicode's `emoji-test.txt` and filtering to what the Twemoji pack draws.
* **The crypto is bindings, not implementations.** `crypto.nim` is OpenSSL
  through EVP. The RFC 8032 vectors in `tests/tcrypto.nim` check that this
  drives it correctly — the seed in the right place, the one-shot signing form,
  the bytes out in the right order — and not that Ed25519 is implemented
  correctly, which is OpenSSL's problem.

```bash
just test nim              # the whole suite
just test nim tircparse    # one file
just build lib             # libfrqcore.so into build/nim
```

## What is not here

`frq.profile` and `frq.replies` were never ported and went with the
ClojureDart rather than moving: a Bluesky profile behind a nick, and asking
freeq what a collapsed msgid was. Neither had a screen in this app to appear
on.

The profile card is state without a screen: `profileViewing` is moved by
nothing and rendered by nothing, and `frq.profile` was never ported.

The emoji picker, the overview strip and the lightbox used to be in that list.
They have screens now — the picker under the message it is for, the overview
as a pane above the compose bar, the lightbox as a panel over the
conversation — so the 🙂 chip, the Overview toggle and clicking a picture all
do what they look like they do.

Untested against a real account: nobody has watched freeq accept a SASL
challenge response or a signature. The shapes are checked and the curve is
OpenSSL's, but the server has not said yes.