-- Leo's gemini proxy

-- Connecting to gemini.dimakrasner.com:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Re: Bloat


skyjake says software bloat is a relative term.


99 KLOC (53 KLOC without libraries) is relatively ... a lot, for a Gemini client. I think "bloat" is a bad thing in this context, because:

Big codebases are bad for interoperability. It's harder to fix client-side violations of the protocol when client codebases are big, not based on a shared "libgemini" and any popular client can shape the de-facto standard servers (and eventually, other clients) must obey (a-la Chrome). For example, some capsules today assume that a client certificate for /x is also used for /, because that's the default behavior of Lagrange.

A large Gemini client can weaken Gemini's community aspect, because users are less likely to contribute to it, fork it and share their personal patches. Some free software projects are "forkable", but only in theory, because forking is impractical. Free software that's hard to modify and open source software with unreadable code are not a huge improvement over proprietary software, in my mind.

If Gemini is about trust (at least, to some degree), it's hard to (blindly) trust a large client: it's harder to trust a Chromium-based browser, even when it's "open source" and "privacy-oriented", because it's still a black box (there's too much code, some behavior is implicit, or the code is hard to understand) and not something you can hold in your head. Only the advantageous (in this case, skilled programmers) benefit from formal equality.

Some of us have old hardware, or soldered RAM. Often, big is fast and small is slow (for example, fast, GPU-accelerated rendering requires extra libraries). In other cases (for example, under heavy swapping), big can be painfully slow.


It's not easy to write a small client, though, and it's not easy to write a useful client with good user experience. If a dependency is small, feels "native" enough and has a smaller memory/CPU footprint compared to the native option, I tend to agree with this Go proverb:


> A little copying is better than a little dependency.


Go Proverbs


This is why gplaces uses vendored bestline instead of a shared readline. The latter leaks memory and the "hints" feature of bestline is very useful. In addition, gplaces uses a 3.x-compatible and LibreSSL-compatible subset of the OpenSSL 1.x API, without any abstraction layer, and uses curl's API parser. These two libraries are very common, and all my attempts to wrap their API only made gplaces less readable.


Most of gplaces.c (0.16.8, 1032 LOC) is glue code, and the "big" features are pretty small in terms of LOC. After some crude CTRL+x, CTRL+n and CTRL+v work:


Feature                       | LOC
------------------------------+-----
Status line handling          | 159
TOFU                          | 30
Client certificate generation | 37
Parsing and pretty-printing   | 100
bestline.c                    | 3057

A static executable linked against musl and OpenSSL 3.x is about 3 MB, or about 1 MB compressed. A dynamically-linked (except bestline) executable is around 100K.

A quick and dirty Spartan port of gplaces, in the "spartan" branch, is about 100 LOC less, and that's not very surprising if TOFU, client certificates and some status codes are dropped, while Gemtext-related stuff, URL handling and the shell are still there.

A libtls port of gplaces, in the "libtls" branch, has a small wrapper around the OpenSSL API for systems that don't support libretls, but gplaces.c is longer because the libtls API is too minimal and abstract; certificate generation is still done the old way.


gplaces shows that it *is* possible to write a Gemini client, with the basics plus some utilities like certificate generation, in a tiny codebase. This makes me wonder if a big portion of the Lagrange codebase consists of functions like these two:


void initCurrent_Time(iTime *d) {
    clock_gettime(CLOCK_REALTIME, &d->ts);
}

time.c, line 59


iBool isExpired_TlsCertificate(const iTlsCertificate *d) {
    if (!d->cert) return iTrue;
    return X509_cmp_current_time(X509_get0_notAfter(d->cert)) < 0;
}

tlsrequest.c, line 545


I don't think these functions make the code easier to understand: they hide implementation details ("time" means "real time" or a monotonic timer?) and I'm used to the libc and OpenSSL API. The "let's wrap everything" attitude can be a problem if it makes life harder for contributors and increase binary size (because these one-liners are not inlined). Is Lagrange bloated, then? I don't think it's easy to write a graphical client with the same feature set and consistent UI across multiple platforms. This coding style thing doesn't make a huge difference. I do prefer a compact, tight codebase, because this makes it easier to debug and generally, easier to work with, even if the binary size doesn't change much.


Let's not forget that gplaces is super bloated, too: the static executable is only slightly smaller than Bombadillo, and the latter (a much better client, for most users), is written in Go. We're all in the same boat here!


> A basic but usable (not ultra-spartan) client should fit comfortably within 50 or so lines of code in a modern high-level language. Certainly not more than 100.


FAQ

-- Response ended

-- Page fetched on Fri May 10 18:39:33 2024