nandi/frqpublic Fork 0
284b59c6810a1b2abace25c071e9d166cd0dafc9
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.

The last of the Clojure 284b59c · on 284b59c6810a1b2abace25c071e9d166cd0dafc9 · nandi · 7h ago
README.md · 84 lines · 4.0 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 emoji picker, the overview strip, the lightbox and the profile card are
state without a screen: the reducer moves them and nothing renders them, so
those buttons change colour and do nothing.

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
# 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 emoji picker, the overview strip, the lightbox and the profile card are
state without a screen: the reducer moves them and nothing renders them, so
those buttons change colour and do nothing.

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.