summary refs log tree commit diff
path: root/www/text.causal.agency/008-how-irc.7
diff options
context:
space:
mode:
Diffstat (limited to 'www/text.causal.agency/008-how-irc.7')
-rw-r--r--www/text.causal.agency/008-how-irc.7193
1 files changed, 193 insertions, 0 deletions
diff --git a/www/text.causal.agency/008-how-irc.7 b/www/text.causal.agency/008-how-irc.7
new file mode 100644
index 00000000..aba1bbf9
--- /dev/null
+++ b/www/text.causal.agency/008-how-irc.7
@@ -0,0 +1,193 @@
+.Dd March  8, 2020
+.Dt HOW-IRC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm How I Relay Chat
+.Nd in code
+.
+.Sh DESCRIPTION
+I've been writing a lot of IRC software lately
+.Pq Sx SEE ALSO ,
+and developed some nice code patterns
+that I've been reusing.
+Here they are.
+.
+.Ss Parsing
+I use fixed size buffers almost everywhere,
+so it's necessary to know IRC's size limits.
+A traditional IRC message is a maximum of 512 bytes,
+but the IRCv3 message-tags spec adds
+(unreasonably, in my opinion)
+8191 bytes for tags.
+IRC messages also have a maximum of 15 command parameters.
+.Bd -literal -offset indent
+enum { MessageCap = 8191 + 512 };
+enum { ParamCap = 15 };
+.Ed
+.
+.Pp
+If I'm using tags,
+I'll use X macros
+to declare the set I care about.
+X macros are a way of maintaining parallel arrays,
+or in this case an enum and an array.
+.Bd -literal -offset indent
+#define ENUM_TAG \e
+	X("msgid", TagMsgid) \e
+	X("time", TagTime)
+
+enum Tag {
+#define X(name, id) id,
+	ENUM_TAG
+#undef X
+	TagCap,
+};
+
+static const char *TagNames[TagCap] = {
+#define X(name, id) [id] = name,
+	ENUM_TAG
+#undef X
+};
+.Ed
+.
+.Pp
+The TagNames array is used by the parsing function
+to assign tag values into the message structure,
+which looks like this:
+.Bd -literal -offset indent
+struct Message {
+	char *tags[TagCap];
+	char *nick;
+	char *user;
+	char *host;
+	char *cmd;
+	char *params[ParamCap];
+};
+.Ed
+.
+.Pp
+I'm a fan of using
+.Xr strsep 3
+for simple parsing.
+Although it modifies its input
+(replacing delimiters with NUL terminators),
+since the raw message is in a static buffer,
+it is ideal for so-called zero-copy parsing.
+I'm not going to include the whole parsing function here,
+but I will at least include the part that many get wrong,
+which is dealing with the colon-prefixed trailing parameter:
+.Bd -literal -offset indent
+msg.cmd = strsep(&line, " ");
+for (int i = 0; line && i < ParamCap; ++i) {
+	if (line[0] == ':') {
+		msg.params[i] = &line[1];
+		break;
+	}
+	msg.params[i] = strsep(&line, " ");
+}
+.Ed
+.
+.Ss Handling
+To handle IRC commands and replies
+I add handler functions to a big array.
+I usually have some form of helper as well
+to check the number of expected parameters.
+.Bd -literal -offset indent
+typedef void HandlerFn(struct Message *msg);
+
+static const struct Handler {
+	const char *cmd;
+	HandlerFn *fn;
+} Handlers[] = {
+	{ "001", handleReplyWelcome },
+	{ "PING", handlePing },
+	{ "PRIVMSG", handlePrivmsg },
+};
+.Ed
+.
+.Pp
+Since I keep these arrays sorted anyway,
+I started using the standard
+.Xr bsearch 3
+function,
+but a basic for loop probably works just as well.
+I do wish I could compile-time assert
+that the array really is sorted, though.
+.Bd -literal -offset indent
+static int compar(const void *cmd, const void *_handler) {
+	const struct Handler *handler = _handler;
+	return strcmp(cmd, handler->cmd);
+}
+
+void handle(struct Message msg) {
+	if (!msg.cmd) return;
+	const struct Handler *handler = bsearch(
+		msg.cmd,
+		Handlers, ARRAY_LEN(Handlers),
+		sizeof(*handler), compar
+	);
+	if (handler) handler->fn(&msg);
+}
+.Ed
+.
+.Ss Capabilities
+For IRCv3 capabilties
+I use X macros again,
+this time with another handy macro
+for declaring bit flag enums.
+.Bd -literal -offset indent
+#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
+
+#define ENUM_CAP \e
+	X("message-tags", CapMessageTags) \e
+	X("sasl", CapSASL) \e
+	X("server-time", CapServerTime)
+
+enum Cap {
+#define X(name, id) BIT(id),
+	ENUM_CAP
+#undef X
+};
+
+static const char *CapNames[] = {
+#define X(name, id) [id##Bit] = name,
+	ENUM_CAP
+#undef X
+};
+.Ed
+.
+.Pp
+The
+.Fn BIT
+macro declares, for example,
+.Dv CapSASL
+as the bit flag and
+.Dv CapSASLBit
+as the corresponding index.
+The
+.Vt "enum Cap"
+is used as a set,
+for example checking if SASL is enabled with
+.Ql caps & CapSASL .
+.
+.Pp
+These patterns are serving my IRC software well,
+and my IRC projects are serving me well.
+It is immensely satisfying
+to be (near) constantly using software
+that I wrote myself and am happy with,
+regardless of how niche it may be.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/pounce/about "IRC bouncer"
+.It
+.Lk https://git.causal.agency/litterbox/about "IRC logger"
+.It
+.Lk https://git.causal.agency/catgirl/about "IRC client"
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency