- Tcl 73.6%
- C 10.5%
- Rust 10.4%
- Makefile 1.8%
- Python 1.6%
- Other 2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| docker | ||
| docs | ||
| embed | ||
| examples | ||
| ext | ||
| platforms | ||
| schema | ||
| tacky | ||
| tacky-core | ||
| tacky-crypto | ||
| tacky-keyring | ||
| tacky-media | ||
| tacky-olm | ||
| tacky-omemo | ||
| tests | ||
| tools | ||
| .gitignore | ||
| .gitmodules | ||
| audit.toml | ||
| CHANGELOG.md | ||
| coverage.mk | ||
| deps.mk | ||
| fuzz.mk | ||
| HANDBOOK.md | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| release.mk | ||
| ROBOTS.md | ||
| tcl.mk | ||
libtacky
An XMPP client library in Tcl 9, Rust and C. It holds the protocol, the local store, end-to-end encryption, calls and file transfer. Apps talk to it through one JSON API. Tcl programs and gateways load its packages directly.
Version 1.2.0-rc3. Licence GPL-3.0.
What you get
dist/libtacky.a: the whole library, Tcl included, behindembed/tacky.h.dist/tackyd-json: the same library over stdin and stdout.- A wasm build for a browser worker (
make wasm). - Builds for Linux, Windows, Android and iOS (
platforms/). - Tcl packages under
tacky::*: session, client, component, HTTP, and the OTR, MTProto and Matrix protocol layers. dist/tackyd: runs a Tcl program with no Tcl installed.- Native crates with C ABIs: tacky-core, tacky-crypto, tacky-keyring, tacky-omemo, tacky-olm, tacky-media.
The API is described once, in schema/tacky.schema.json: 72 modules, 638
methods, 204 events, 198 types. HANDBOOK.md documents all
of it, with the Tcl reference and the guides under docs/. The handbook
(make handbook) and the host bindings are generated from the schema.
Features
- One SQLCipher store per account, keys in a sealed keyring
- A message is acknowledged only after it is stored
- SASL2, Bind 2, FAST, SCRAM with channel binding
- Stream management with resume, CSI, reconnects
- Entity Capabilities 2.0, Happy Eyeballs, authentication pipelining
- TLS through rustls, certificate pins, no plaintext fallback
- STARTTLS, direct TLS and WebSocket
- SOCKS5 proxy, Tor and I2P per account
- OMEMO 0.3 and OMEMO 2 under one identity
- OMEMO automatic trust management (XEP-0450)
- OTR v3 secret chats with SMP, announced to the contact's devices
- Telegram secret chats through a gateway, and MTProto secret chats directly between two XMPP devices (experimental)
- Matrix end-to-end encryption (Olm, Megolm, SAS verification) through a gateway
- Group chats (MUC), bookmarks, archive (MAM) with catch-up
- Room hats, mention notifications, invitation links, spaces
- Replies, reactions, corrections, retractions, mentions
- Polls (Fluux's
urn:fluux:poll:0), stickers, silent and scheduled messages - Publish-subscribe on any service, microblogging, stories
- WebXDC apps, online meetings, payment requests (work in progress)
- HTTP upload and Jingle file transfer, encrypted
- Audio and video calls, group calls (Muji)
- XEP-0114 components for gateways and bots
docs/features.md has one row per specification with its
status. Our own extensions (urn:tacky:*) are specified under
docs/protocol/.
| status | meaning | specifications |
|---|---|---|
| 🔵 MOSTLY | implemented and tested, not checked by hand against other software | 171 |
| 🟡 WIP | partly implemented or partly tested | 27 |
| 🔴 TODO | not implemented, at most reserved in the API | 9 |
| 🟢 DONE | checked by hand; set by a maintainer | 0 |
| ⛔ NOT SUPPORTED | deliberately not implemented | 1 |
How it fits together
app (libtacky.a), tackyd-json, or a browser worker (wasm)
│ JSON ["module","method",{args},token] → ["result"|"error",token,…] ←
▼
embed/tacky.c one backend thread, one Tcl interpreter
▼
tacky/core/wire dispatch through the schema's allow-list
▼
tacky/xmpp/client one module per area (account, roster, message, muc, …)
├── tacky/xmpp/session the XMPP session, in Tcl
│ └── tacky::core tacky-core (Rust): DNS, connect, TLS, XML
├── tacky/core/storage SQLCipher store per account
├── tacky::core::keyring tacky-keyring (Rust): the key vault
├── tacky::omemo tacky-omemo (Rust): the OMEMO ratchet
├── tacky::olm tacky-olm (Rust, vodozemac): Olm and Megolm
├── tacky/http HTTP/1.1 in Tcl
└── tacky::media calls: webrtc | datachannel | host | remote
tacky/xmpp/component an XEP-0114 component, no client loaded
- Rust reads untrusted bytes and holds keys: the stream, TLS, parsers, crypto, OMEMO.
- C binds each crate to Tcl, and runs media (libwebrtc or libdatachannel).
- Tcl is everything that means something: the session, the client, the store, HTTP.
Calls use one native media backend per build, chosen by MEDIA_BACKEND.
ROBOTS.md has the full layout and the reasons behind it.
| component | language | what it is | code | tests | coverage |
|---|---|---|---|---|---|
| core | Tcl | tacky/core: the wire API dispatch, the per-account store, the keyring layer |
2,458 | 1,779 | 98.9 % procs |
| C | ext/core: the Tcl binding of tacky-core and the keyring; the SQLCipher crypto provider |
3,914 | 3,500 | 99.3 % lines | |
| Rust | tacky-core: DNS, TCP, TLS (rustls), WebSocket, the XML stream, the C ABI; tacky-keyring: the key vault |
12,962 | 7,887 | 98.9 % lines | |
| crypto | C | ext/crypto: the tacky::crypto binding |
1,393 | 719 | 99.7 % lines |
| Rust | tacky-crypto: hashes, HMAC, HKDF, PBKDF2, AES, X25519 with a C ABI |
2,543 | 2,933 | 99.9 % lines | |
| http | Tcl | tacky/http: HTTP/1.1 client for uploads and downloads |
1,068 | 2,199 | 100.0 % procs |
| media | Tcl | tacky/media: the call media contract and its backends |
1,009 | 1,906 | 97.8 % procs |
| C | ext/media: the binding; tacky-media: the media C API over libdatachannel, with audio (Opus) and video (VP8) engines |
9,949 | 10,538 | -- | |
| C++ | tacky-media/webrtc: the same C API over libwebrtc |
4,925 | 323 | -- | |
| olm | C | ext/olm: the tacky::olm binding of Olm and Megolm; Matrix above it is the client's |
1,042 | 375 | 99.8 % lines |
| Rust | tacky-olm: Olm, Megolm, the SAS and Ed25519 keypairs for Matrix's end-to-end encryption over vodozemac, with a C ABI |
827 | 1,022 | 100.0 % lines | |
| omemo | C | ext/omemo: the tacky::omemo binding |
979 | 398 | 98.6 % lines |
| Rust | tacky-omemo: X3DH, Double Ratchet, XEdDSA; OMEMO 0.3 and OMEMO 2 |
2,080 | 4,045 | 99.8 % lines | |
| xmpp | Tcl | tacky/xmpp: the session, the client modules, the XEP-0114 component, the in-process test driver |
85,135 | 139,983 | 99.0 % procs |
| everything else | C, Tcl, Python, sh, make, CMake | embed/, examples/, tools/, test runner, build files; not in the total | 10,628 | 5,457 | -- |
| total (without everything else) | Tcl 89,670 · C/C++ 22,202 · Rust 18,412 | 130,284 | 177,607 | Tcl 99.0 % procs · Rust 99.2 % lines · C 99.1 % lines |
code: lines of code, no blanks, comments or generated files. tests: lines of test code for that part. The total leaves out everything else. coverage: Tcl counts procedures called, Rust and C count lines run, from the make coverage-all run on 2026-11-05 at commit 55d7df67.
The media C and C++ have no coverage figure: the coverage run uses the
default webrtc build, and libwebrtc is not measured.
Status and tests
The API is stable. A change that breaks a host comes with a new major version.
Unit suites run against in-process peers: a fake server with real SCRAM,
TLS and WebSocket, a stream relay, fault shims for the C bindings, and
reference vectors for the ratchet. Integration suites (tests/integration)
run against a real server in a container, with OMEMO echo bots beside it.
Integration results, measured on 1.2.0-rc3 (2026-11-05, at commit
6125f186), three rigs per server, the six at once:
| server | passed | failed | skipped |
|---|---|---|---|
| Prosody 13 | 285 | 0 | 53 |
| ejabberd 26.09 | 314 | 0 | 24 |
| MongooseIM 6.10.0 | 160 | 0 | 49 |
MongooseIM's row is from v1.1 (2026-10-23); it was not rerun since. Skips are features a server or its rig lacks.
Nothing has been checked by hand against public servers or other clients yet. That is why no feature is DONE.
130,284 lines of code, 177,607 lines of tests (16,648 of them integration suites), 10,133 test cases. Coverage: Tcl 99.0 % procs · Rust 99.2 % lines · C 99.1 % lines (make coverage-all on 2026-11-05 at commit 55d7df67).
| suite family | files | cases |
|---|---|---|
| Tacky unit suites (tests/client, tests/http, tests/tcl) | 254 | 7602 |
| Tacky integration suites (tests/integration, need a server) | 60 | 327 |
| Rust: tacky-core | 53 | 447 |
| Rust: tacky-keyring | 6 | 54 |
| Rust: tacky-crypto | 21 | 134 |
| Rust: tacky-omemo | 15 | 181 |
| Rust: tacky-olm | 5 | 24 |
| Tcl extensions (ext/*) | 21 | 1318 |
| C: tacky-media: libtackymedia, rtc-ma, rtc-mv (ctest programs) | 42 | 42 |
| C++: tacky-media: webrtc (ctest programs) | 4 | 4 |
| total | 481 | 10133 |
make docs regenerates these numbers. Coverage comes from the last
make coverage-all; its full report is _build/cov/REPORT.md.
Building
Everything builds in libtacky's container image. The machine needs git and docker or podman.
git clone --recurse-submodules ssh://git@dev.another.im:3022/another-im/libtacky.git
cd libtacky
make all test # build, run every suite that needs no server
make all test REQUIRE=1 # the gate: a missing prerequisite fails
MEDIA_BACKEND is webrtc (default), datachannel or none.
| target | what it does |
|---|---|
make, make dist |
build everything; dist/libtacky.a |
make tackyd-json, make tackyd |
the two executables |
make test |
every suite that needs no server |
make test-tacky-prosody, -ejabberd, -mongooseim |
integration suites against a server in a container |
make coverage-all |
the coverage run behind the table above |
make audit |
every Cargo.lock against the RustSec advisory database (audit.toml is the allow-list); in make gate |
make docs |
handbook, README numbers, DOAP file, Rust API docs |
make handbook |
HANDBOOK.md, from the schema and docs/ |
make help |
every target |
More in docs/building.md.
Using it
An app links embed/tacky.h and speaks JSON:
tacky *tacky_create(const char *const *backend_args, tacky_emit_fn emit, void *ud);
void tacky_send(tacky *t, const char *json, size_t len); /* ["module","method",{args},token] */
void tacky_destroy(tacky *t); /* replies and events arrive at emit */
void tacky_destroy_wait(tacky *t, unsigned int max_ms);
A Tcl program loads the packages, from a built checkout or through
dist/tackyd:
package require tacky::xmpp::component
set h [tacky::xmpp::component new -domain gateway.example.org -secret $secret \
-host 127.0.0.1 -port 5347 -command onEvent]
tacky::xmpp::component connect $h
examples/component/echo.tcl is a complete component. The handbook,
HANDBOOK.md, documents every method, event and Tcl command.