Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options, it was decided to implement an XDR-like protocol code generator in portable AWK. It now has two backends, per each of: - xF, the X11 frontend, is in C, and is meant to be the primary user interface in the future. - xP, the web frontend, relies on a protocol proxy written in Go, and is meant for use on-the-go (no pun intended). They are very much work-in-progress proofs of concept right now, and the relay protocol is certain to change.
This commit is contained in:
		@@ -1,10 +1,12 @@
 | 
				
			|||||||
# Ubuntu 18.04 LTS and OpenBSD 6.4
 | 
					# Ubuntu 18.04 LTS and OpenBSD 6.4
 | 
				
			||||||
cmake_minimum_required (VERSION 3.10)
 | 
					cmake_minimum_required (VERSION 3.10)
 | 
				
			||||||
project (xK VERSION 1.5.0 DESCRIPTION "IRC client, daemon and bot" LANGUAGES C)
 | 
					project (xK VERSION 1.5.0
 | 
				
			||||||
 | 
						DESCRIPTION "IRC daemon, bot, TUI client and X11/web frontends" LANGUAGES C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Options
 | 
					# Options
 | 
				
			||||||
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
 | 
					option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
 | 
				
			||||||
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
 | 
					option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
 | 
				
			||||||
 | 
					option (WANT_XF "Build xF" OFF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Moar warnings
 | 
					# Moar warnings
 | 
				
			||||||
set (CMAKE_C_STANDARD 99)
 | 
					set (CMAKE_C_STANDARD 99)
 | 
				
			||||||
@@ -143,7 +145,8 @@ set (HAVE_EDITLINE "${WANT_LIBEDIT}")
 | 
				
			|||||||
set (HAVE_LUA      "${WITH_LUA}")
 | 
					set (HAVE_LUA      "${WITH_LUA}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
include (GNUInstallDirs)
 | 
					include (GNUInstallDirs)
 | 
				
			||||||
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
 | 
					set (project_config ${PROJECT_BINARY_DIR}/config.h)
 | 
				
			||||||
 | 
					configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${project_config})
 | 
				
			||||||
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
 | 
					include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Generate IRC replies--we need a custom target because of the multiple outputs
 | 
					# Generate IRC replies--we need a custom target because of the multiple outputs
 | 
				
			||||||
@@ -157,17 +160,40 @@ add_custom_command (OUTPUT xD-replies.c xD.msg
 | 
				
			|||||||
	COMMENT "Generating files from the list of server numerics")
 | 
						COMMENT "Generating files from the list of server numerics")
 | 
				
			||||||
add_custom_target (replies DEPENDS ${PROJECT_BINARY_DIR}/xD-replies.c)
 | 
					add_custom_target (replies DEPENDS ${PROJECT_BINARY_DIR}/xD-replies.c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_custom_command (OUTPUT xC-proto.c
 | 
				
			||||||
 | 
						COMMAND env LC_ALL=C awk
 | 
				
			||||||
 | 
							-f ${PROJECT_SOURCE_DIR}/xC-gen-proto.awk
 | 
				
			||||||
 | 
							-f ${PROJECT_SOURCE_DIR}/xC-gen-proto-c.awk
 | 
				
			||||||
 | 
							${PROJECT_SOURCE_DIR}/xC-proto > xC-proto.c
 | 
				
			||||||
 | 
						DEPENDS
 | 
				
			||||||
 | 
							${PROJECT_SOURCE_DIR}/xC-gen-proto.awk
 | 
				
			||||||
 | 
							${PROJECT_SOURCE_DIR}/xC-gen-proto-c.awk
 | 
				
			||||||
 | 
							${PROJECT_SOURCE_DIR}/xC-proto
 | 
				
			||||||
 | 
						COMMENT "Generating xC relay protocol code")
 | 
				
			||||||
 | 
					add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Build
 | 
					# Build
 | 
				
			||||||
foreach (name xB xC xD)
 | 
					foreach (name xB xC xD)
 | 
				
			||||||
	add_executable (${name} ${name}.c ${PROJECT_BINARY_DIR}/config.h)
 | 
						add_executable (${name} ${name}.c ${project_config})
 | 
				
			||||||
	target_link_libraries (${name} ${project_libraries})
 | 
						target_link_libraries (${name} ${project_libraries})
 | 
				
			||||||
	add_threads (${name})
 | 
						add_threads (${name})
 | 
				
			||||||
endforeach ()
 | 
					endforeach ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_dependencies (xD replies)
 | 
					add_dependencies (xD replies)
 | 
				
			||||||
add_dependencies (xC replies)
 | 
					add_dependencies (xC replies xC-proto)
 | 
				
			||||||
target_link_libraries (xC ${xC_libraries})
 | 
					target_link_libraries (xC ${xC_libraries})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (WANT_XF)
 | 
				
			||||||
 | 
						pkg_check_modules (x11 REQUIRED x11 xrender xft fontconfig)
 | 
				
			||||||
 | 
						include_directories (${x11_INCLUDE_DIRS})
 | 
				
			||||||
 | 
						link_directories (${x11_LIBRARY_DIRS})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						add_executable (xF xF.c ${project_config})
 | 
				
			||||||
 | 
						add_dependencies (xF xC-proto)
 | 
				
			||||||
 | 
						target_link_libraries (xF ${x11_LIBRARIES} ${project_libraries})
 | 
				
			||||||
 | 
						add_threads (xF)
 | 
				
			||||||
 | 
					endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Tests
 | 
					# Tests
 | 
				
			||||||
include (CTest)
 | 
					include (CTest)
 | 
				
			||||||
if (BUILD_TESTING)
 | 
					if (BUILD_TESTING)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								NEWS
									
									
									
									
									
								
							@@ -1,5 +1,9 @@
 | 
				
			|||||||
Unreleased
 | 
					Unreleased
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * xD: implemented WALLOPS, choosing to make it target even non-operators
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * xC: made it show WALLOPS messages, as PRIVMSG for the server buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 * xC: all behaviour.* configuration options have been renamed to general.*,
 | 
					 * xC: all behaviour.* configuration options have been renamed to general.*,
 | 
				
			||||||
   with the exception of editor_command/editor, backlog_helper/pager,
 | 
					   with the exception of editor_command/editor, backlog_helper/pager,
 | 
				
			||||||
   and backlog_helper_strip_formatting/pager_strip_formatting
 | 
					   and backlog_helper_strip_formatting/pager_strip_formatting
 | 
				
			||||||
@@ -10,11 +14,13 @@ Unreleased
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 * xC: normalized editline's history behaviour, making it a viable frontend
 | 
					 * xC: normalized editline's history behaviour, making it a viable frontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 * xC: made it show WALLOPS messages, as PRIVMSG for the server buffer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 * xC: various bugfixes
 | 
					 * xC: various bugfixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 * xD: implemented WALLOPS, choosing to make it target even non-operators
 | 
					 * xC: added a relay interface, enabled through the general.relay_bind option
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * Added an experimental X11 frontend for xC called xF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * Added an experimental web frontend for xC called xP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1.5.0 (2021-12-21) "The Show Must Go On"
 | 
					1.5.0 (2021-12-21) "The Show Must Go On"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								README.adoc
									
									
									
									
									
								
							@@ -1,9 +1,9 @@
 | 
				
			|||||||
xK
 | 
					xK
 | 
				
			||||||
==
 | 
					==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
'xK' (chat kit) is an IRC software suite consisting of a terminal client,
 | 
					'xK' (chat kit) is an IRC software suite consisting of a daemon, bot, terminal
 | 
				
			||||||
daemon, and bot.  It's all you're ever going to need for chatting,
 | 
					client, and X11/web frontends for the client.  It's all you're ever going to
 | 
				
			||||||
so long as you can make do with slightly minimalist software.
 | 
					need for chatting, so long as you can make do with slightly minimalist software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
They're all lean on dependencies, and offer a maximally permissive licence.
 | 
					They're all lean on dependencies, and offer a maximally permissive licence.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,8 +20,18 @@ a powerful configuration system, integrated help, text formatting, automatic
 | 
				
			|||||||
message splitting, multiline editing, bracketed paste support, word wrapping
 | 
					message splitting, multiline editing, bracketed paste support, word wrapping
 | 
				
			||||||
that doesn't break links, autocomplete, logging, CTCP queries, auto-away,
 | 
					that doesn't break links, autocomplete, logging, CTCP queries, auto-away,
 | 
				
			||||||
command aliases, SOCKS proxying, SASL EXTERNAL authentication using TLS client
 | 
					command aliases, SOCKS proxying, SASL EXTERNAL authentication using TLS client
 | 
				
			||||||
certificates, or basic support for Lua scripting.  As a unique bonus, you can
 | 
					certificates, a remote relay interface, or basic support for Lua scripting.
 | 
				
			||||||
launch a full text editor from within.
 | 
					As a unique bonus, you can launch a full text editor from within.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xF
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					The X11 frontend for 'xC', making use of its networked relay interface.
 | 
				
			||||||
 | 
					It's currently in development, and is hidden behind a CMake option.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xP
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					The web frontend for 'xC', making use of its networked relay interface.
 | 
				
			||||||
 | 
					It's currently rather elementary, and can be built from within its directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
xD
 | 
					xD
 | 
				
			||||||
--
 | 
					--
 | 
				
			||||||
@@ -38,9 +48,8 @@ What it notably doesn't support is online changes to configuration, any limits
 | 
				
			|||||||
besides the total number of connections and mode `+l`, or server linking
 | 
					besides the total number of connections and mode `+l`, or server linking
 | 
				
			||||||
(which also means no services).
 | 
					(which also means no services).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This program has been
 | 
					This program has been https://git.janouch.name/p/haven/src/branch/master/hid[
 | 
				
			||||||
https://git.janouch.name/p/haven/src/branch/master/hid[ported to Go],
 | 
					ported to Go] in a different project, and development continues over there.
 | 
				
			||||||
and development continues over there.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
xB
 | 
					xB
 | 
				
			||||||
--
 | 
					--
 | 
				
			||||||
@@ -60,11 +69,12 @@ a package with the latest development version from Archlinux's AUR.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Building
 | 
					Building
 | 
				
			||||||
--------
 | 
					--------
 | 
				
			||||||
Build dependencies: CMake, pkg-config, asciidoctor or asciidoc, awk,
 | 
					Build-only dependencies:
 | 
				
			||||||
                    liberty (included) +
 | 
					 CMake, pkg-config, asciidoctor or asciidoc, awk, liberty (included) +
 | 
				
			||||||
Runtime dependencies: openssl +
 | 
					Common runtime dependencies: openssl +
 | 
				
			||||||
Additionally for 'xC': curses, libffi, lua >= 5.3 (optional),
 | 
					Additionally for 'xC': curses, libffi, +
 | 
				
			||||||
                       readline >= 6.0 or libedit >= 2013-07-12
 | 
					 readline >= 6.0 or libedit >= 2013-07-12, lua >= 5.3 (optional) +
 | 
				
			||||||
 | 
					Additionally for 'xF': x11, xft
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 $ git clone --recursive https://git.janouch.name/p/xK.git
 | 
					 $ git clone --recursive https://git.janouch.name/p/xK.git
 | 
				
			||||||
 $ mkdir xK/build
 | 
					 $ mkdir xK/build
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										71
									
								
								common.c
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								common.c
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
 * common.c: common functionality
 | 
					 * common.c: common functionality
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
 | 
					 * Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name>
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Permission to use, copy, modify, and/or distribute this software for any
 | 
					 * Permission to use, copy, modify, and/or distribute this software for any
 | 
				
			||||||
 * purpose with or without fee is hereby granted.
 | 
					 * purpose with or without fee is hereby granted.
 | 
				
			||||||
@@ -48,6 +48,66 @@ init_openssl (void)
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static char *
 | 
				
			||||||
 | 
					gai_reconstruct_address (struct addrinfo *ai)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						char host[NI_MAXHOST] = {}, port[NI_MAXSERV] = {};
 | 
				
			||||||
 | 
						int err = getnameinfo (ai->ai_addr, ai->ai_addrlen,
 | 
				
			||||||
 | 
							host, sizeof host, port, sizeof port,
 | 
				
			||||||
 | 
							NI_NUMERICHOST | NI_NUMERICSERV);
 | 
				
			||||||
 | 
						if (err)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
 | 
				
			||||||
 | 
							return xstrdup ("?");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return format_host_port_pair (host, port);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					accept_error_is_transient (int err)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// OS kernels may return a wide range of unforeseeable errors.
 | 
				
			||||||
 | 
						// Assuming that they're either transient or caused by
 | 
				
			||||||
 | 
						// a connection that we've just extracted from the queue.
 | 
				
			||||||
 | 
						switch (err)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						case EBADF:
 | 
				
			||||||
 | 
						case EINVAL:
 | 
				
			||||||
 | 
						case ENOTSOCK:
 | 
				
			||||||
 | 
						case EOPNOTSUPP:
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Destructively tokenize an address into a host part, and a port part.
 | 
				
			||||||
 | 
					/// The port is only overwritten if that part is found, allowing for defaults.
 | 
				
			||||||
 | 
					static const char *
 | 
				
			||||||
 | 
					tokenize_host_port (char *address, const char **port)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Unwrap IPv6 addresses in format_host_port_pair() format.
 | 
				
			||||||
 | 
						char *rbracket = strchr (address, ']');
 | 
				
			||||||
 | 
						if (*address == '[' && rbracket)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (rbracket[1] == ':')
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								*port = rbracket + 2;
 | 
				
			||||||
 | 
								return *rbracket = 0, address + 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (!rbracket[1])
 | 
				
			||||||
 | 
								return *rbracket = 0, address + 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						char *colon = strchr (address, ':');
 | 
				
			||||||
 | 
						if (colon)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							*port = colon + 1;
 | 
				
			||||||
 | 
							return *colon = 0, address;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return address;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- To be moved to liberty --------------------------------------------------
 | 
					// --- To be moved to liberty --------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FIXME: in xssl_get_error() we rely on error reasons never being NULL (i.e.,
 | 
					// FIXME: in xssl_get_error() we rely on error reasons never being NULL (i.e.,
 | 
				
			||||||
@@ -74,6 +134,15 @@ xerr_describe_error (void)
 | 
				
			|||||||
	return reason;
 | 
						return reason;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct str
 | 
				
			||||||
 | 
					str_from_cstr (const char *cstr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct str self;
 | 
				
			||||||
 | 
						self.alloc = (self.len = strlen (cstr)) + 1;
 | 
				
			||||||
 | 
						self.str = memcpy (xmalloc (self.alloc), cstr, self.alloc);
 | 
				
			||||||
 | 
						return self;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static ssize_t
 | 
					static ssize_t
 | 
				
			||||||
strv_find (const struct strv *v, const char *s)
 | 
					strv_find (const struct strv *v, const char *s)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										325
									
								
								xC-gen-proto-c.awk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								xC-gen-proto-c.awk
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,325 @@
 | 
				
			|||||||
 | 
					# xC-gen-proto-c.awk: C backend for xC-gen-proto.awk.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: 0BSD
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Neither *_new() nor *_destroy() functions are provided, because they'd only
 | 
				
			||||||
 | 
					# be useful for top-levels, and are merely extra malloc()/free() calls.
 | 
				
			||||||
 | 
					# Users are expected to reuse buffers.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Similarly, no constructors are produced--those are easy to write manually.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# All arrays are deserialized zero-terminated, so u8<> and i8<> can be directly
 | 
				
			||||||
 | 
					# used as C strings.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# All types must be able to dispose partially zero values going from the back,
 | 
				
			||||||
 | 
					# i.e., in the reverse order of deserialization.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function define_internal(name, ctype) {
 | 
				
			||||||
 | 
						Types[name] = "internal"
 | 
				
			||||||
 | 
						CodegenCType[name] = ctype
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function define_int(shortname, ctype) {
 | 
				
			||||||
 | 
						define_internal(shortname, ctype)
 | 
				
			||||||
 | 
						CodegenSerialize[shortname] = \
 | 
				
			||||||
 | 
							"\tstr_pack_" shortname "(w, %s);\n"
 | 
				
			||||||
 | 
						CodegenDeserialize[shortname] = \
 | 
				
			||||||
 | 
							"\tif (!msg_unpacker_" shortname "(r, &%s))\n" \
 | 
				
			||||||
 | 
							"\t\treturn false;\n"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function define_sint(size) { define_int("i" size, "int" size "_t") }
 | 
				
			||||||
 | 
					function define_uint(size) { define_int("u" size, "uint" size "_t") }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_begin() {
 | 
				
			||||||
 | 
						define_sint("8")
 | 
				
			||||||
 | 
						define_sint("16")
 | 
				
			||||||
 | 
						define_sint("32")
 | 
				
			||||||
 | 
						define_sint("64")
 | 
				
			||||||
 | 
						define_uint("8")
 | 
				
			||||||
 | 
						define_uint("16")
 | 
				
			||||||
 | 
						define_uint("32")
 | 
				
			||||||
 | 
						define_uint("64")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						define_internal("string", "struct str")
 | 
				
			||||||
 | 
						CodegenDispose["string"] = "\tstr_free(&%s);\n"
 | 
				
			||||||
 | 
						CodegenSerialize["string"] = \
 | 
				
			||||||
 | 
							"\tif (!proto_string_serialize(&%s, w))\n" \
 | 
				
			||||||
 | 
							"\t\treturn false;\n"
 | 
				
			||||||
 | 
						CodegenDeserialize["string"] = \
 | 
				
			||||||
 | 
							"\tif (!proto_string_deserialize(&%s, r))\n" \
 | 
				
			||||||
 | 
							"\t\treturn false;\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						define_internal("bool", "bool")
 | 
				
			||||||
 | 
						CodegenSerialize["bool"] = \
 | 
				
			||||||
 | 
							"\tstr_pack_u8(w, !!%s);\n"
 | 
				
			||||||
 | 
						CodegenDeserialize["bool"] = \
 | 
				
			||||||
 | 
							"\t{\n" \
 | 
				
			||||||
 | 
							"\t\tuint8_t v = 0;\n" \
 | 
				
			||||||
 | 
							"\t\tif (!msg_unpacker_u8(r, &v))\n" \
 | 
				
			||||||
 | 
							"\t\t\treturn false;\n" \
 | 
				
			||||||
 | 
							"\t\t%s = !!v;\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "// This file directly depends on liberty.c, but doesn't include it."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print "static bool"
 | 
				
			||||||
 | 
						print "proto_string_serialize(const struct str *s, struct str *w) {"
 | 
				
			||||||
 | 
						print "\tif (s->len > UINT32_MAX)"
 | 
				
			||||||
 | 
						print "\t\treturn false;"
 | 
				
			||||||
 | 
						print "\tstr_pack_u32(w, s->len);"
 | 
				
			||||||
 | 
						print "\tstr_append_str(w, s);"
 | 
				
			||||||
 | 
						print "\treturn true;"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print "static bool"
 | 
				
			||||||
 | 
						print "proto_string_deserialize(struct str *s, struct msg_unpacker *r) {"
 | 
				
			||||||
 | 
						print "\tuint32_t len = 0;"
 | 
				
			||||||
 | 
						print "\tif (!msg_unpacker_u32(r, &len))"
 | 
				
			||||||
 | 
						print "\t\treturn false;"
 | 
				
			||||||
 | 
						print "\tif (msg_unpacker_get_available(r) < len)"
 | 
				
			||||||
 | 
						print "\t\treturn false;"
 | 
				
			||||||
 | 
						print "\t*s = str_make();"
 | 
				
			||||||
 | 
						print "\tstr_append_data(s, r->data + r->offset, len);"
 | 
				
			||||||
 | 
						print "\tr->offset += len;"
 | 
				
			||||||
 | 
						print "\tif (!utf8_validate (s->str, s->len))"
 | 
				
			||||||
 | 
						print "\t\treturn false;"
 | 
				
			||||||
 | 
						print "\treturn true;"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_constant(name, value) {
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print "enum { " PrefixUpper name " = " value " };"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_enum_value(name, subname, value, cg) {
 | 
				
			||||||
 | 
						append(cg, "fields",
 | 
				
			||||||
 | 
							"\t" PrefixUpper toupper(cameltosnake(name)) "_" subname \
 | 
				
			||||||
 | 
							" = " value ",\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_enum(name, cg,    ctype) {
 | 
				
			||||||
 | 
						ctype = "enum " PrefixLower cameltosnake(name)
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print ctype " {"
 | 
				
			||||||
 | 
						print cg["fields"] "};"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# XXX: This should also check if it isn't out-of-range for any reason,
 | 
				
			||||||
 | 
						# but our usage of sprintf() stands in the way a bit.
 | 
				
			||||||
 | 
						CodegenSerialize[name] = "\tstr_pack_i32(w, %s);\n"
 | 
				
			||||||
 | 
						CodegenDeserialize[name] = \
 | 
				
			||||||
 | 
							"\t{\n" \
 | 
				
			||||||
 | 
							"\t\tint32_t v = 0;\n" \
 | 
				
			||||||
 | 
							"\t\tif (!msg_unpacker_i32(r, &v) || !v)\n" \
 | 
				
			||||||
 | 
							"\t\t\treturn false;\n" \
 | 
				
			||||||
 | 
							"\t\t%s = v;\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenCType[name] = ctype
 | 
				
			||||||
 | 
						for (i in cg)
 | 
				
			||||||
 | 
							delete cg[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_struct_tag(d, cg,    f) {
 | 
				
			||||||
 | 
						f = "self->" d["name"]
 | 
				
			||||||
 | 
						append(cg, "fields", "\t" CodegenCType[d["type"]] " " d["name"] ";\n")
 | 
				
			||||||
 | 
						append(cg, "dispose", sprintf(CodegenDispose[d["type"]], f))
 | 
				
			||||||
 | 
						append(cg, "serialize", sprintf(CodegenSerialize[d["type"]], f))
 | 
				
			||||||
 | 
						# Do not deserialize here, that would be out of order.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_struct_field(d, cg,    f, dispose, serialize, deserialize) {
 | 
				
			||||||
 | 
						f = "self->" d["name"]
 | 
				
			||||||
 | 
						dispose = CodegenDispose[d["type"]]
 | 
				
			||||||
 | 
						serialize = CodegenSerialize[d["type"]]
 | 
				
			||||||
 | 
						deserialize = CodegenDeserialize[d["type"]]
 | 
				
			||||||
 | 
						if (!d["isarray"]) {
 | 
				
			||||||
 | 
							append(cg, "fields", "\t" CodegenCType[d["type"]] " " d["name"] ";\n")
 | 
				
			||||||
 | 
							append(cg, "dispose", sprintf(dispose, f))
 | 
				
			||||||
 | 
							append(cg, "serialize", sprintf(serialize, f))
 | 
				
			||||||
 | 
							append(cg, "deserialize", sprintf(deserialize, f))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						append(cg, "fields",
 | 
				
			||||||
 | 
							"\t" CodegenCType["u32"] " " d["name"] "_len;\n" \
 | 
				
			||||||
 | 
							"\t" CodegenCType[d["type"]] " *" d["name"] ";\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (dispose)
 | 
				
			||||||
 | 
							append(cg, "dispose", "\tif (" f ")\n" \
 | 
				
			||||||
 | 
								"\t\tfor (size_t i = 0; i < " f "_len; i++)\n" \
 | 
				
			||||||
 | 
								indent(indent(sprintf(dispose, f "[i]"))))
 | 
				
			||||||
 | 
						append(cg, "dispose", "\tfree(" f ");\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						append(cg, "serialize", sprintf(CodegenSerialize["u32"], f "_len"))
 | 
				
			||||||
 | 
						if (d["type"] == "u8" || d["type"] == "i8") {
 | 
				
			||||||
 | 
							append(cg, "serialize",
 | 
				
			||||||
 | 
								"\tstr_append_data(w, " f ", " f "_len);\n")
 | 
				
			||||||
 | 
						} else if (serialize) {
 | 
				
			||||||
 | 
							append(cg, "serialize",
 | 
				
			||||||
 | 
								"\tfor (size_t i = 0; i < " f "_len; i++)\n" \
 | 
				
			||||||
 | 
								indent(sprintf(serialize, f "[i]")))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						append(cg, "deserialize", sprintf(CodegenDeserialize["u32"], f "_len") \
 | 
				
			||||||
 | 
							"\tif (!(" f " = calloc(" f "_len + 1, sizeof *" f ")))\n" \
 | 
				
			||||||
 | 
							"\t\treturn false;\n")
 | 
				
			||||||
 | 
						if (d["type"] == "u8" || d["type"] == "i8") {
 | 
				
			||||||
 | 
							append(cg, "deserialize",
 | 
				
			||||||
 | 
								"\tif (msg_unpacker_get_available(r) < " f "_len)\n" \
 | 
				
			||||||
 | 
								"\t\treturn false;\n" \
 | 
				
			||||||
 | 
								"\tmemcpy(" f ", r->data + r->offset, " f "_len);\n" \
 | 
				
			||||||
 | 
								"\tr->offset += " f "_len;\n")
 | 
				
			||||||
 | 
						} else if (deserialize) {
 | 
				
			||||||
 | 
							append(cg, "deserialize",
 | 
				
			||||||
 | 
								"\tfor (size_t i = 0; i < " f "_len; i++)\n" \
 | 
				
			||||||
 | 
								indent(sprintf(deserialize, f "[i]")))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_struct(name, cg,    ctype, funcname) {
 | 
				
			||||||
 | 
						ctype = "struct " PrefixLower cameltosnake(name)
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print ctype " {"
 | 
				
			||||||
 | 
						print cg["fields"] "};"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cg["dispose"]) {
 | 
				
			||||||
 | 
							funcname = PrefixLower cameltosnake(name) "_free"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
							print "static void\n" funcname "(" ctype " *self) {"
 | 
				
			||||||
 | 
							print cg["dispose"] "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenDispose[name] = "\t" funcname "(&%s);\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cg["serialize"]) {
 | 
				
			||||||
 | 
							funcname = PrefixLower cameltosnake(name) "_serialize"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
							print "static bool\n" \
 | 
				
			||||||
 | 
								  funcname "(\n\t\t" ctype " *self, struct str *w) {"
 | 
				
			||||||
 | 
							print cg["serialize"] "\treturn true;"
 | 
				
			||||||
 | 
							print "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenSerialize[name] = "\tif (!" funcname "(&%s, w))\n" \
 | 
				
			||||||
 | 
								"\t\treturn false;\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cg["deserialize"]) {
 | 
				
			||||||
 | 
							funcname = PrefixLower cameltosnake(name) "_deserialize"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
							print "static bool\n" \
 | 
				
			||||||
 | 
								  funcname "(\n\t\t" ctype " *self, struct msg_unpacker *r) {"
 | 
				
			||||||
 | 
							print cg["deserialize"] "\treturn true;"
 | 
				
			||||||
 | 
							print "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenDeserialize[name] = "\tif (!" funcname "(&%s, r))\n" \
 | 
				
			||||||
 | 
								"\t\treturn false;\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenCType[name] = ctype
 | 
				
			||||||
 | 
						for (i in cg)
 | 
				
			||||||
 | 
							delete cg[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_union_tag(d, cg) {
 | 
				
			||||||
 | 
						cg["tagtype"] = d["type"]
 | 
				
			||||||
 | 
						cg["tagname"] = d["name"]
 | 
				
			||||||
 | 
						append(cg, "fields", "\t" CodegenCType[d["type"]] " " d["name"] ";\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_union_struct( \
 | 
				
			||||||
 | 
							name, casename, cg, scg,     structname, fieldname, fullcasename) {
 | 
				
			||||||
 | 
						# Don't generate obviously useless structs.
 | 
				
			||||||
 | 
						fullcasename = toupper(cameltosnake(cg["tagtype"])) "_" casename
 | 
				
			||||||
 | 
						if (!scg["dispose"] && !scg["deserialize"]) {
 | 
				
			||||||
 | 
							append(cg, "structless", "\tcase " PrefixUpper fullcasename ":\n")
 | 
				
			||||||
 | 
							for (i in scg)
 | 
				
			||||||
 | 
								delete scg[i]
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# And thus not all generated structs are present in Types.
 | 
				
			||||||
 | 
						structname = name "_" casename
 | 
				
			||||||
 | 
						fieldname = tolower(casename)
 | 
				
			||||||
 | 
						codegen_struct(structname, scg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						append(cg, "fields", "\t" CodegenCType[structname] " " fieldname ";\n")
 | 
				
			||||||
 | 
						if (CodegenDispose[structname])
 | 
				
			||||||
 | 
							append(cg, "dispose", "\tcase " PrefixUpper fullcasename ":\n" \
 | 
				
			||||||
 | 
								indent(sprintf(CodegenDispose[structname], "self->" fieldname)) \
 | 
				
			||||||
 | 
								"\t\tbreak;\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# With no de/serialization code, this will simply recognize the tag.
 | 
				
			||||||
 | 
						append(cg, "serialize", "\tcase " PrefixUpper fullcasename ":\n" \
 | 
				
			||||||
 | 
							indent(sprintf(CodegenSerialize[structname], "self->" fieldname)) \
 | 
				
			||||||
 | 
							"\t\tbreak;\n")
 | 
				
			||||||
 | 
						append(cg, "deserialize", "\tcase " PrefixUpper fullcasename ":\n" \
 | 
				
			||||||
 | 
							indent(sprintf(CodegenDeserialize[structname], "self->" fieldname)) \
 | 
				
			||||||
 | 
							"\t\tbreak;\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_union(name, cg,    f, ctype, funcname) {
 | 
				
			||||||
 | 
						ctype = "union " PrefixLower cameltosnake(name)
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print ctype " {"
 | 
				
			||||||
 | 
						print cg["fields"] "};"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f = "self->" cg["tagname"]
 | 
				
			||||||
 | 
						if (cg["dispose"]) {
 | 
				
			||||||
 | 
							funcname = PrefixLower cameltosnake(name) "_free"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
							print "static void\n" funcname "(" ctype " *self) {"
 | 
				
			||||||
 | 
							print "\tswitch (" f ") {"
 | 
				
			||||||
 | 
							if (cg["structless"])
 | 
				
			||||||
 | 
								print cg["structless"] \
 | 
				
			||||||
 | 
									indent(sprintf(CodegenDispose[cg["tagtype"]], f)) "\t\tbreak;"
 | 
				
			||||||
 | 
							print cg["dispose"] "\tdefault:"
 | 
				
			||||||
 | 
							print "\t\tbreak;"
 | 
				
			||||||
 | 
							print "\t}"
 | 
				
			||||||
 | 
							print "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenDispose[name] = "\t" funcname "(&%s);\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cg["serialize"]) {
 | 
				
			||||||
 | 
							funcname = PrefixLower cameltosnake(name) "_serialize"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
							print "static bool\n" \
 | 
				
			||||||
 | 
								  funcname "(\n\t\t" ctype " *self, struct str *w) {"
 | 
				
			||||||
 | 
							print "\tswitch (" f ") {"
 | 
				
			||||||
 | 
							if (cg["structless"])
 | 
				
			||||||
 | 
								print cg["structless"] \
 | 
				
			||||||
 | 
									indent(sprintf(CodegenSerialize[cg["tagtype"]], f)) "\t\tbreak;"
 | 
				
			||||||
 | 
							print cg["serialize"] "\tdefault:"
 | 
				
			||||||
 | 
							print "\t\treturn false;"
 | 
				
			||||||
 | 
							print "\t}"
 | 
				
			||||||
 | 
							print "\treturn true;"
 | 
				
			||||||
 | 
							print "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenSerialize[name] = "\tif (!" funcname "(&%s, w))\n" \
 | 
				
			||||||
 | 
								"\t\treturn false;\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cg["deserialize"]) {
 | 
				
			||||||
 | 
							funcname = PrefixLower cameltosnake(name) "_deserialize"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
							print "static bool\n" \
 | 
				
			||||||
 | 
								  funcname "(\n\t\t" ctype " *self, struct msg_unpacker *r) {"
 | 
				
			||||||
 | 
							print sprintf(CodegenDeserialize[cg["tagtype"]], f)
 | 
				
			||||||
 | 
							print "\tswitch (" f ") {"
 | 
				
			||||||
 | 
							if (cg["structless"])
 | 
				
			||||||
 | 
								print cg["structless"] "\t\tbreak;"
 | 
				
			||||||
 | 
							print cg["deserialize"] "\tdefault:"
 | 
				
			||||||
 | 
							print "\t\treturn false;"
 | 
				
			||||||
 | 
							print "\t}"
 | 
				
			||||||
 | 
							print "\treturn true;"
 | 
				
			||||||
 | 
							print "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenDeserialize[name] = "\tif (!" funcname "(&%s, r))\n" \
 | 
				
			||||||
 | 
								"\t\treturn false;\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenCType[name] = ctype
 | 
				
			||||||
 | 
						for (i in cg)
 | 
				
			||||||
 | 
							delete cg[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										447
									
								
								xC-gen-proto-go.awk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								xC-gen-proto-go.awk
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,447 @@
 | 
				
			|||||||
 | 
					# xC-gen-proto-go.awk: Go backend for xC-gen-proto.awk.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: 0BSD
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This backend also enables proxying to other endpoints using JSON.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function define_internal(name, gotype) {
 | 
				
			||||||
 | 
						Types[name] = "internal"
 | 
				
			||||||
 | 
						CodegenGoType[name] = gotype
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function define_sint(size,    shortname, gotype) {
 | 
				
			||||||
 | 
						shortname = "i" size
 | 
				
			||||||
 | 
						gotype = "int" size
 | 
				
			||||||
 | 
						define_internal(shortname, gotype)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (size == 8) {
 | 
				
			||||||
 | 
							CodegenSerialize[shortname] = "\tdata = append(data, uint8(%s))\n"
 | 
				
			||||||
 | 
							CodegenDeserialize[shortname] = \
 | 
				
			||||||
 | 
								"\tif len(data) >= 1 {\n" \
 | 
				
			||||||
 | 
								"\t\t%s, data = int8(data[0]), data[1:]\n" \
 | 
				
			||||||
 | 
								"\t} else {\n" \
 | 
				
			||||||
 | 
								"\t\treturn nil, false\n" \
 | 
				
			||||||
 | 
								"\t}\n"
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenSerialize[shortname] = \
 | 
				
			||||||
 | 
							"\tdata = binary.BigEndian.AppendUint" size "(data, uint" size "(%s))\n"
 | 
				
			||||||
 | 
						CodegenDeserialize[shortname] = \
 | 
				
			||||||
 | 
							"\tif len(data) >= " (size / 8) " {\n" \
 | 
				
			||||||
 | 
							"\t\t%s = " gotype "(binary.BigEndian.Uint" size "(data))\n" \
 | 
				
			||||||
 | 
							"\t\tdata = data[" (size / 8) ":]\n" \
 | 
				
			||||||
 | 
							"\t} else {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, false\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function define_uint(size,    shortname, gotype) {
 | 
				
			||||||
 | 
						shortname = "u" size
 | 
				
			||||||
 | 
						gotype = "uint" size
 | 
				
			||||||
 | 
						define_internal(shortname, gotype)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Both byte and uint8 luckily marshal as base64-encoded JSON strings.
 | 
				
			||||||
 | 
						if (size == 8) {
 | 
				
			||||||
 | 
							CodegenSerialize[shortname] = "\tdata = append(data, %s)\n"
 | 
				
			||||||
 | 
							CodegenDeserialize[shortname] = \
 | 
				
			||||||
 | 
								"\tif len(data) >= 1 {\n" \
 | 
				
			||||||
 | 
								"\t\t%s, data = data[0], data[1:]\n" \
 | 
				
			||||||
 | 
								"\t} else {\n" \
 | 
				
			||||||
 | 
								"\t\treturn nil, false\n" \
 | 
				
			||||||
 | 
								"\t}\n"
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenSerialize[shortname] = \
 | 
				
			||||||
 | 
							"\tdata = binary.BigEndian.AppendUint" size "(data, %s)\n"
 | 
				
			||||||
 | 
						CodegenDeserialize[shortname] = \
 | 
				
			||||||
 | 
							"\tif len(data) >= " (size / 8) " {\n" \
 | 
				
			||||||
 | 
							"\t\t%s = binary.BigEndian.Uint" size "(data)\n" \
 | 
				
			||||||
 | 
							"\t\tdata = data[" (size / 8) ":]\n" \
 | 
				
			||||||
 | 
							"\t} else {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, false\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_begin() {
 | 
				
			||||||
 | 
						define_sint("8")
 | 
				
			||||||
 | 
						define_sint("16")
 | 
				
			||||||
 | 
						define_sint("32")
 | 
				
			||||||
 | 
						define_sint("64")
 | 
				
			||||||
 | 
						define_uint("8")
 | 
				
			||||||
 | 
						define_uint("16")
 | 
				
			||||||
 | 
						define_uint("32")
 | 
				
			||||||
 | 
						define_uint("64")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						define_internal("bool", "bool")
 | 
				
			||||||
 | 
						CodegenSerialize["bool"] = \
 | 
				
			||||||
 | 
							"\tif %s {\n" \
 | 
				
			||||||
 | 
							"\t\tdata = append(data, 1)\n" \
 | 
				
			||||||
 | 
							"\t} else {\n" \
 | 
				
			||||||
 | 
							"\t\tdata = append(data, 0)\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
						CodegenDeserialize["bool"] = \
 | 
				
			||||||
 | 
							"\tif data, ok = protoConsumeBoolFrom(data, &%s); !ok {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, ok\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						define_internal("string", "string")
 | 
				
			||||||
 | 
						CodegenSerialize["string"] = \
 | 
				
			||||||
 | 
							"\tif data, ok = protoAppendStringTo(data, %s); !ok {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, ok\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
						CodegenDeserialize["string"] = \
 | 
				
			||||||
 | 
							"\tif data, ok = protoConsumeStringFrom(data, &%s); !ok {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, ok\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "package main"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print "import ("
 | 
				
			||||||
 | 
						print "\t`encoding/binary`"
 | 
				
			||||||
 | 
						print "\t`encoding/json`"
 | 
				
			||||||
 | 
						print "\t`errors`"
 | 
				
			||||||
 | 
						print "\t`math`"
 | 
				
			||||||
 | 
						print "\t`strconv`"
 | 
				
			||||||
 | 
						print "\t`unicode/utf8`"
 | 
				
			||||||
 | 
						print ")"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "// protoConsumeBoolFrom tries to deserialize a boolean value"
 | 
				
			||||||
 | 
						print "// from the beginning of a byte stream. When successful,"
 | 
				
			||||||
 | 
						print "// it returns a subslice with any data that might follow."
 | 
				
			||||||
 | 
						print "func protoConsumeBoolFrom(data []byte, b *bool) ([]byte, bool) {"
 | 
				
			||||||
 | 
						print "\tif len(data) < 1 {"
 | 
				
			||||||
 | 
						print "\t\treturn nil, false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\tif data[0] != 0 {"
 | 
				
			||||||
 | 
						print "\t\t*b = true"
 | 
				
			||||||
 | 
						print "\t} else {"
 | 
				
			||||||
 | 
						print "\t\t*b = false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\treturn data[1:], true"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "// protoAppendStringTo tries to serialize a string value,"
 | 
				
			||||||
 | 
						print "// appending it to the end of a byte stream."
 | 
				
			||||||
 | 
						print "func protoAppendStringTo(data []byte, s string) ([]byte, bool) {"
 | 
				
			||||||
 | 
						print "\tif len(s) > math.MaxUint32 {"
 | 
				
			||||||
 | 
						print "\t\treturn nil, false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\tdata = binary.BigEndian.AppendUint32(data, uint32(len(s)))"
 | 
				
			||||||
 | 
						print "\treturn append(data, s...), true"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "// protoConsumeStringFrom tries to deserialize a string value"
 | 
				
			||||||
 | 
						print "// from the beginning of a byte stream. When successful,"
 | 
				
			||||||
 | 
						print "// it returns a subslice with any data that might follow."
 | 
				
			||||||
 | 
						print "func protoConsumeStringFrom(data []byte, s *string) ([]byte, bool) {"
 | 
				
			||||||
 | 
						print "\tif len(data) < 4 {"
 | 
				
			||||||
 | 
						print "\t\treturn nil, false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\tlength := binary.BigEndian.Uint32(data)"
 | 
				
			||||||
 | 
						print "\tif data = data[4:]; uint64(len(data)) < uint64(length) {"
 | 
				
			||||||
 | 
						print "\t\treturn nil, false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\t*s = string(data[:length])"
 | 
				
			||||||
 | 
						print "\tif !utf8.ValidString(*s) {"
 | 
				
			||||||
 | 
						print "\t\treturn nil, false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\treturn data[length:], true"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "// protoUnmarshalEnumJSON converts a JSON fragment to an integer,"
 | 
				
			||||||
 | 
						print "// ensuring that it's within the expected range of enum values."
 | 
				
			||||||
 | 
						print "func protoUnmarshalEnumJSON(data []byte) (int64, error) {"
 | 
				
			||||||
 | 
						print "\tvar n int64"
 | 
				
			||||||
 | 
						print "\tif err := json.Unmarshal(data, &n); err != nil {"
 | 
				
			||||||
 | 
						print "\t\treturn 0, err"
 | 
				
			||||||
 | 
						print "\t} else if n > math.MaxInt32 || n < math.MinInt32 {"
 | 
				
			||||||
 | 
						print "\t\treturn 0, errors.New(`integer out of range`)"
 | 
				
			||||||
 | 
						print "\t} else {"
 | 
				
			||||||
 | 
						print "\t\treturn n, nil"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_constant(name, value) {
 | 
				
			||||||
 | 
						print "const " PrefixCamel snaketocamel(name) " = " value
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_enum_value(name, subname, value, cg,    goname) {
 | 
				
			||||||
 | 
						goname = PrefixCamel name snaketocamel(subname)
 | 
				
			||||||
 | 
						append(cg, "fields",
 | 
				
			||||||
 | 
							"\t" goname " = " value "\n")
 | 
				
			||||||
 | 
						append(cg, "stringer",
 | 
				
			||||||
 | 
							"\tcase " goname ":\n" \
 | 
				
			||||||
 | 
							"\t\treturn `" snaketocamel(subname) "`\n")
 | 
				
			||||||
 | 
						append(cg, "marshal",
 | 
				
			||||||
 | 
							goname ",\n")
 | 
				
			||||||
 | 
						append(cg, "unmarshal",
 | 
				
			||||||
 | 
							"\tcase `" snaketocamel(subname) "`:\n" \
 | 
				
			||||||
 | 
							"\t\t*v = " goname "\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_enum(name, cg,    gotype, fields) {
 | 
				
			||||||
 | 
						gotype = PrefixCamel name
 | 
				
			||||||
 | 
						print "type " gotype " int"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "const ("
 | 
				
			||||||
 | 
						print cg["fields"] ")"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "func (v " gotype ") String() string {"
 | 
				
			||||||
 | 
						print "\tswitch v {"
 | 
				
			||||||
 | 
						print cg["stringer"] "\tdefault:"
 | 
				
			||||||
 | 
						print "\t\treturn strconv.Itoa(int(v))"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fields = cg["marshal"]
 | 
				
			||||||
 | 
						sub(/,\n$/, ":", fields)
 | 
				
			||||||
 | 
						gsub(/\n/, "\n\t", fields)
 | 
				
			||||||
 | 
						print "func (v " gotype ") MarshalJSON() ([]byte, error) {"
 | 
				
			||||||
 | 
						print "\tswitch v {"
 | 
				
			||||||
 | 
						print indent("case " fields)
 | 
				
			||||||
 | 
						print "\t\treturn json.Marshal(v.String())"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\treturn json.Marshal(int(v))"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "func (v *" gotype ") UnmarshalJSON(data []byte) error {"
 | 
				
			||||||
 | 
						print "\tvar s string"
 | 
				
			||||||
 | 
						print "\tif json.Unmarshal(data, &s) == nil {"
 | 
				
			||||||
 | 
						print "\t\t// Handled below."
 | 
				
			||||||
 | 
						print "\t} else if n, err := protoUnmarshalEnumJSON(data); err != nil {"
 | 
				
			||||||
 | 
						print "\t\treturn err"
 | 
				
			||||||
 | 
						print "\t} else {"
 | 
				
			||||||
 | 
						print "\t\t*v = " gotype "(n)"
 | 
				
			||||||
 | 
						print "\t\treturn nil"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print "\tswitch s {"
 | 
				
			||||||
 | 
						print cg["unmarshal"] "\tdefault:"
 | 
				
			||||||
 | 
						print "\t\treturn errors.New(`unrecognized value: ` + s)"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\treturn nil"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# XXX: This should also check if it isn't out-of-range for any reason,
 | 
				
			||||||
 | 
						# but our usage of sprintf() stands in the way a bit.
 | 
				
			||||||
 | 
						CodegenSerialize[name] = \
 | 
				
			||||||
 | 
							"\tdata = binary.BigEndian.AppendUint32(data, uint32(%s))\n"
 | 
				
			||||||
 | 
						CodegenDeserialize[name] = \
 | 
				
			||||||
 | 
							"\tif len(data) >= 4 {\n" \
 | 
				
			||||||
 | 
							"\t\t%s = " gotype "(int32(binary.BigEndian.Uint32(data)))\n" \
 | 
				
			||||||
 | 
							"\t\tdata = data[4:]\n" \
 | 
				
			||||||
 | 
							"\t} else {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, false\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenGoType[name] = gotype
 | 
				
			||||||
 | 
						for (i in cg)
 | 
				
			||||||
 | 
							delete cg[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_struct_field(d, cg,    camel, f, serialize, deserialize) {
 | 
				
			||||||
 | 
						camel = snaketocamel(d["name"])
 | 
				
			||||||
 | 
						f = "s." camel
 | 
				
			||||||
 | 
						serialize = CodegenSerialize[d["type"]]
 | 
				
			||||||
 | 
						deserialize = CodegenDeserialize[d["type"]]
 | 
				
			||||||
 | 
						if (!d["isarray"]) {
 | 
				
			||||||
 | 
							append(cg, "fields", "\t" camel " " CodegenGoType[d["type"]] \
 | 
				
			||||||
 | 
								" `json:\"" decapitalize(camel) "\"`\n")
 | 
				
			||||||
 | 
							append(cg, "serialize", sprintf(serialize, f))
 | 
				
			||||||
 | 
							append(cg, "deserialize", sprintf(deserialize, f))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						append(cg, "fields", "\t" camel " []" CodegenGoType[d["type"]] \
 | 
				
			||||||
 | 
							" `json:\"" decapitalize(camel) "\"`\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# XXX: This should also check if it isn't out-of-range for any reason.
 | 
				
			||||||
 | 
						append(cg, "serialize",
 | 
				
			||||||
 | 
							sprintf(CodegenSerialize["u32"], "uint32(len(" f "))"))
 | 
				
			||||||
 | 
						if (d["type"] == "u8") {
 | 
				
			||||||
 | 
							append(cg, "serialize",
 | 
				
			||||||
 | 
								"\tdata = append(data, " f "...)\n")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							append(cg, "serialize",
 | 
				
			||||||
 | 
								"\tfor i := 0; i < len(" f "); i++ {\n" \
 | 
				
			||||||
 | 
								indent(sprintf(serialize, f "[i]")) \
 | 
				
			||||||
 | 
								"\t}\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						append(cg, "deserialize",
 | 
				
			||||||
 | 
							"\t{\n" \
 | 
				
			||||||
 | 
							"\t\tvar length uint32\n" \
 | 
				
			||||||
 | 
							indent(sprintf(CodegenDeserialize["u32"], "length")))
 | 
				
			||||||
 | 
						if (d["type"] == "u8") {
 | 
				
			||||||
 | 
							append(cg, "deserialize",
 | 
				
			||||||
 | 
								"\t\tif uint64(len(data)) < uint64(length) {\n" \
 | 
				
			||||||
 | 
								"\t\t\treturn nil, false\n" \
 | 
				
			||||||
 | 
								"\t\t}\n" \
 | 
				
			||||||
 | 
								"\t\t" f ", data = data[:length], data[length:]\n" \
 | 
				
			||||||
 | 
								"\t}\n")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							append(cg, "deserialize",
 | 
				
			||||||
 | 
								"\t\t" f " = make([]" CodegenGoType[d["type"]] ", length)\n" \
 | 
				
			||||||
 | 
								"\t}\n" \
 | 
				
			||||||
 | 
								"\tfor i := 0; i < len(" f "); i++ {\n" \
 | 
				
			||||||
 | 
								indent(sprintf(deserialize, f "[i]")) \
 | 
				
			||||||
 | 
								"\t}\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_struct_tag(d, cg,    camel, f) {
 | 
				
			||||||
 | 
						camel = snaketocamel(d["name"])
 | 
				
			||||||
 | 
						f = "s." camel
 | 
				
			||||||
 | 
						append(cg, "fields", "\t" camel " " CodegenGoType[d["type"]] \
 | 
				
			||||||
 | 
							" `json:\"" decapitalize(camel) "\"`\n")
 | 
				
			||||||
 | 
						append(cg, "serialize", sprintf(CodegenSerialize[d["type"]], f))
 | 
				
			||||||
 | 
						# Do not deserialize here, that is already done by the containing union.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_struct(name, cg,    gotype) {
 | 
				
			||||||
 | 
						gotype = PrefixCamel name
 | 
				
			||||||
 | 
						print "type " gotype " struct {\n" cg["fields"] "}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cg["serialize"]) {
 | 
				
			||||||
 | 
							print "func (s *" gotype ") AppendTo(data []byte) ([]byte, bool) {"
 | 
				
			||||||
 | 
							print "\tok := true"
 | 
				
			||||||
 | 
							print cg["serialize"] "\treturn data, ok"
 | 
				
			||||||
 | 
							print "}"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenSerialize[name] = \
 | 
				
			||||||
 | 
								"\tif data, ok = %s.AppendTo(data); !ok {\n" \
 | 
				
			||||||
 | 
								"\t\treturn nil, ok\n" \
 | 
				
			||||||
 | 
								"\t}\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cg["deserialize"]) {
 | 
				
			||||||
 | 
							print "func (s *" gotype ") ConsumeFrom(data []byte) ([]byte, bool) {"
 | 
				
			||||||
 | 
							print "\tok := true"
 | 
				
			||||||
 | 
							print cg["deserialize"] "\treturn data, ok"
 | 
				
			||||||
 | 
							print "}"
 | 
				
			||||||
 | 
							print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CodegenDeserialize[name] = \
 | 
				
			||||||
 | 
								"\tif data, ok = %s.ConsumeFrom(data); !ok {\n" \
 | 
				
			||||||
 | 
								"\t\treturn nil, ok\n" \
 | 
				
			||||||
 | 
								"\t}\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenGoType[name] = gotype
 | 
				
			||||||
 | 
						for (i in cg)
 | 
				
			||||||
 | 
							delete cg[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_union_tag(d, cg) {
 | 
				
			||||||
 | 
						cg["tagtype"] = d["type"]
 | 
				
			||||||
 | 
						cg["tagname"] = d["name"]
 | 
				
			||||||
 | 
						# The tag is implied from the type of struct stored in the interface.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_union_struct(name, casename, cg, scg,     structname, init) {
 | 
				
			||||||
 | 
						# And thus not all generated structs are present in Types.
 | 
				
			||||||
 | 
						structname = name snaketocamel(casename)
 | 
				
			||||||
 | 
						codegen_struct(structname, scg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						init = CodegenGoType[structname] "{" snaketocamel(cg["tagname"]) \
 | 
				
			||||||
 | 
							": " decapitalize(snaketocamel(cg["tagname"])) "}"
 | 
				
			||||||
 | 
						append(cg, "unmarshal",
 | 
				
			||||||
 | 
							"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
 | 
				
			||||||
 | 
							"\t\ts := " init "\n" \
 | 
				
			||||||
 | 
							"\t\terr = json.Unmarshal(data, &s)\n" \
 | 
				
			||||||
 | 
							"\t\tu.Interface = s\n")
 | 
				
			||||||
 | 
						append(cg, "serialize",
 | 
				
			||||||
 | 
							"\tcase " CodegenGoType[structname] ":\n" \
 | 
				
			||||||
 | 
							indent(sprintf(CodegenSerialize[structname], "union")))
 | 
				
			||||||
 | 
						append(cg, "deserialize",
 | 
				
			||||||
 | 
							"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
 | 
				
			||||||
 | 
							"\t\ts := " init "\n" \
 | 
				
			||||||
 | 
							indent(sprintf(CodegenDeserialize[structname], "s")) \
 | 
				
			||||||
 | 
							"\t\tu.Interface = s\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function codegen_union(name, cg,    gotype, tagfield, tagvar) {
 | 
				
			||||||
 | 
						gotype = PrefixCamel name
 | 
				
			||||||
 | 
						print "type " gotype " struct {"
 | 
				
			||||||
 | 
						print "\tInterface any"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "func (u *" gotype ") MarshalJSON() ([]byte, error) {"
 | 
				
			||||||
 | 
						print "\treturn json.Marshal(u.Interface)"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tagfield = snaketocamel(cg["tagname"])
 | 
				
			||||||
 | 
						tagvar = decapitalize(tagfield)
 | 
				
			||||||
 | 
						print "func (u *" gotype ") UnmarshalJSON(data []byte) (err error) {"
 | 
				
			||||||
 | 
						print "\tvar t struct {"
 | 
				
			||||||
 | 
						print "\t\t" tagfield " " CodegenGoType[cg["tagtype"]] \
 | 
				
			||||||
 | 
							" `json:\"" tagvar "\"`"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\tif err := json.Unmarshal(data, &t); err != nil {"
 | 
				
			||||||
 | 
						print "\t\treturn err"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
						print "\tswitch " tagvar " := t." tagfield "; " tagvar " {"
 | 
				
			||||||
 | 
						print cg["unmarshal"] "\tdefault:"
 | 
				
			||||||
 | 
						print "\t\terr = errors.New(`unsupported value: ` + " tagvar ".String())"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\treturn err"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# XXX: Consider changing the interface into an AppendTo/ConsumeFrom one,
 | 
				
			||||||
 | 
						# that would eliminate these type case switches entirely.
 | 
				
			||||||
 | 
						# On the other hand, it would make it possible to send unsuitable structs.
 | 
				
			||||||
 | 
						print "func (u *" gotype ") AppendTo(data []byte) ([]byte, bool) {"
 | 
				
			||||||
 | 
						print "\tok := true"
 | 
				
			||||||
 | 
						print "\tswitch union := u.Interface.(type) {"
 | 
				
			||||||
 | 
						print cg["serialize"] "\tdefault:"
 | 
				
			||||||
 | 
						print "\t\treturn nil, false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\treturn data, ok"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenSerialize[name] = \
 | 
				
			||||||
 | 
							"\tif data, ok = %s.AppendTo(data); !ok {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, ok\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "func (u *" gotype ") ConsumeFrom(data []byte) ([]byte, bool) {"
 | 
				
			||||||
 | 
						print "\tok := true"
 | 
				
			||||||
 | 
						print "\tvar " tagvar " " CodegenGoType[cg["tagtype"]]
 | 
				
			||||||
 | 
						print sprintf(CodegenDeserialize[cg["tagtype"]], tagvar)
 | 
				
			||||||
 | 
						print "\tswitch " tagvar " {"
 | 
				
			||||||
 | 
						print cg["deserialize"] "\tdefault:"
 | 
				
			||||||
 | 
						print "\t\treturn nil, false"
 | 
				
			||||||
 | 
						print "\t}"
 | 
				
			||||||
 | 
						print "\treturn data, ok"
 | 
				
			||||||
 | 
						print "}"
 | 
				
			||||||
 | 
						print ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenDeserialize[name] = \
 | 
				
			||||||
 | 
							"\tif data, ok = %s.ConsumeFrom(data); !ok {\n" \
 | 
				
			||||||
 | 
							"\t\treturn nil, ok\n" \
 | 
				
			||||||
 | 
							"\t}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CodegenGoType[name] = gotype
 | 
				
			||||||
 | 
						for (i in cg)
 | 
				
			||||||
 | 
							delete cg[i]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										303
									
								
								xC-gen-proto.awk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								xC-gen-proto.awk
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,303 @@
 | 
				
			|||||||
 | 
					# xC-gen-proto.awk: an XDR-derived code generator for network protocols.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: 0BSD
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You may read RFC 4506 for context, however it is only a source of inspiration.
 | 
				
			||||||
 | 
					# Grammar is easy to deduce from the parser.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Native types: bool, u{8,16,32,64}, i{8,16,32,64}, string
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Don't define any new types, unless you hate yourself, then it's okay to do so.
 | 
				
			||||||
 | 
					# Both backends are a pain in the arse, for different reasons.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# All numbers are encoded in big-endian byte order.
 | 
				
			||||||
 | 
					# Booleans are one byte each.
 | 
				
			||||||
 | 
					# Strings must be valid UTF-8, use u8<> to lift that restriction.
 | 
				
			||||||
 | 
					# String and array lengths are encoded as u32.
 | 
				
			||||||
 | 
					# Enumeration values automatically start at 1, and are encoded as i32.
 | 
				
			||||||
 | 
					# Any struct or union field may be a variable-length array.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Message framing is done externally, but also happens to prefix u32 lengths.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Usage: env LC_ALL=C awk -v prefix=Relay \
 | 
				
			||||||
 | 
					#  -f xC-gen-proto.awk        < xC-proto \
 | 
				
			||||||
 | 
					#  -f xC-gen-proto-{c,go}.awk > xC-proto.{c,go} | {clang-format,gofmt}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# --- Utilities ----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function cameltosnake(s) {
 | 
				
			||||||
 | 
						while (match(s, /[[:lower:]][[:upper:]]/)) {
 | 
				
			||||||
 | 
							s = substr(s, 1, RSTART) "_" \
 | 
				
			||||||
 | 
								tolower(substr(s, RSTART + 1, RLENGTH - 1)) \
 | 
				
			||||||
 | 
								substr(s, RSTART + RLENGTH)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return tolower(s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function snaketocamel(s) {
 | 
				
			||||||
 | 
						s = toupper(substr(s, 1, 1)) tolower(substr(s, 2))
 | 
				
			||||||
 | 
						while (match(s, /_[[:alnum:]]/)) {
 | 
				
			||||||
 | 
							s = substr(s, 1, RSTART - 1) \
 | 
				
			||||||
 | 
								toupper(substr(s, RSTART + 1, RLENGTH - 1)) \
 | 
				
			||||||
 | 
								substr(s, RSTART + RLENGTH)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function decapitalize(s) {
 | 
				
			||||||
 | 
						if (match(s, /[[:upper:]][[:lower:]]/)) {
 | 
				
			||||||
 | 
							return tolower(substr(s, 1, 1)) substr(s, 2)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function indent(s) {
 | 
				
			||||||
 | 
						if (!s)
 | 
				
			||||||
 | 
							return s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gsub(/\n/, "\n\t", s)
 | 
				
			||||||
 | 
						sub(/\t*$/, "", s)
 | 
				
			||||||
 | 
						return "\t" s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function append(a, key, value) {
 | 
				
			||||||
 | 
						a[key] = a[key] value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# --- Parsing ------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function fatal(message) {
 | 
				
			||||||
 | 
						print "// " FILENAME ":" FNR ": fatal error: " message
 | 
				
			||||||
 | 
						print FILENAME ":" FNR ": fatal error: " message > "/dev/stderr"
 | 
				
			||||||
 | 
						exit 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function skipcomment() {
 | 
				
			||||||
 | 
						do {
 | 
				
			||||||
 | 
							if (match($0, /[*][/]/)) {
 | 
				
			||||||
 | 
								$0 = substr($0, RSTART + RLENGTH)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} while (getline > 0)
 | 
				
			||||||
 | 
						fatal("unterminated block comment")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function nexttoken() {
 | 
				
			||||||
 | 
						do {
 | 
				
			||||||
 | 
							if (match($0, /^[[:space:]]+/)) {
 | 
				
			||||||
 | 
								$0 = substr($0, RLENGTH + 1)
 | 
				
			||||||
 | 
							} else if (match($0, /^[/][/].*/)) {
 | 
				
			||||||
 | 
								$0 = ""
 | 
				
			||||||
 | 
							} else if (match($0, /^[/][*]/)) {
 | 
				
			||||||
 | 
								$0 = substr($0, RLENGTH + 1)
 | 
				
			||||||
 | 
								skipcomment()
 | 
				
			||||||
 | 
							} else if (match($0, /^[[:alpha:]][[:alnum:]_]*/)) {
 | 
				
			||||||
 | 
								Token = substr($0, 1, RLENGTH)
 | 
				
			||||||
 | 
								$0 = substr($0, RLENGTH + 1)
 | 
				
			||||||
 | 
								return Token
 | 
				
			||||||
 | 
							} else if (match($0, /^(0[xX][0-9a-fA-F]+|[1-9][0-9]*)/)) {
 | 
				
			||||||
 | 
								Token = substr($0, 1, RLENGTH)
 | 
				
			||||||
 | 
								$0 = substr($0, RLENGTH + 1)
 | 
				
			||||||
 | 
								return Token
 | 
				
			||||||
 | 
							} else if (/./) {
 | 
				
			||||||
 | 
								Token = substr($0, 1, 1)
 | 
				
			||||||
 | 
								$0 = substr($0, 2)
 | 
				
			||||||
 | 
								return Token
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} while (/./ || getline > 0)
 | 
				
			||||||
 | 
						Token = ""
 | 
				
			||||||
 | 
						return Token
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function expect(v) {
 | 
				
			||||||
 | 
						if (!v)
 | 
				
			||||||
 | 
							fatal("broken expectations at `" Token "' before `" $0 "'")
 | 
				
			||||||
 | 
						return v
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function accept(what) {
 | 
				
			||||||
 | 
						if (Token != what)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						nexttoken()
 | 
				
			||||||
 | 
						return 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function identifier(    v) {
 | 
				
			||||||
 | 
						if (Token !~ /^[[:alpha:]]/)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						v = Token
 | 
				
			||||||
 | 
						nexttoken()
 | 
				
			||||||
 | 
						return v
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function number(    v) {
 | 
				
			||||||
 | 
						if (Token !~ /^[0-9]/)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						v = Token
 | 
				
			||||||
 | 
						nexttoken()
 | 
				
			||||||
 | 
						return v
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function readnumber(    ident) {
 | 
				
			||||||
 | 
						ident = identifier()
 | 
				
			||||||
 | 
						if (!ident)
 | 
				
			||||||
 | 
							return expect(number())
 | 
				
			||||||
 | 
						if (!(ident in Consts))
 | 
				
			||||||
 | 
							fatal("unknown constant: " ident)
 | 
				
			||||||
 | 
						return Consts[ident]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function defconst(    ident, num) {
 | 
				
			||||||
 | 
						if (!accept("const"))
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ident = expect(identifier())
 | 
				
			||||||
 | 
						expect(accept("="))
 | 
				
			||||||
 | 
						num = readnumber()
 | 
				
			||||||
 | 
						if (ident in Consts)
 | 
				
			||||||
 | 
							fatal("constant redefined: " ident)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Consts[ident] = num
 | 
				
			||||||
 | 
						codegen_constant(ident, num)
 | 
				
			||||||
 | 
						return 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function readtype(    ident) {
 | 
				
			||||||
 | 
						ident = deftype()
 | 
				
			||||||
 | 
						if (ident)
 | 
				
			||||||
 | 
							return ident
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ident = identifier()
 | 
				
			||||||
 | 
						if (!ident)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!(ident in Types))
 | 
				
			||||||
 | 
							fatal("unknown type: " ident)
 | 
				
			||||||
 | 
						return ident
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function defenum(    name, ident, value, cg) {
 | 
				
			||||||
 | 
						delete cg[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name = expect(identifier())
 | 
				
			||||||
 | 
						expect(accept("{"))
 | 
				
			||||||
 | 
						while (!accept("}")) {
 | 
				
			||||||
 | 
							ident = expect(identifier())
 | 
				
			||||||
 | 
							value = value + 1
 | 
				
			||||||
 | 
							if (accept("="))
 | 
				
			||||||
 | 
								value = readnumber()
 | 
				
			||||||
 | 
							if (!value)
 | 
				
			||||||
 | 
								fatal("enumeration values cannot be zero")
 | 
				
			||||||
 | 
							expect(accept(","))
 | 
				
			||||||
 | 
							append(EnumValues, name, SUBSEP ident)
 | 
				
			||||||
 | 
							if (EnumValues[name, ident]++)
 | 
				
			||||||
 | 
								fatal("duplicate enum value: " ident)
 | 
				
			||||||
 | 
							codegen_enum_value(name, ident, value, cg)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Types[name] = "enum"
 | 
				
			||||||
 | 
						codegen_enum(name, cg)
 | 
				
			||||||
 | 
						return name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function readfield(out,    nonvoid) {
 | 
				
			||||||
 | 
						nonvoid = !accept("void")
 | 
				
			||||||
 | 
						if (nonvoid) {
 | 
				
			||||||
 | 
							out["type"] = expect(readtype())
 | 
				
			||||||
 | 
							out["name"] = expect(identifier())
 | 
				
			||||||
 | 
							# TODO: Consider supporting XDR's VLA length limits here.
 | 
				
			||||||
 | 
							# TODO: Consider supporting XDR's fixed-length syntax for string limits.
 | 
				
			||||||
 | 
							out["isarray"] = accept("<") && expect(accept(">"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						expect(accept(";"))
 | 
				
			||||||
 | 
						return nonvoid
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function defstruct(    name, d, cg) {
 | 
				
			||||||
 | 
						delete d[0]
 | 
				
			||||||
 | 
						delete cg[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name = expect(identifier())
 | 
				
			||||||
 | 
						expect(accept("{"))
 | 
				
			||||||
 | 
						while (!accept("}")) {
 | 
				
			||||||
 | 
							if (readfield(d))
 | 
				
			||||||
 | 
								codegen_struct_field(d, cg)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Types[name] = "struct"
 | 
				
			||||||
 | 
						codegen_struct(name, cg)
 | 
				
			||||||
 | 
						return name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function defunion(    name, tag, tagtype, tagvalue, cg, scg, d, a, i, unseen) {
 | 
				
			||||||
 | 
						delete cg[0]
 | 
				
			||||||
 | 
						delete scg[0]
 | 
				
			||||||
 | 
						delete d[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name = expect(identifier())
 | 
				
			||||||
 | 
						expect(accept("switch"))
 | 
				
			||||||
 | 
						expect(accept("("))
 | 
				
			||||||
 | 
						tag["type"] = tagtype = expect(readtype())
 | 
				
			||||||
 | 
						tag["name"] = expect(identifier())
 | 
				
			||||||
 | 
						expect(accept(")"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (Types[tagtype] != "enum")
 | 
				
			||||||
 | 
							fatal("not an enum type: " tagtype)
 | 
				
			||||||
 | 
						codegen_union_tag(tag, cg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						split(EnumValues[tagtype], a, SUBSEP)
 | 
				
			||||||
 | 
						for (i in a)
 | 
				
			||||||
 | 
							unseen[a[i]]++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expect(accept("{"))
 | 
				
			||||||
 | 
						while (!accept("}")) {
 | 
				
			||||||
 | 
							if (accept("case")) {
 | 
				
			||||||
 | 
								if (tagvalue)
 | 
				
			||||||
 | 
									codegen_union_struct(name, tagvalue, cg, scg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								tagvalue = expect(identifier())
 | 
				
			||||||
 | 
								expect(accept(":"))
 | 
				
			||||||
 | 
								if (!unseen[tagvalue]--)
 | 
				
			||||||
 | 
									fatal("no such value or duplicate case: " tagtype "." tagvalue)
 | 
				
			||||||
 | 
								codegen_struct_tag(tag, scg)
 | 
				
			||||||
 | 
							} else if (tagvalue) {
 | 
				
			||||||
 | 
								if (readfield(d))
 | 
				
			||||||
 | 
									codegen_struct_field(d, scg)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								fatal("union fields must fall under a case")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (tagvalue)
 | 
				
			||||||
 | 
							codegen_union_struct(name, tagvalue, cg, scg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# What remains non-zero in unseen[2..] is simply not recognized/allowed.
 | 
				
			||||||
 | 
						Types[name] = "union"
 | 
				
			||||||
 | 
						codegen_union(name, cg)
 | 
				
			||||||
 | 
						return name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function deftype() {
 | 
				
			||||||
 | 
						if (accept("enum"))
 | 
				
			||||||
 | 
							return defenum()
 | 
				
			||||||
 | 
						if (accept("struct"))
 | 
				
			||||||
 | 
							return defstruct()
 | 
				
			||||||
 | 
						if (accept("union"))
 | 
				
			||||||
 | 
							return defunion()
 | 
				
			||||||
 | 
						return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BEGIN {
 | 
				
			||||||
 | 
						PrefixLower = "relay_"
 | 
				
			||||||
 | 
						PrefixUpper = "RELAY_"
 | 
				
			||||||
 | 
						PrefixCamel = "Relay"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print "// Generated by xC-gen-proto.awk. DO NOT MODIFY."
 | 
				
			||||||
 | 
						codegen_begin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nexttoken()
 | 
				
			||||||
 | 
						while (Token != "") {
 | 
				
			||||||
 | 
							expect(defconst() || deftype())
 | 
				
			||||||
 | 
							expect(accept(";"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										120
									
								
								xC-proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								xC-proto
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					// Backwards-compatible protocol version.
 | 
				
			||||||
 | 
					const VERSION = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// From the frontend to the relay.
 | 
				
			||||||
 | 
					struct CommandMessage {
 | 
				
			||||||
 | 
						u32 command_seq;
 | 
				
			||||||
 | 
						union CommandData switch (enum Command {
 | 
				
			||||||
 | 
							HELLO,
 | 
				
			||||||
 | 
							PING,
 | 
				
			||||||
 | 
							ACTIVE,
 | 
				
			||||||
 | 
							BUFFER_COMPLETE,
 | 
				
			||||||
 | 
							BUFFER_INPUT,
 | 
				
			||||||
 | 
							BUFFER_ACTIVATE,
 | 
				
			||||||
 | 
							BUFFER_LOG,
 | 
				
			||||||
 | 
						} command) {
 | 
				
			||||||
 | 
						case HELLO:
 | 
				
			||||||
 | 
							u32 version;
 | 
				
			||||||
 | 
						case PING:
 | 
				
			||||||
 | 
							void;
 | 
				
			||||||
 | 
						case ACTIVE:
 | 
				
			||||||
 | 
							void;
 | 
				
			||||||
 | 
						case BUFFER_COMPLETE:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
							string text;
 | 
				
			||||||
 | 
							u32 position;
 | 
				
			||||||
 | 
						case BUFFER_INPUT:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
							string text;
 | 
				
			||||||
 | 
						case BUFFER_ACTIVATE:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
						case BUFFER_LOG:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
						} data;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// From the relay to the frontend.
 | 
				
			||||||
 | 
					struct EventMessage {
 | 
				
			||||||
 | 
						u32 event_seq;
 | 
				
			||||||
 | 
						union EventData switch (enum Event {
 | 
				
			||||||
 | 
							PING,
 | 
				
			||||||
 | 
							BUFFER_UPDATE,
 | 
				
			||||||
 | 
							BUFFER_RENAME,
 | 
				
			||||||
 | 
							BUFFER_REMOVE,
 | 
				
			||||||
 | 
							BUFFER_ACTIVATE,
 | 
				
			||||||
 | 
							BUFFER_LINE,
 | 
				
			||||||
 | 
							BUFFER_CLEAR,
 | 
				
			||||||
 | 
							ERROR,
 | 
				
			||||||
 | 
							RESPONSE,
 | 
				
			||||||
 | 
						} event) {
 | 
				
			||||||
 | 
						case PING:
 | 
				
			||||||
 | 
							void;
 | 
				
			||||||
 | 
						case BUFFER_UPDATE:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
						case BUFFER_RENAME:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
							string new;
 | 
				
			||||||
 | 
						case BUFFER_REMOVE:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
						case BUFFER_ACTIVATE:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
						case BUFFER_LINE:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
							bool is_unimportant;
 | 
				
			||||||
 | 
							bool is_highlight;
 | 
				
			||||||
 | 
							enum Rendition {
 | 
				
			||||||
 | 
								BARE,
 | 
				
			||||||
 | 
								INDENT,
 | 
				
			||||||
 | 
								STATUS,
 | 
				
			||||||
 | 
								ERROR,
 | 
				
			||||||
 | 
								JOIN,
 | 
				
			||||||
 | 
								PART,
 | 
				
			||||||
 | 
							} rendition;
 | 
				
			||||||
 | 
							// Unix timestamp in seconds.
 | 
				
			||||||
 | 
							u64 when;
 | 
				
			||||||
 | 
							// Broken-up text, with in-band formatting.
 | 
				
			||||||
 | 
							union ItemData switch (enum Item {
 | 
				
			||||||
 | 
								TEXT,
 | 
				
			||||||
 | 
								RESET,
 | 
				
			||||||
 | 
								FG_COLOR,
 | 
				
			||||||
 | 
								BG_COLOR,
 | 
				
			||||||
 | 
								FLIP_BOLD,
 | 
				
			||||||
 | 
								FLIP_ITALIC,
 | 
				
			||||||
 | 
								FLIP_UNDERLINE,
 | 
				
			||||||
 | 
								FLIP_INVERSE,
 | 
				
			||||||
 | 
								FLIP_CROSSED_OUT,
 | 
				
			||||||
 | 
								FLIP_MONOSPACE,
 | 
				
			||||||
 | 
							} kind) {
 | 
				
			||||||
 | 
							case TEXT:
 | 
				
			||||||
 | 
								string text;
 | 
				
			||||||
 | 
							case RESET:
 | 
				
			||||||
 | 
								void;
 | 
				
			||||||
 | 
							case FG_COLOR:
 | 
				
			||||||
 | 
								i16 color;
 | 
				
			||||||
 | 
							case BG_COLOR:
 | 
				
			||||||
 | 
								i16 color;
 | 
				
			||||||
 | 
							case FLIP_BOLD:
 | 
				
			||||||
 | 
							case FLIP_ITALIC:
 | 
				
			||||||
 | 
							case FLIP_UNDERLINE:
 | 
				
			||||||
 | 
							case FLIP_INVERSE:
 | 
				
			||||||
 | 
							case FLIP_CROSSED_OUT:
 | 
				
			||||||
 | 
							case FLIP_MONOSPACE:
 | 
				
			||||||
 | 
								void;
 | 
				
			||||||
 | 
							} items<>;
 | 
				
			||||||
 | 
						case BUFFER_CLEAR:
 | 
				
			||||||
 | 
							string buffer_name;
 | 
				
			||||||
 | 
						case ERROR:
 | 
				
			||||||
 | 
							u32 command_seq;
 | 
				
			||||||
 | 
							string error;
 | 
				
			||||||
 | 
						case RESPONSE:
 | 
				
			||||||
 | 
							u32 command_seq;
 | 
				
			||||||
 | 
							union ResponseData switch (Command command) {
 | 
				
			||||||
 | 
							case BUFFER_COMPLETE:
 | 
				
			||||||
 | 
								u32 start;
 | 
				
			||||||
 | 
								string completions<>;
 | 
				
			||||||
 | 
							case BUFFER_LOG:
 | 
				
			||||||
 | 
								// UTF-8, but not guaranteed.
 | 
				
			||||||
 | 
								u8 log<>;
 | 
				
			||||||
 | 
							} data;
 | 
				
			||||||
 | 
						} data;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										785
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										785
									
								
								xC.c
									
									
									
									
									
								
							@@ -50,6 +50,7 @@ enum
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "common.c"
 | 
					#include "common.c"
 | 
				
			||||||
#include "xD-replies.c"
 | 
					#include "xD-replies.c"
 | 
				
			||||||
 | 
					#include "xC-proto.c"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <math.h>
 | 
					#include <math.h>
 | 
				
			||||||
#include <langinfo.h>
 | 
					#include <langinfo.h>
 | 
				
			||||||
@@ -1526,6 +1527,7 @@ enum buffer_line_flags
 | 
				
			|||||||
	BUFFER_LINE_HIGHLIGHT   = 1 << 2,   ///< The user was highlighted by this
 | 
						BUFFER_LINE_HIGHLIGHT   = 1 << 2,   ///< The user was highlighted by this
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOTE: This sequence must match up with xC-proto, only one lower.
 | 
				
			||||||
enum buffer_line_rendition
 | 
					enum buffer_line_rendition
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	BUFFER_LINE_BARE,                   ///< Unadorned
 | 
						BUFFER_LINE_BARE,                   ///< Unadorned
 | 
				
			||||||
@@ -1666,6 +1668,50 @@ buffer_destroy (struct buffer *self)
 | 
				
			|||||||
REF_COUNTABLE_METHODS (buffer)
 | 
					REF_COUNTABLE_METHODS (buffer)
 | 
				
			||||||
#define buffer_ref do_not_use_dangerous
 | 
					#define buffer_ref do_not_use_dangerous
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ~~~ Relay ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct client
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						LIST_HEADER (struct client)
 | 
				
			||||||
 | 
						struct app_context *ctx;            ///< Application context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Convert this all to TLS, and only TLS, with required client cert.
 | 
				
			||||||
 | 
						//   That means replacing plumbing functions with the /other/ set from xD.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int socket_fd;                      ///< The TCP socket
 | 
				
			||||||
 | 
						struct str read_buffer;             ///< Unprocessed input
 | 
				
			||||||
 | 
						struct str write_buffer;            ///< Output yet to be sent out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t event_seq;                 ///< Outgoing message counter
 | 
				
			||||||
 | 
						bool initialized;                   ///< Initial sync took place
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct poller_fd socket_event;      ///< The socket can be read/written to
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct client *
 | 
				
			||||||
 | 
					client_new (void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct client *self = xcalloc (1, sizeof *self);
 | 
				
			||||||
 | 
						self->socket_fd = -1;
 | 
				
			||||||
 | 
						self->read_buffer = str_make ();
 | 
				
			||||||
 | 
						self->write_buffer = str_make ();
 | 
				
			||||||
 | 
						return self;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					client_destroy (struct client *self)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!soft_assert (self->socket_fd == -1))
 | 
				
			||||||
 | 
							xclose (self->socket_fd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						str_free (&self->read_buffer);
 | 
				
			||||||
 | 
						str_free (&self->write_buffer);
 | 
				
			||||||
 | 
						free (self);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void client_kill (struct client *c);
 | 
				
			||||||
 | 
					static bool client_process_buffer (struct client *c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ~~~ Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
					// ~~~ Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The only real purpose of this is to abstract away TLS
 | 
					// The only real purpose of this is to abstract away TLS
 | 
				
			||||||
@@ -2079,10 +2125,19 @@ struct app_context
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	struct str_map servers;             ///< Our servers
 | 
						struct str_map servers;             ///< Our servers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Relay:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int relay_fd;                       ///< Listening socket FD
 | 
				
			||||||
 | 
						struct client *clients;             ///< Our relay clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// A single message buffer to prepare all outcoming messages within
 | 
				
			||||||
 | 
						struct relay_event_message relay_message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Events:
 | 
						// Events:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct poller_fd tty_event;         ///< Terminal input event
 | 
						struct poller_fd tty_event;         ///< Terminal input event
 | 
				
			||||||
	struct poller_fd signal_event;      ///< Signal FD event
 | 
						struct poller_fd signal_event;      ///< Signal FD event
 | 
				
			||||||
 | 
						struct poller_fd relay_event;       ///< New relay connection available
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct poller_timer flush_timer;    ///< Flush all open files (e.g. logs)
 | 
						struct poller_timer flush_timer;    ///< Flush all open files (e.g. logs)
 | 
				
			||||||
	struct poller_timer date_chg_tmr;   ///< Print a date change
 | 
						struct poller_timer date_chg_tmr;   ///< Print a date change
 | 
				
			||||||
@@ -2129,6 +2184,8 @@ struct app_context
 | 
				
			|||||||
	char *editor_filename;              ///< The file being edited by user
 | 
						char *editor_filename;              ///< The file being edited by user
 | 
				
			||||||
	int terminal_suspended;             ///< Terminal suspension level
 | 
						int terminal_suspended;             ///< Terminal suspension level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Plugins:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct plugin *plugins;             ///< Loaded plugins
 | 
						struct plugin *plugins;             ///< Loaded plugins
 | 
				
			||||||
	struct hook *input_hooks;           ///< Input hooks
 | 
						struct hook *input_hooks;           ///< Input hooks
 | 
				
			||||||
	struct hook *irc_hooks;             ///< IRC hooks
 | 
						struct hook *irc_hooks;             ///< IRC hooks
 | 
				
			||||||
@@ -2197,6 +2254,8 @@ app_context_init (struct app_context *self)
 | 
				
			|||||||
	self->config = config_make ();
 | 
						self->config = config_make ();
 | 
				
			||||||
	poller_init (&self->poller);
 | 
						poller_init (&self->poller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						self->relay_fd = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	self->servers = str_map_make ((str_map_free_fn) server_unref);
 | 
						self->servers = str_map_make ((str_map_free_fn) server_unref);
 | 
				
			||||||
	self->servers.key_xfrm = tolower_ascii_strxfrm;
 | 
						self->servers.key_xfrm = tolower_ascii_strxfrm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2222,6 +2281,17 @@ app_context_init (struct app_context *self)
 | 
				
			|||||||
		filter_color_cube_for_acceptable_nick_colors (&self->nick_palette_len);
 | 
							filter_color_cube_for_acceptable_nick_colors (&self->nick_palette_len);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					app_context_relay_stop (struct app_context *self)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (self->relay_fd != -1)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							poller_fd_reset (&self->relay_event);
 | 
				
			||||||
 | 
							xclose (self->relay_fd);
 | 
				
			||||||
 | 
							self->relay_fd = -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
app_context_free (struct app_context *self)
 | 
					app_context_free (struct app_context *self)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -2247,6 +2317,11 @@ app_context_free (struct app_context *self)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	str_map_free (&self->buffers_by_name);
 | 
						str_map_free (&self->buffers_by_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app_context_relay_stop (self);
 | 
				
			||||||
 | 
						LIST_FOR_EACH (struct client, c, self->clients)
 | 
				
			||||||
 | 
							client_kill (c);
 | 
				
			||||||
 | 
						relay_event_message_free (&self->relay_message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	str_map_free (&self->servers);
 | 
						str_map_free (&self->servers);
 | 
				
			||||||
	poller_free (&self->poller);
 | 
						poller_free (&self->poller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2285,6 +2360,7 @@ on_config_show_all_prefixes_change (struct config_item *item)
 | 
				
			|||||||
	refresh_prompt (ctx);
 | 
						refresh_prompt (ctx);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void on_config_relay_bind_change (struct config_item *item);
 | 
				
			||||||
static void on_config_backlog_limit_change (struct config_item *item);
 | 
					static void on_config_backlog_limit_change (struct config_item *item);
 | 
				
			||||||
static void on_config_attribute_change (struct config_item *item);
 | 
					static void on_config_attribute_change (struct config_item *item);
 | 
				
			||||||
static void on_config_logging_change (struct config_item *item);
 | 
					static void on_config_logging_change (struct config_item *item);
 | 
				
			||||||
@@ -2479,6 +2555,11 @@ static struct config_schema g_config_general[] =
 | 
				
			|||||||
	  .comment   = "Plugins to automatically load on start",
 | 
						  .comment   = "Plugins to automatically load on start",
 | 
				
			||||||
	  .type      = CONFIG_ITEM_STRING_ARRAY,
 | 
						  .type      = CONFIG_ITEM_STRING_ARRAY,
 | 
				
			||||||
	  .validate  = config_validate_nonjunk_string },
 | 
						  .validate  = config_validate_nonjunk_string },
 | 
				
			||||||
 | 
						{ .name      = "relay_bind",
 | 
				
			||||||
 | 
						  .comment   = "Address to bind to for a user interface relay point",
 | 
				
			||||||
 | 
						  .type      = CONFIG_ITEM_STRING,
 | 
				
			||||||
 | 
						  .validate  = config_validate_nonjunk_string,
 | 
				
			||||||
 | 
						  .on_change = on_config_relay_bind_change },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Buffer history:
 | 
						// Buffer history:
 | 
				
			||||||
	{ .name      = "backlog_limit",
 | 
						{ .name      = "backlog_limit",
 | 
				
			||||||
@@ -2681,6 +2762,418 @@ serialize_configuration (struct config_item *root, struct str *output)
 | 
				
			|||||||
	config_item_write (root, true, output);
 | 
						config_item_write (root, true, output);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- Relay plumbing ----------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					client_kill (struct client *c)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct app_context *ctx = c->ctx;
 | 
				
			||||||
 | 
						poller_fd_reset (&c->socket_event);
 | 
				
			||||||
 | 
						xclose (c->socket_fd);
 | 
				
			||||||
 | 
						c->socket_fd = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						LIST_UNLINK (ctx->clients, c);
 | 
				
			||||||
 | 
						client_destroy (c);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					client_try_read (struct client *c)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct str *buf = &c->read_buffer;
 | 
				
			||||||
 | 
						ssize_t n_read;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while ((n_read = read (c->socket_fd, buf->str + buf->len,
 | 
				
			||||||
 | 
							buf->alloc - buf->len - 1 /* null byte */)) > 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							buf->len += n_read;
 | 
				
			||||||
 | 
							if (!client_process_buffer (c))
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							str_reserve (buf, 512);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (n_read < 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (errno == EAGAIN || errno == EINTR)
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							print_debug ("%s: %s: %s", __func__, "read", strerror (errno));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client_kill (c);
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					client_try_write (struct client *c)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct str *buf = &c->write_buffer;
 | 
				
			||||||
 | 
						ssize_t n_written;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while (buf->len)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							n_written = write (c->socket_fd, buf->str, buf->len);
 | 
				
			||||||
 | 
							if (n_written >= 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								str_remove_slice (buf, 0, n_written);
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (errno == EAGAIN || errno == EINTR)
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							print_debug ("%s: %s: %s", __func__, "write", strerror (errno));
 | 
				
			||||||
 | 
							client_kill (c);
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					client_update_poller (struct client *c, const struct pollfd *pfd)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int new_events = POLLIN;
 | 
				
			||||||
 | 
						if (c->write_buffer.len)
 | 
				
			||||||
 | 
							new_events |= POLLOUT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hard_assert (new_events != 0);
 | 
				
			||||||
 | 
						if (!pfd || pfd->events != new_events)
 | 
				
			||||||
 | 
							poller_fd_set (&c->socket_event, new_events);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_client_ready (const struct pollfd *pfd, void *user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct client *c = user_data;
 | 
				
			||||||
 | 
						if (client_try_read (c) && client_try_write (c))
 | 
				
			||||||
 | 
							client_update_poller (c, pfd);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					relay_try_fetch_client (struct app_context *ctx, int listen_fd)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// XXX: `struct sockaddr_storage' is not the most portable thing
 | 
				
			||||||
 | 
						struct sockaddr_storage peer;
 | 
				
			||||||
 | 
						socklen_t peer_len = sizeof peer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int fd = accept (listen_fd, (struct sockaddr *) &peer, &peer_len);
 | 
				
			||||||
 | 
						if (fd == -1)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (errno == EAGAIN || errno == EWOULDBLOCK)
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							if (errno == EINTR)
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (accept_error_is_transient (errno))
 | 
				
			||||||
 | 
								print_warning ("%s: %s", "accept", strerror (errno));
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								print_fatal ("%s: %s", "accept", strerror (errno));
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hard_assert (peer_len <= sizeof peer);
 | 
				
			||||||
 | 
						set_blocking (fd, false);
 | 
				
			||||||
 | 
						set_cloexec (fd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We already buffer our output, so reduce latencies.
 | 
				
			||||||
 | 
						int yes = 1;
 | 
				
			||||||
 | 
						soft_assert (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY,
 | 
				
			||||||
 | 
							&yes, sizeof yes) != -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct client *c = client_new ();
 | 
				
			||||||
 | 
						c->ctx = ctx;
 | 
				
			||||||
 | 
						c->socket_fd = fd;
 | 
				
			||||||
 | 
						LIST_PREPEND (ctx->clients, c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c->socket_event = poller_fd_make (&c->ctx->poller, c->socket_fd);
 | 
				
			||||||
 | 
						c->socket_event.dispatcher = (poller_fd_fn) on_client_ready;
 | 
				
			||||||
 | 
						c->socket_event.user_data = c;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client_update_poller (c, NULL);
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_relay_client_available (const struct pollfd *pfd, void *user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct app_context *ctx = user_data;
 | 
				
			||||||
 | 
						while (relay_try_fetch_client (ctx, pfd->fd))
 | 
				
			||||||
 | 
							;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int
 | 
				
			||||||
 | 
					relay_listen (struct addrinfo *ai, struct error **e)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int fd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
 | 
				
			||||||
 | 
						if (fd == -1)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							error_set (e, "socket: %s", strerror (errno));
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set_cloexec (fd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int yes = 1;
 | 
				
			||||||
 | 
						soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
 | 
				
			||||||
 | 
							&yes, sizeof yes) != -1);
 | 
				
			||||||
 | 
						soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
 | 
				
			||||||
 | 
							&yes, sizeof yes) != -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bind (fd, ai->ai_addr, ai->ai_addrlen))
 | 
				
			||||||
 | 
							error_set (e, "bind: %s", strerror (errno));
 | 
				
			||||||
 | 
						else if (listen (fd, 16 /* arbitrary number */))
 | 
				
			||||||
 | 
							error_set (e, "listen: %s", strerror (errno));
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							return fd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						xclose (fd);
 | 
				
			||||||
 | 
						return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int
 | 
				
			||||||
 | 
					relay_listen_with_context (struct addrinfo *ai, struct error **e)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						char *address = gai_reconstruct_address (ai);
 | 
				
			||||||
 | 
						print_debug ("binding to `%s'", address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct error *error = NULL;
 | 
				
			||||||
 | 
						int fd = relay_listen (ai, &error);
 | 
				
			||||||
 | 
						if (fd == -1)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							error_set (e, "binding to `%s' failed: %s", address, error->message);
 | 
				
			||||||
 | 
							error_free (error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						free (address);
 | 
				
			||||||
 | 
						return fd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					relay_start (struct app_context *ctx, char *address, struct error **e)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const char *port = NULL, *host = tokenize_host_port (address, &port);
 | 
				
			||||||
 | 
						if (!port || !*port)
 | 
				
			||||||
 | 
							return error_set (e, "missing port");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct addrinfo hints = {}, *result = NULL;
 | 
				
			||||||
 | 
						hints.ai_socktype = SOCK_STREAM;
 | 
				
			||||||
 | 
						hints.ai_flags = AI_PASSIVE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int err = getaddrinfo (*host ? host : NULL, port, &hints, &result);
 | 
				
			||||||
 | 
						if (err)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return error_set (e, "failed to resolve `%s', port `%s': %s: %s",
 | 
				
			||||||
 | 
								host, port, "getaddrinfo", gai_strerror (err));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Just try the first one, disregarding IPv4/IPv6 ordering.
 | 
				
			||||||
 | 
						int fd = relay_listen_with_context (result, e);
 | 
				
			||||||
 | 
						freeaddrinfo (result);
 | 
				
			||||||
 | 
						if (fd == -1)
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set_blocking (fd, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct poller_fd *event = &ctx->relay_event;
 | 
				
			||||||
 | 
						*event = poller_fd_make (&ctx->poller, fd);
 | 
				
			||||||
 | 
						event->dispatcher = (poller_fd_fn) on_relay_client_available;
 | 
				
			||||||
 | 
						event->user_data = ctx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx->relay_fd = fd;
 | 
				
			||||||
 | 
						poller_fd_set (event, POLLIN);
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_config_relay_bind_change (struct config_item *item)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct app_context *ctx = item->user_data;
 | 
				
			||||||
 | 
						char *value = item->value.string.str;
 | 
				
			||||||
 | 
						app_context_relay_stop (ctx);
 | 
				
			||||||
 | 
						if (!value)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct error *e = NULL;
 | 
				
			||||||
 | 
						char *address = xstrdup (value);
 | 
				
			||||||
 | 
						if (!relay_start (ctx, address, &e))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// TODO: Try to make sure this finds its way to the global buffer.
 | 
				
			||||||
 | 
							print_error ("%s: %s", item->schema->name, e->message);
 | 
				
			||||||
 | 
							error_free (e);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						free (address);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- Relay output ------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_send (struct client *c)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = &c->ctx->relay_message;
 | 
				
			||||||
 | 
						m->event_seq = c->event_seq++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Also don't try sending anything if half-closed.
 | 
				
			||||||
 | 
						if (!c->initialized || c->socket_fd == -1)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// liberty has msg_{reader,writer} already, but they use 8-byte lengths.
 | 
				
			||||||
 | 
						size_t frame_len_pos = c->write_buffer.len, frame_len = 0;
 | 
				
			||||||
 | 
						str_pack_u32 (&c->write_buffer, 0);
 | 
				
			||||||
 | 
						if (!relay_event_message_serialize (m, &c->write_buffer)
 | 
				
			||||||
 | 
						 || (frame_len = c->write_buffer.len - frame_len_pos - 4) > UINT32_MAX)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							print_error ("serialization failed, killing client");
 | 
				
			||||||
 | 
							client_kill (c);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t len = htonl (frame_len);
 | 
				
			||||||
 | 
						memcpy (c->write_buffer.str + frame_len_pos, &len, sizeof len);
 | 
				
			||||||
 | 
						client_update_poller (c, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_broadcast (struct app_context *ctx)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						LIST_FOR_EACH (struct client, c, ctx->clients)
 | 
				
			||||||
 | 
							relay_send (c);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct relay_event_message *
 | 
				
			||||||
 | 
					relay_prepare (struct app_context *ctx)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = &ctx->relay_message;
 | 
				
			||||||
 | 
						relay_event_message_free (m);
 | 
				
			||||||
 | 
						memset (m, 0, sizeof *m);
 | 
				
			||||||
 | 
						return m;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_ping (struct app_context *ctx)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						relay_prepare (ctx)->data.event = RELAY_EVENT_PING;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_buffer_update *e = &m->data.buffer_update;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_BUFFER_UPDATE;
 | 
				
			||||||
 | 
						e->buffer_name = str_from_cstr (buffer->name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_buffer_rename (struct app_context *ctx, struct buffer *buffer,
 | 
				
			||||||
 | 
						const char *new_name)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_buffer_rename *e = &m->data.buffer_rename;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_BUFFER_RENAME;
 | 
				
			||||||
 | 
						e->buffer_name = str_from_cstr (buffer->name);
 | 
				
			||||||
 | 
						e->new = str_from_cstr (new_name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_buffer_remove (struct app_context *ctx, struct buffer *buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_buffer_remove *e = &m->data.buffer_remove;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_BUFFER_REMOVE;
 | 
				
			||||||
 | 
						e->buffer_name = str_from_cstr (buffer->name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_buffer_activate *e = &m->data.buffer_activate;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_BUFFER_ACTIVATE;
 | 
				
			||||||
 | 
						e->buffer_name = str_from_cstr (buffer->name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
 | 
				
			||||||
 | 
						struct buffer_line *line)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_buffer_line *e = &m->data.buffer_line;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_BUFFER_LINE;
 | 
				
			||||||
 | 
						e->buffer_name = str_from_cstr (buffer->name);
 | 
				
			||||||
 | 
						e->is_unimportant = !!(line->flags & BUFFER_LINE_UNIMPORTANT);
 | 
				
			||||||
 | 
						e->is_highlight = !!(line->flags & BUFFER_LINE_HIGHLIGHT);
 | 
				
			||||||
 | 
						e->rendition = 1 + line->r;
 | 
				
			||||||
 | 
						e->when = line->when;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size_t len = 0;
 | 
				
			||||||
 | 
						for (size_t i = 0; line->items[i].type; i++)
 | 
				
			||||||
 | 
							len++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// XXX: This way helps xP's JSON conversion, but is super annoying for us.
 | 
				
			||||||
 | 
						union relay_item_data *p = e->items = xcalloc (len * 6, sizeof *e->items);
 | 
				
			||||||
 | 
						for (struct formatter_item *i = line->items; len--; i++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							switch (i->type)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
							case FORMATTER_ITEM_TEXT:
 | 
				
			||||||
 | 
								p->text.text = str_from_cstr (i->text);
 | 
				
			||||||
 | 
								(p++)->kind = RELAY_ITEM_TEXT;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case FORMATTER_ITEM_ATTR:
 | 
				
			||||||
 | 
								// For future consideration.
 | 
				
			||||||
 | 
								(p++)->kind = RELAY_ITEM_RESET;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case FORMATTER_ITEM_FG_COLOR:
 | 
				
			||||||
 | 
								p->fg_color.color = i->color;
 | 
				
			||||||
 | 
								(p++)->kind = RELAY_ITEM_FG_COLOR;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case FORMATTER_ITEM_BG_COLOR:
 | 
				
			||||||
 | 
								p->bg_color.color = i->color;
 | 
				
			||||||
 | 
								(p++)->kind = RELAY_ITEM_BG_COLOR;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case FORMATTER_ITEM_SIMPLE:
 | 
				
			||||||
 | 
								if (i->attribute & TEXT_BOLD)
 | 
				
			||||||
 | 
									(p++)->kind = RELAY_ITEM_FLIP_BOLD;
 | 
				
			||||||
 | 
								if (i->attribute & TEXT_ITALIC)
 | 
				
			||||||
 | 
									(p++)->kind = RELAY_ITEM_FLIP_ITALIC;
 | 
				
			||||||
 | 
								if (i->attribute & TEXT_UNDERLINE)
 | 
				
			||||||
 | 
									(p++)->kind = RELAY_ITEM_FLIP_UNDERLINE;
 | 
				
			||||||
 | 
								if (i->attribute & TEXT_INVERSE)
 | 
				
			||||||
 | 
									(p++)->kind = RELAY_ITEM_FLIP_INVERSE;
 | 
				
			||||||
 | 
								if (i->attribute & TEXT_CROSSED_OUT)
 | 
				
			||||||
 | 
									(p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT;
 | 
				
			||||||
 | 
								if (i->attribute & TEXT_MONOSPACE)
 | 
				
			||||||
 | 
									(p++)->kind = RELAY_ITEM_FLIP_MONOSPACE;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e->items_len = p - e->items;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_buffer_clear (struct app_context *ctx,
 | 
				
			||||||
 | 
						struct buffer *buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_buffer_clear *e = &m->data.buffer_clear;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_BUFFER_CLEAR;
 | 
				
			||||||
 | 
						e->buffer_name = str_from_cstr (buffer->name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					relay_prepare_error (struct app_context *ctx, uint32_t seq, const char *message)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_error *e = &m->data.error;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_ERROR;
 | 
				
			||||||
 | 
						e->command_seq = seq;
 | 
				
			||||||
 | 
						e->error = str_from_cstr (message);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Terminal output ---------------------------------------------------------
 | 
					// --- Terminal output ---------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Default colour pair
 | 
					/// Default colour pair
 | 
				
			||||||
@@ -4089,6 +4582,9 @@ log_formatter (struct app_context *ctx, struct buffer *buffer,
 | 
				
			|||||||
	if (buffer->log_file)
 | 
						if (buffer->log_file)
 | 
				
			||||||
		buffer_line_write_to_log (ctx, line, buffer->log_file);
 | 
							buffer_line_write_to_log (ctx, line, buffer->log_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relay_prepare_buffer_line (ctx, buffer, line);
 | 
				
			||||||
 | 
						relay_broadcast (ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool unseen_pm = buffer->type == BUFFER_PM
 | 
						bool unseen_pm = buffer->type == BUFFER_PM
 | 
				
			||||||
		&& buffer != ctx->current_buffer
 | 
							&& buffer != ctx->current_buffer
 | 
				
			||||||
		&& !(flags & BUFFER_LINE_UNIMPORTANT);
 | 
							&& !(flags & BUFFER_LINE_UNIMPORTANT);
 | 
				
			||||||
@@ -4302,6 +4798,9 @@ buffer_add (struct app_context *ctx, struct buffer *buffer)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	buffer_open_log_file (ctx, buffer);
 | 
						buffer_open_log_file (ctx, buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relay_prepare_buffer_update (ctx, buffer);
 | 
				
			||||||
 | 
						relay_broadcast (ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Normally this doesn't cause changes in the prompt but a prompt hook
 | 
						// Normally this doesn't cause changes in the prompt but a prompt hook
 | 
				
			||||||
	// could decide to show some information for all buffers nonetheless
 | 
						// could decide to show some information for all buffers nonetheless
 | 
				
			||||||
	refresh_prompt (ctx);
 | 
						refresh_prompt (ctx);
 | 
				
			||||||
@@ -4328,6 +4827,9 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
 | 
				
			|||||||
	if (buffer->type == BUFFER_SERVER)
 | 
						if (buffer->type == BUFFER_SERVER)
 | 
				
			||||||
		buffer->server->buffer = NULL;
 | 
							buffer->server->buffer = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relay_prepare_buffer_remove (ctx, buffer);
 | 
				
			||||||
 | 
						relay_broadcast (ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
 | 
						str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
 | 
				
			||||||
	LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
 | 
						LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
 | 
				
			||||||
	buffer_unref (buffer);
 | 
						buffer_unref (buffer);
 | 
				
			||||||
@@ -4457,6 +4959,9 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer)
 | 
				
			|||||||
	ctx->last_buffer = ctx->current_buffer;
 | 
						ctx->last_buffer = ctx->current_buffer;
 | 
				
			||||||
	ctx->current_buffer = buffer;
 | 
						ctx->current_buffer = buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relay_prepare_buffer_activate (ctx, buffer);
 | 
				
			||||||
 | 
						relay_broadcast (ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	refresh_prompt (ctx);
 | 
						refresh_prompt (ctx);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -4491,12 +4996,19 @@ buffer_merge (struct app_context *ctx,
 | 
				
			|||||||
	merged->lines_tail = start->prev;
 | 
						merged->lines_tail = start->prev;
 | 
				
			||||||
	merged->lines_count -= n;
 | 
						merged->lines_count -= n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// And append them to current lines in the buffer
 | 
						// Append them to current lines in the buffer
 | 
				
			||||||
	buffer->lines_tail->next = start;
 | 
						buffer->lines_tail->next = start;
 | 
				
			||||||
	start->prev = buffer->lines_tail;
 | 
						start->prev = buffer->lines_tail;
 | 
				
			||||||
	buffer->lines_tail = tail;
 | 
						buffer->lines_tail = tail;
 | 
				
			||||||
	buffer->lines_count += n;
 | 
						buffer->lines_count += n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// And since there is no log_*() call, send them to relays manually
 | 
				
			||||||
 | 
						LIST_FOR_EACH (struct buffer_line, line, start)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							relay_prepare_buffer_line (ctx, buffer, line);
 | 
				
			||||||
 | 
							relay_broadcast (ctx);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log_full (ctx, NULL, buffer, BUFFER_LINE_SKIP_FILE, BUFFER_LINE_STATUS,
 | 
						log_full (ctx, NULL, buffer, BUFFER_LINE_SKIP_FILE, BUFFER_LINE_STATUS,
 | 
				
			||||||
		"End of merged content");
 | 
							"End of merged content");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -4511,6 +5023,9 @@ buffer_rename (struct app_context *ctx,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	hard_assert (!collision);
 | 
						hard_assert (!collision);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relay_prepare_buffer_rename (ctx, buffer, new_name);
 | 
				
			||||||
 | 
						relay_broadcast (ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
 | 
						str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
 | 
				
			||||||
	str_map_set (&ctx->buffers_by_name, new_name, buffer);
 | 
						str_map_set (&ctx->buffers_by_name, new_name, buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -4524,13 +5039,16 @@ buffer_rename (struct app_context *ctx,
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
buffer_clear (struct buffer *buffer)
 | 
					buffer_clear (struct app_context *ctx, struct buffer *buffer)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	LIST_FOR_EACH (struct buffer_line, iter, buffer->lines)
 | 
						LIST_FOR_EACH (struct buffer_line, iter, buffer->lines)
 | 
				
			||||||
		buffer_line_destroy (iter);
 | 
							buffer_line_destroy (iter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	buffer->lines = buffer->lines_tail = NULL;
 | 
						buffer->lines = buffer->lines_tail = NULL;
 | 
				
			||||||
	buffer->lines_count = 0;
 | 
						buffer->lines_count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relay_prepare_buffer_clear (ctx, buffer);
 | 
				
			||||||
 | 
						relay_broadcast (ctx);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct buffer *
 | 
					static struct buffer *
 | 
				
			||||||
@@ -5947,29 +6465,6 @@ irc_finish_connection (struct server *s, int socket, const char *hostname)
 | 
				
			|||||||
	refresh_prompt (s->ctx);
 | 
						refresh_prompt (s->ctx);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Unwrap IPv6 addresses in format_host_port_pair() format
 | 
					 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
irc_split_host_port (char *s, char **host, char **port)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	*host = s;
 | 
					 | 
				
			||||||
	*port = "6667";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	char *right_bracket = strchr (s, ']');
 | 
					 | 
				
			||||||
	if (s[0] == '[' && right_bracket)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		*right_bracket = '\0';
 | 
					 | 
				
			||||||
		*host = s + 1;
 | 
					 | 
				
			||||||
		s = right_bracket + 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	char *colon = strchr (s, ':');
 | 
					 | 
				
			||||||
	if (colon)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		*colon = '\0';
 | 
					 | 
				
			||||||
		*port = colon + 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
@@ -6019,8 +6514,8 @@ irc_setup_connector (struct server *s, const struct strv *addresses)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for (size_t i = 0; i < addresses->len; i++)
 | 
						for (size_t i = 0; i < addresses->len; i++)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		char *host, *port;
 | 
							const char *port = "6667",
 | 
				
			||||||
		irc_split_host_port (addresses->vector[i], &host, &port);
 | 
								*host = tokenize_host_port (addresses->vector[i], &port);
 | 
				
			||||||
		connector_add_target (connector, host, port);
 | 
							connector_add_target (connector, host, port);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -6062,9 +6557,8 @@ irc_setup_connector_socks (struct server *s, const struct strv *addresses,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for (size_t i = 0; i < addresses->len; i++)
 | 
						for (size_t i = 0; i < addresses->len; i++)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		char *host, *port;
 | 
							const char *port = "6667",
 | 
				
			||||||
		irc_split_host_port (addresses->vector[i], &host, &port);
 | 
								*host = tokenize_host_port (addresses->vector[i], &port);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!socks_connector_add_target (connector, host, port, e))
 | 
							if (!socks_connector_add_target (connector, host, port, e))
 | 
				
			||||||
			return false;
 | 
								return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -7644,7 +8138,7 @@ irc_on_registered (struct server *s, const char *nickname)
 | 
				
			|||||||
	if (command)
 | 
						if (command)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		log_server_debug (s, "Executing \"#s\"", command);
 | 
							log_server_debug (s, "Executing \"#s\"", command);
 | 
				
			||||||
		process_input_utf8 (s->ctx, s->buffer, command, 0);
 | 
							(void) process_input_utf8 (s->ctx, s->buffer, command, 0);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int64_t command_delay = get_config_integer (s->config, "command_delay");
 | 
						int64_t command_delay = get_config_integer (s->config, "command_delay");
 | 
				
			||||||
@@ -8229,6 +8723,24 @@ irc_handle_rpl_isupport (struct server *s, const struct irc_message *msg)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					irc_adjust_motd (char **motd)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Heuristic, force MOTD to be monospace in graphical frontends.
 | 
				
			||||||
 | 
						if (!strchr (*motd, '\x11'))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							struct str s = str_make ();
 | 
				
			||||||
 | 
							str_append_c (&s, '\x11');
 | 
				
			||||||
 | 
							for (const char *p = *motd; *p; p++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								str_append_c (&s, *p);
 | 
				
			||||||
 | 
								if (*p == '\x0f')
 | 
				
			||||||
 | 
									str_append_c (&s, '\x11');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cstr_set (motd, str_steal (&s));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
irc_process_numeric (struct server *s,
 | 
					irc_process_numeric (struct server *s,
 | 
				
			||||||
	const struct irc_message *msg, unsigned long numeric)
 | 
						const struct irc_message *msg, unsigned long numeric)
 | 
				
			||||||
@@ -8251,6 +8763,10 @@ irc_process_numeric (struct server *s,
 | 
				
			|||||||
		if (msg->params.len == 2)
 | 
							if (msg->params.len == 2)
 | 
				
			||||||
			irc_try_parse_welcome_for_userhost (s, msg->params.vector[1]);
 | 
								irc_try_parse_welcome_for_userhost (s, msg->params.vector[1]);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
						case IRC_RPL_MOTD:
 | 
				
			||||||
 | 
							if (copy.len)
 | 
				
			||||||
 | 
								irc_adjust_motd (©.vector[0]);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case IRC_RPL_ISUPPORT:
 | 
						case IRC_RPL_ISUPPORT:
 | 
				
			||||||
		irc_handle_rpl_isupport      (s, msg);                break;
 | 
							irc_handle_rpl_isupport      (s, msg);                break;
 | 
				
			||||||
@@ -9248,7 +9764,7 @@ lua_buffer_execute (lua_State *L)
 | 
				
			|||||||
	struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
 | 
						struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
 | 
				
			||||||
	struct buffer *buffer = wrapper->object;
 | 
						struct buffer *buffer = wrapper->object;
 | 
				
			||||||
	const char *line = lua_plugin_check_utf8 (L, 2);
 | 
						const char *line = lua_plugin_check_utf8 (L, 2);
 | 
				
			||||||
	process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0);
 | 
						(void) process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11304,7 +11820,7 @@ handle_command_buffer (struct handler_args *a)
 | 
				
			|||||||
		show_buffers_list (ctx);
 | 
							show_buffers_list (ctx);
 | 
				
			||||||
	else if (!strcasecmp_ascii (action, "clear"))
 | 
						else if (!strcasecmp_ascii (action, "clear"))
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		buffer_clear (a->buffer);
 | 
							buffer_clear (ctx, a->buffer);
 | 
				
			||||||
		if (a->buffer == ctx->current_buffer)
 | 
							if (a->buffer == ctx->current_buffer)
 | 
				
			||||||
			buffer_print_backlog (ctx, a->buffer);
 | 
								buffer_print_backlog (ctx, a->buffer);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -12926,8 +13442,8 @@ complete_set_value_array (struct config_item *item, const char *word,
 | 
				
			|||||||
	cstr_split (item->value.string.str, ",", false, &items);
 | 
						cstr_split (item->value.string.str, ",", false, &items);
 | 
				
			||||||
	for (size_t i = 0; i < items.len; i++)
 | 
						for (size_t i = 0; i < items.len; i++)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		struct str wrapped = str_make (), serialized = str_make ();
 | 
							struct str wrapped = str_from_cstr (items.vector[i]);
 | 
				
			||||||
		str_append (&wrapped, items.vector[i]);
 | 
							struct str serialized = str_make ();
 | 
				
			||||||
		config_item_write_string (&serialized, &wrapped);
 | 
							config_item_write_string (&serialized, &wrapped);
 | 
				
			||||||
		str_free (&wrapped);
 | 
							str_free (&wrapped);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13546,6 +14062,25 @@ on_display_backlog_nowrap (int count, int key, void *user_data)
 | 
				
			|||||||
	return display_backlog (user_data, FLUSH_OPT_NOWRAP);
 | 
						return display_backlog (user_data, FLUSH_OPT_NOWRAP);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static FILE *
 | 
				
			||||||
 | 
					open_log_path (struct app_context *ctx, struct buffer *buffer, const char *path)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FILE *fp = fopen (path, "rb");
 | 
				
			||||||
 | 
						if (!fp)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							log_global_error (ctx,
 | 
				
			||||||
 | 
								"Failed to open `#l': #l", path, strerror (errno));
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (buffer->log_file)
 | 
				
			||||||
 | 
							// The regular flush will log any error eventually
 | 
				
			||||||
 | 
							(void) fflush (buffer->log_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set_cloexec (fileno (fp));
 | 
				
			||||||
 | 
						return fp;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool
 | 
					static bool
 | 
				
			||||||
on_display_full_log (int count, int key, void *user_data)
 | 
					on_display_full_log (int count, int key, void *user_data)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -13555,20 +14090,13 @@ on_display_full_log (int count, int key, void *user_data)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	struct buffer *buffer = ctx->current_buffer;
 | 
						struct buffer *buffer = ctx->current_buffer;
 | 
				
			||||||
	char *path = buffer_get_log_path (buffer);
 | 
						char *path = buffer_get_log_path (buffer);
 | 
				
			||||||
	FILE *full_log = fopen (path, "rb");
 | 
						FILE *full_log = open_log_path (ctx, buffer, path);
 | 
				
			||||||
	if (!full_log)
 | 
						if (!full_log)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		log_global_error (ctx, "Failed to open log file for #s: #l",
 | 
					 | 
				
			||||||
			ctx->current_buffer->name, strerror (errno));
 | 
					 | 
				
			||||||
		free (path);
 | 
							free (path);
 | 
				
			||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (buffer->log_file)
 | 
					 | 
				
			||||||
		// The regular flush will log any error eventually
 | 
					 | 
				
			||||||
		(void) fflush (buffer->log_file);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	set_cloexec (fileno (full_log));
 | 
					 | 
				
			||||||
	launch_pager (ctx, fileno (full_log), buffer->name, path);
 | 
						launch_pager (ctx, fileno (full_log), buffer->name, path);
 | 
				
			||||||
	fclose (full_log);
 | 
						fclose (full_log);
 | 
				
			||||||
	free (path);
 | 
						free (path);
 | 
				
			||||||
@@ -14601,6 +15129,177 @@ init_poller_events (struct app_context *ctx)
 | 
				
			|||||||
	ctx->input_event.user_data = ctx;
 | 
						ctx->input_event.user_data = ctx;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- Relay processing --------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// XXX: This could be below completion code if reset_autoaway() was higher up.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					client_resync (struct client *c)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						LIST_FOR_EACH (struct buffer, buffer, c->ctx->buffers)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							relay_prepare_buffer_update (c->ctx, buffer);
 | 
				
			||||||
 | 
							relay_send (c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							LIST_FOR_EACH (struct buffer_line, line, buffer->lines)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								relay_prepare_buffer_line (c->ctx, buffer, line);
 | 
				
			||||||
 | 
								relay_send (c);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relay_prepare_buffer_activate (c->ctx, c->ctx->current_buffer);
 | 
				
			||||||
 | 
						relay_send (c);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *
 | 
				
			||||||
 | 
					client_message_buffer_name (const struct relay_command_message *m)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						switch (m->data.command)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_COMPLETE:
 | 
				
			||||||
 | 
							return m->data.buffer_input.buffer_name.str;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_INPUT:
 | 
				
			||||||
 | 
							return m->data.buffer_input.buffer_name.str;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_ACTIVATE:
 | 
				
			||||||
 | 
							return m->data.buffer_activate.buffer_name.str;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_LOG:
 | 
				
			||||||
 | 
							return m->data.buffer_log.buffer_name.str;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					client_process_buffer_log
 | 
				
			||||||
 | 
						(struct client *c, uint32_t seq, struct buffer *buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct relay_event_message *m = relay_prepare (c->ctx);
 | 
				
			||||||
 | 
						struct relay_event_data_response *e = &m->data.response;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_RESPONSE;
 | 
				
			||||||
 | 
						e->command_seq = seq;
 | 
				
			||||||
 | 
						e->data.command = RELAY_COMMAND_BUFFER_LOG;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						char *path = buffer_get_log_path (buffer);
 | 
				
			||||||
 | 
						FILE *fp = open_log_path (c->ctx, buffer, path);
 | 
				
			||||||
 | 
						if (fp)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							struct str log = str_make ();
 | 
				
			||||||
 | 
							char buf[BUFSIZ];
 | 
				
			||||||
 | 
							size_t len;
 | 
				
			||||||
 | 
							while ((len = fread (buf, 1, sizeof buf, fp)))
 | 
				
			||||||
 | 
								str_append_data (&log, buf, len);
 | 
				
			||||||
 | 
							if (ferror (fp))
 | 
				
			||||||
 | 
								log_global_error (c->ctx, "Failed to read `#l': #l",
 | 
				
			||||||
 | 
									path, strerror (errno));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// On overflow, it will later fail serialization.
 | 
				
			||||||
 | 
							e->data.buffer_log.log_len = MIN (UINT32_MAX, log.len);
 | 
				
			||||||
 | 
							e->data.buffer_log.log = (uint8_t *) str_steal (&log);
 | 
				
			||||||
 | 
							fclose (fp);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// XXX: We log failures to the global buffer,
 | 
				
			||||||
 | 
						//   so the client just receives nothing if there is no log file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						free (path);
 | 
				
			||||||
 | 
						relay_send (c);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					client_process_message (struct client *c,
 | 
				
			||||||
 | 
						struct msg_unpacker *r, struct relay_command_message *m)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!relay_command_message_deserialize (m, r)
 | 
				
			||||||
 | 
						 || msg_unpacker_get_available (r))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							print_error ("deserialization failed, killing client");
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const char *buffer_name = client_message_buffer_name (m);
 | 
				
			||||||
 | 
						struct buffer *buffer = NULL;
 | 
				
			||||||
 | 
						if (buffer_name && !(buffer = buffer_by_name (c->ctx, buffer_name)))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							relay_prepare_error (c->ctx, m->command_seq, "Unknown buffer");
 | 
				
			||||||
 | 
							relay_send (c);
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (m->data.command)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						case RELAY_COMMAND_HELLO:
 | 
				
			||||||
 | 
							if (m->data.hello.version != RELAY_VERSION)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// TODO: This should send back an error message and shut down.
 | 
				
			||||||
 | 
								print_error ("protocol version mismatch, killing client");
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c->initialized = true;
 | 
				
			||||||
 | 
							client_resync (c);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_PING:
 | 
				
			||||||
 | 
							relay_prepare_ping (c->ctx);
 | 
				
			||||||
 | 
							relay_send (c);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_ACTIVE:
 | 
				
			||||||
 | 
							reset_autoaway (c->ctx);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_COMPLETE:
 | 
				
			||||||
 | 
							// TODO: Run the completion machinery.
 | 
				
			||||||
 | 
							relay_prepare_error (c->ctx, m->command_seq, "Not implemented");
 | 
				
			||||||
 | 
							relay_send (c);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_INPUT:
 | 
				
			||||||
 | 
							(void) process_input_utf8 (c->ctx,
 | 
				
			||||||
 | 
								buffer, m->data.buffer_input.text.str, 0);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_ACTIVATE:
 | 
				
			||||||
 | 
							buffer_activate (c->ctx, buffer);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case RELAY_COMMAND_BUFFER_LOG:
 | 
				
			||||||
 | 
							client_process_buffer_log (c, m->command_seq, buffer);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							print_warning ("unhandled client command");
 | 
				
			||||||
 | 
							relay_prepare_error (c->ctx, m->command_seq, "Unknown command");
 | 
				
			||||||
 | 
							relay_send (c);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					client_process_buffer (struct client *c)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct str *buf = &c->read_buffer;
 | 
				
			||||||
 | 
						size_t offset = 0;
 | 
				
			||||||
 | 
						while (true)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							uint32_t frame_len = 0;
 | 
				
			||||||
 | 
							struct msg_unpacker r =
 | 
				
			||||||
 | 
								msg_unpacker_make (buf->str + offset, buf->len - offset);
 | 
				
			||||||
 | 
							if (!msg_unpacker_u32 (&r, &frame_len))
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r.len = MIN (r.len, sizeof frame_len + frame_len);
 | 
				
			||||||
 | 
							if (msg_unpacker_get_available (&r) < frame_len)
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct relay_command_message m = {};
 | 
				
			||||||
 | 
							bool ok = client_process_message (c, &r, &m);
 | 
				
			||||||
 | 
							relay_command_message_free (&m);
 | 
				
			||||||
 | 
							if (!ok)
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset += r.offset;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						str_remove_slice (buf, 0, offset);
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Tests -------------------------------------------------------------------
 | 
					// --- Tests -------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The application is quite monolithic and can only be partially unit-tested.
 | 
					// The application is quite monolithic and can only be partially unit-tested.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								xD.c
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								xD.c
									
									
									
									
									
								
							@@ -853,8 +853,6 @@ client_send_str (struct client *c, const struct str *s)
 | 
				
			|||||||
	str_append_data (&c->write_buffer, s->str,
 | 
						str_append_data (&c->write_buffer, s->str,
 | 
				
			||||||
		MIN (s->len, IRC_MAX_MESSAGE_LENGTH));
 | 
							MIN (s->len, IRC_MAX_MESSAGE_LENGTH));
 | 
				
			||||||
	str_append (&c->write_buffer, "\r\n");
 | 
						str_append (&c->write_buffer, "\r\n");
 | 
				
			||||||
	// XXX: we might want to move this elsewhere, so that it doesn't get called
 | 
					 | 
				
			||||||
	//   as often; it's going to cause a lot of syscalls with epoll.
 | 
					 | 
				
			||||||
	client_update_poller (c, NULL);
 | 
						client_update_poller (c, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Technically we haven't sent it yet but that's a minor detail
 | 
						// Technically we haven't sent it yet but that's a minor detail
 | 
				
			||||||
@@ -3095,6 +3093,7 @@ irc_try_read (struct client *c)
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			buf->str[buf->len += n_read] = '\0';
 | 
								buf->str[buf->len += n_read] = '\0';
 | 
				
			||||||
			// TODO: discard characters above the 512 character limit
 | 
								// TODO: discard characters above the 512 character limit
 | 
				
			||||||
 | 
								// FIXME: we should probably discard the data if closing_link
 | 
				
			||||||
			irc_process_buffer (buf, irc_process_message, c);
 | 
								irc_process_buffer (buf, irc_process_message, c);
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -3136,6 +3135,7 @@ irc_try_read_tls (struct client *c)
 | 
				
			|||||||
		case SSL_ERROR_NONE:
 | 
							case SSL_ERROR_NONE:
 | 
				
			||||||
			buf->str[buf->len += n_read] = '\0';
 | 
								buf->str[buf->len += n_read] = '\0';
 | 
				
			||||||
			// TODO: discard characters above the 512 character limit
 | 
								// TODO: discard characters above the 512 character limit
 | 
				
			||||||
 | 
								// FIXME: we should probably discard the data if closing_link
 | 
				
			||||||
			irc_process_buffer (buf, irc_process_message, c);
 | 
								irc_process_buffer (buf, irc_process_message, c);
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		case SSL_ERROR_ZERO_RETURN:
 | 
							case SSL_ERROR_ZERO_RETURN:
 | 
				
			||||||
@@ -3421,16 +3421,10 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd)
 | 
				
			|||||||
		if (errno == EINTR)
 | 
							if (errno == EINTR)
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (errno == EBADF
 | 
							if (accept_error_is_transient (errno))
 | 
				
			||||||
		 || errno == EINVAL
 | 
					 | 
				
			||||||
		 || errno == ENOTSOCK
 | 
					 | 
				
			||||||
		 || errno == EOPNOTSUPP)
 | 
					 | 
				
			||||||
			print_fatal ("%s: %s", "accept", strerror (errno));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// OS kernels may return a wide range of unforeseeable errors.
 | 
					 | 
				
			||||||
		// Assuming that they're either transient or caused by
 | 
					 | 
				
			||||||
		// a connection that we've just extracted from the queue.
 | 
					 | 
				
			||||||
			print_warning ("%s: %s", "accept", strerror (errno));
 | 
								print_warning ("%s: %s", "accept", strerror (errno));
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								print_fatal ("%s: %s", "accept", strerror (errno));
 | 
				
			||||||
		return true;
 | 
							return true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -3814,10 +3808,9 @@ irc_lock_pid_file (struct server_context *ctx, struct error **e)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int
 | 
					static int
 | 
				
			||||||
irc_listen (struct addrinfo *gai_iter)
 | 
					irc_listen (struct addrinfo *ai)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int fd = socket (gai_iter->ai_family,
 | 
						int fd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
 | 
				
			||||||
		gai_iter->ai_socktype, gai_iter->ai_protocol);
 | 
					 | 
				
			||||||
	if (fd == -1)
 | 
						if (fd == -1)
 | 
				
			||||||
		return -1;
 | 
							return -1;
 | 
				
			||||||
	set_cloexec (fd);
 | 
						set_cloexec (fd);
 | 
				
			||||||
@@ -3831,21 +3824,13 @@ irc_listen (struct addrinfo *gai_iter)
 | 
				
			|||||||
#if defined SOL_IPV6 && defined IPV6_V6ONLY
 | 
					#if defined SOL_IPV6 && defined IPV6_V6ONLY
 | 
				
			||||||
	// Make NULL always bind to both IPv4 and IPv6, irrespectively of the order
 | 
						// Make NULL always bind to both IPv4 and IPv6, irrespectively of the order
 | 
				
			||||||
	// of results; only INADDR6_ANY seems to be affected by this
 | 
						// of results; only INADDR6_ANY seems to be affected by this
 | 
				
			||||||
	if (gai_iter->ai_family == AF_INET6)
 | 
						if (ai->ai_family == AF_INET6)
 | 
				
			||||||
		soft_assert (setsockopt (fd, SOL_IPV6, IPV6_V6ONLY,
 | 
							soft_assert (setsockopt (fd, SOL_IPV6, IPV6_V6ONLY,
 | 
				
			||||||
			&yes, sizeof yes) != -1);
 | 
								&yes, sizeof yes) != -1);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	char host[NI_MAXHOST], port[NI_MAXSERV];
 | 
						char *address = gai_reconstruct_address (ai);
 | 
				
			||||||
	host[0] = port[0] = '\0';
 | 
						if (bind (fd, ai->ai_addr, ai->ai_addrlen))
 | 
				
			||||||
	int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
 | 
					 | 
				
			||||||
		host, sizeof host, port, sizeof port,
 | 
					 | 
				
			||||||
		NI_NUMERICHOST | NI_NUMERICSERV);
 | 
					 | 
				
			||||||
	if (err)
 | 
					 | 
				
			||||||
		print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	char *address = format_host_port_pair (host, port);
 | 
					 | 
				
			||||||
	if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
 | 
					 | 
				
			||||||
		print_error ("bind to %s failed: %s", address, strerror (errno));
 | 
							print_error ("bind to %s failed: %s", address, strerror (errno));
 | 
				
			||||||
	else if (listen (fd, 16 /* arbitrary number */))
 | 
						else if (listen (fd, 16 /* arbitrary number */))
 | 
				
			||||||
		print_error ("listen on %s failed: %s", address, strerror (errno));
 | 
							print_error ("listen on %s failed: %s", address, strerror (errno));
 | 
				
			||||||
@@ -3865,12 +3850,12 @@ static void
 | 
				
			|||||||
irc_listen_resolve (struct server_context *ctx,
 | 
					irc_listen_resolve (struct server_context *ctx,
 | 
				
			||||||
	const char *host, const char *port, struct addrinfo *gai_hints)
 | 
						const char *host, const char *port, struct addrinfo *gai_hints)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct addrinfo *gai_result, *gai_iter;
 | 
						struct addrinfo *gai_result = NULL, *gai_iter = NULL;
 | 
				
			||||||
	int err = getaddrinfo (host, port, gai_hints, &gai_result);
 | 
						int err = getaddrinfo (host, port, gai_hints, &gai_result);
 | 
				
			||||||
	if (err)
 | 
						if (err)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		char *address = format_host_port_pair (host, port);
 | 
							char *address = format_host_port_pair (host, port);
 | 
				
			||||||
		print_error ("bind to %s failed: %s: %s",
 | 
							print_error ("binding to %s failed: %s: %s",
 | 
				
			||||||
			address, "getaddrinfo", gai_strerror (err));
 | 
								address, "getaddrinfo", gai_strerror (err));
 | 
				
			||||||
		free (address);
 | 
							free (address);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										172
									
								
								xF.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								xF.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * xF.c: a toothless IRC client frontend
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Permission to use, copy, modify, and/or distribute this software for any
 | 
				
			||||||
 | 
					 * purpose with or without fee is hereby granted.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
				
			||||||
 | 
					 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | 
				
			||||||
 | 
					 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 | 
				
			||||||
 | 
					 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
				
			||||||
 | 
					 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 | 
				
			||||||
 | 
					 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 | 
				
			||||||
 | 
					 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "config.h"
 | 
				
			||||||
 | 
					#define PROGRAM_NAME "xF"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common.c"
 | 
				
			||||||
 | 
					#include "xC-proto.c"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <X11/Xatom.h>
 | 
				
			||||||
 | 
					#include <X11/Xlib.h>
 | 
				
			||||||
 | 
					#include <X11/keysym.h>
 | 
				
			||||||
 | 
					#include <X11/XKBlib.h>
 | 
				
			||||||
 | 
					#include <X11/Xft/Xft.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						bool polling;
 | 
				
			||||||
 | 
						struct connector connector;
 | 
				
			||||||
 | 
						int socket;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					g;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_connector_connecting (void *user_data, const char *address)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) user_data;
 | 
				
			||||||
 | 
						print_status ("connecting to %s...", address);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_connector_error (void *user_data, const char *error)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) user_data;
 | 
				
			||||||
 | 
						print_status ("connection failed: %s", error);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_connector_failure (void *user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) user_data;
 | 
				
			||||||
 | 
						exit_fatal ("giving up");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_connector_connected (void *user_data, int socket, const char *hostname)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) user_data;
 | 
				
			||||||
 | 
						(void) hostname;
 | 
				
			||||||
 | 
						g.polling = false;
 | 
				
			||||||
 | 
						g.socket = socket;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					protocol_test (const char *host, const char *port)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct poller poller = {};
 | 
				
			||||||
 | 
						poller_init (&poller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						connector_init (&g.connector, &poller);
 | 
				
			||||||
 | 
						g.connector.on_connecting = on_connector_connecting;
 | 
				
			||||||
 | 
						g.connector.on_error      = on_connector_error;
 | 
				
			||||||
 | 
						g.connector.on_connected  = on_connector_connected;
 | 
				
			||||||
 | 
						g.connector.on_failure    = on_connector_failure;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						connector_add_target (&g.connector, host, port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						g.polling = true;
 | 
				
			||||||
 | 
						while (g.polling)
 | 
				
			||||||
 | 
							poller_run (&poller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						connector_free (&g.connector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct str s = str_make ();
 | 
				
			||||||
 | 
						str_pack_u32 (&s, 0);
 | 
				
			||||||
 | 
						struct relay_command_message m = {};
 | 
				
			||||||
 | 
						m.data.hello.command = RELAY_COMMAND_HELLO;
 | 
				
			||||||
 | 
						m.data.hello.version = RELAY_VERSION;
 | 
				
			||||||
 | 
						if (!relay_command_message_serialize (&m, &s))
 | 
				
			||||||
 | 
							exit_fatal ("serialization failed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t len = htonl (s.len - sizeof len);
 | 
				
			||||||
 | 
						memcpy (s.str, &len, sizeof len);
 | 
				
			||||||
 | 
						if (errno = 0, write (g.socket, s.str, s.len) != (ssize_t) s.len)
 | 
				
			||||||
 | 
							exit_fatal ("short send or error: %s", strerror (errno));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						char buf[1 << 20] = "";
 | 
				
			||||||
 | 
						while (errno = 0, read (g.socket, &len, sizeof len) == sizeof len)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							len = ntohl (len);
 | 
				
			||||||
 | 
							if (errno = 0, read (g.socket, buf, MIN (len, sizeof buf)) != len)
 | 
				
			||||||
 | 
								exit_fatal ("short read or error: %s", strerror (errno));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct msg_unpacker r = msg_unpacker_make (buf, len);
 | 
				
			||||||
 | 
							struct relay_event_message m = {};
 | 
				
			||||||
 | 
							if (!relay_event_message_deserialize (&m, &r))
 | 
				
			||||||
 | 
								exit_fatal ("deserialization failed");
 | 
				
			||||||
 | 
							if (msg_unpacker_get_available (&r))
 | 
				
			||||||
 | 
								exit_fatal ("trailing data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							printf ("event: %d\n", m.data.event);
 | 
				
			||||||
 | 
							relay_event_message_free (&m);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						exit_fatal ("short read or error: %s", strerror (errno));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int
 | 
				
			||||||
 | 
					main (int argc, char *argv[])
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						static const struct opt opts[] =
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							{ 'h', "help", NULL, 0, "display this help and exit" },
 | 
				
			||||||
 | 
							{ 'V', "version", NULL, 0, "output version information and exit" },
 | 
				
			||||||
 | 
							{ 0, NULL, NULL, 0, NULL }
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct opt_handler oh = opt_handler_make (argc, argv, opts,
 | 
				
			||||||
 | 
							"HOST:PORT", "X11 frontend for xC.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int c;
 | 
				
			||||||
 | 
						while ((c = opt_handler_get (&oh)) != -1)
 | 
				
			||||||
 | 
						switch (c)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						case 'h':
 | 
				
			||||||
 | 
							opt_handler_usage (&oh, stdout);
 | 
				
			||||||
 | 
							exit (EXIT_SUCCESS);
 | 
				
			||||||
 | 
						case 'V':
 | 
				
			||||||
 | 
							printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
 | 
				
			||||||
 | 
							exit (EXIT_SUCCESS);
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							print_error ("wrong options");
 | 
				
			||||||
 | 
							opt_handler_usage (&oh, stderr);
 | 
				
			||||||
 | 
							exit (EXIT_FAILURE);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						argc -= optind;
 | 
				
			||||||
 | 
						argv += optind;
 | 
				
			||||||
 | 
						if (argc != 1)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							opt_handler_usage (&oh, stderr);
 | 
				
			||||||
 | 
							exit (EXIT_FAILURE);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opt_handler_free (&oh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						char *address = xstrdup (argv[0]);
 | 
				
			||||||
 | 
						const char *port = NULL, *host = tokenize_host_port (address, &port);
 | 
				
			||||||
 | 
						if (!port)
 | 
				
			||||||
 | 
							exit_fatal ("missing port number/service name");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Actually implement an X11-based user interface.
 | 
				
			||||||
 | 
						protocol_test (host, port);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								xF.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								xF.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
				
			||||||
 | 
					<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
 | 
				
			||||||
 | 
					   xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
				
			||||||
 | 
					   xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					   xmlns:svg="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <defs>
 | 
				
			||||||
 | 
					    <linearGradient id="background" x1="0" y1="0" x2="1" y2="1">
 | 
				
			||||||
 | 
					      <stop stop-color="#808080" offset="0" />
 | 
				
			||||||
 | 
					      <stop stop-color="#000000" offset="1" />
 | 
				
			||||||
 | 
					    </linearGradient>
 | 
				
			||||||
 | 
					    <!-- librsvg screws up the filter's orientation in a weird way
 | 
				
			||||||
 | 
					         otherwise a larger blur value would look better -->
 | 
				
			||||||
 | 
					    <filter id="shadow" color-interpolation-filters="sRGB">
 | 
				
			||||||
 | 
					      <feOffset dy="0.5" />
 | 
				
			||||||
 | 
					      <feGaussianBlur stdDeviation="0.5" />
 | 
				
			||||||
 | 
					      <feComposite in2="SourceGraphic" operator="in" />
 | 
				
			||||||
 | 
					    </filter>
 | 
				
			||||||
 | 
					    <clipPath id="clip">
 | 
				
			||||||
 | 
					      <rect x="-7" y="-10" width="14" height="20" />
 | 
				
			||||||
 | 
					    </clipPath>
 | 
				
			||||||
 | 
					  </defs>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <circle cx="24" cy="24" r="20"
 | 
				
			||||||
 | 
					     fill="url(#background)" stroke="#404040" stroke-width="2" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <g transform="rotate(-45 24 24)" filter="url(#shadow)">
 | 
				
			||||||
 | 
					    <path d="m 12,25 h 24 v 11 h -5 v -8 h -4.5 v 6 h -5 v -6 h -9.5 z"
 | 
				
			||||||
 | 
					       fill="#ffffff" />
 | 
				
			||||||
 | 
					    <g stroke-width="4" transform="translate(24, 16)" clip-path="url(#clip)"
 | 
				
			||||||
 | 
					       stroke="#ffffff">
 | 
				
			||||||
 | 
					      <line x1="-8" x2="8" y1="-5" y2="5" />
 | 
				
			||||||
 | 
					      <line x1="-8" x2="8" y1="5" y2="-5" />
 | 
				
			||||||
 | 
					    </g>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.3 KiB  | 
							
								
								
									
										3
									
								
								xP/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								xP/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					/xP
 | 
				
			||||||
 | 
					/proto.go
 | 
				
			||||||
 | 
					/public/mithril.js
 | 
				
			||||||
							
								
								
									
										14
									
								
								xP/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								xP/Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					.POSIX:
 | 
				
			||||||
 | 
					.SUFFIXES:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					outputs = xP proto.go public/mithril.js
 | 
				
			||||||
 | 
					all: $(outputs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xP: xP.go proto.go
 | 
				
			||||||
 | 
						go build -o $@
 | 
				
			||||||
 | 
					proto.go: ../xC-gen-proto.awk ../xC-gen-proto-go.awk ../xC-proto
 | 
				
			||||||
 | 
						awk -f ../xC-gen-proto.awk -f ../xC-gen-proto-go.awk ../xC-proto > $@
 | 
				
			||||||
 | 
					public/mithril.js:
 | 
				
			||||||
 | 
						curl -Lo $@ https://unpkg.com/mithril/mithril.js
 | 
				
			||||||
 | 
					clean:
 | 
				
			||||||
 | 
						rm -f $(outputs)
 | 
				
			||||||
							
								
								
									
										5
									
								
								xP/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								xP/go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					module janouch.name/xK
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
 | 
				
			||||||
							
								
								
									
										2
									
								
								xP/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								xP/go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
				
			||||||
							
								
								
									
										109
									
								
								xP/public/xP.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								xP/public/xP.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					body {
 | 
				
			||||||
 | 
						margin: 0;
 | 
				
			||||||
 | 
						padding: 0;
 | 
				
			||||||
 | 
						font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.xP {
 | 
				
			||||||
 | 
						height: 100vh;
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-direction: column;
 | 
				
			||||||
 | 
						overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.title, .status {
 | 
				
			||||||
 | 
						background: #f8f8f8;
 | 
				
			||||||
 | 
						border-bottom: 1px solid #ccc;
 | 
				
			||||||
 | 
						padding: .05rem .3rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.middle {
 | 
				
			||||||
 | 
						flex: auto;
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-direction: row;
 | 
				
			||||||
 | 
						overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.list {
 | 
				
			||||||
 | 
						overflow-y: auto;
 | 
				
			||||||
 | 
						border-right: 1px solid #ccc;
 | 
				
			||||||
 | 
						min-width: 10rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.item {
 | 
				
			||||||
 | 
						padding: .05rem .3rem;
 | 
				
			||||||
 | 
						cursor: default;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.item.active {
 | 
				
			||||||
 | 
						font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Only Firefox currently supports align-content: safe end, thus this. */
 | 
				
			||||||
 | 
					.buffer-container {
 | 
				
			||||||
 | 
						flex: auto;
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-direction: column;
 | 
				
			||||||
 | 
						overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.filler {
 | 
				
			||||||
 | 
						flex: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.buffer {
 | 
				
			||||||
 | 
						display: grid;
 | 
				
			||||||
 | 
						grid-template-columns: max-content auto;
 | 
				
			||||||
 | 
						overflow-y: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.date {
 | 
				
			||||||
 | 
						padding: .3rem;
 | 
				
			||||||
 | 
						grid-column: span 2;
 | 
				
			||||||
 | 
						font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.time {
 | 
				
			||||||
 | 
						padding: .1rem .3rem;
 | 
				
			||||||
 | 
						background: #f8f8f8;
 | 
				
			||||||
 | 
						color: #bbb;
 | 
				
			||||||
 | 
						border-right: 1px solid #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.mark {
 | 
				
			||||||
 | 
						padding-right: .3rem;
 | 
				
			||||||
 | 
						text-align: center;
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						min-width: 2rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.mark.error {
 | 
				
			||||||
 | 
						color: red;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.mark.join {
 | 
				
			||||||
 | 
						color: green;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.mark.part {
 | 
				
			||||||
 | 
						color: red;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.content {
 | 
				
			||||||
 | 
						padding: .1rem .3rem;
 | 
				
			||||||
 | 
						white-space: pre-wrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.content span.b {
 | 
				
			||||||
 | 
						font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.content span.i {
 | 
				
			||||||
 | 
						font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.content span.u {
 | 
				
			||||||
 | 
						text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.content span.s {
 | 
				
			||||||
 | 
						text-decoration: line-through;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.content span.m {
 | 
				
			||||||
 | 
						font-family: monospace;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.status {
 | 
				
			||||||
 | 
						border-top: 2px solid #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					textarea {
 | 
				
			||||||
 | 
						padding: .05rem .3rem;
 | 
				
			||||||
 | 
						font-family: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										188
									
								
								xP/public/xP.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								xP/public/xP.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
				
			|||||||
 | 
					// TODO: Probably reset state on disconnect, and indicate to user.
 | 
				
			||||||
 | 
					let socket = new WebSocket(proxy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let commandSeq = 0
 | 
				
			||||||
 | 
					function send(command) {
 | 
				
			||||||
 | 
						socket.send(JSON.stringify({commandSeq: ++commandSeq, data: command}))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					socket.onopen = function(event) {
 | 
				
			||||||
 | 
						send({command: 'Hello', version: 1})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let buffers = new Map()
 | 
				
			||||||
 | 
					let bufferCurrent = undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					socket.onmessage = function(event) {
 | 
				
			||||||
 | 
						console.log(event.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let e = JSON.parse(event.data).data
 | 
				
			||||||
 | 
						switch (e.event) {
 | 
				
			||||||
 | 
						case 'BufferUpdate':
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							let b = buffers.get(e.bufferName)
 | 
				
			||||||
 | 
							if (b === undefined) {
 | 
				
			||||||
 | 
								b = {lines: []}
 | 
				
			||||||
 | 
								buffers.set(e.bufferName, b)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// TODO: Update any buffer properties.
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						case 'BufferRename':
 | 
				
			||||||
 | 
							buffers.set(e.new, buffers.get(e.bufferName))
 | 
				
			||||||
 | 
							buffers.delete(e.bufferName)
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						case 'BufferRemove':
 | 
				
			||||||
 | 
							buffers.delete(e.bufferName)
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						case 'BufferActivate':
 | 
				
			||||||
 | 
							bufferCurrent = e.bufferName
 | 
				
			||||||
 | 
							// TODO: Somehow scroll to the end of it immediately.
 | 
				
			||||||
 | 
							// TODO: Focus the textarea.
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						case 'BufferLine':
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							let b = buffers.get(e.bufferName)
 | 
				
			||||||
 | 
							if (b !== undefined)
 | 
				
			||||||
 | 
								b.lines.push({when: e.when, rendition: e.rendition, items: e.items})
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						case 'BufferClear':
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							let b = buffers.get(e.bufferName)
 | 
				
			||||||
 | 
							if (b !== undefined)
 | 
				
			||||||
 | 
								b.lines.length = 0
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m.redraw()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let BufferList = {
 | 
				
			||||||
 | 
						view: vnode => {
 | 
				
			||||||
 | 
							let items = []
 | 
				
			||||||
 | 
							buffers.forEach((b, name) => {
 | 
				
			||||||
 | 
								let attrs = {
 | 
				
			||||||
 | 
									onclick: e => {
 | 
				
			||||||
 | 
										send({command: 'BufferActivate', bufferName: name})
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (name == bufferCurrent)
 | 
				
			||||||
 | 
									attrs.class = 'active'
 | 
				
			||||||
 | 
								items.push(m('.item', attrs, name))
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return m('.list', {}, items)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let Content = {
 | 
				
			||||||
 | 
						view: vnode => {
 | 
				
			||||||
 | 
							let line = vnode.children[0]
 | 
				
			||||||
 | 
							let content = []
 | 
				
			||||||
 | 
							switch (line.rendition) {
 | 
				
			||||||
 | 
							case 'Indent': content.push(m('span.mark',       {}, ''));  break
 | 
				
			||||||
 | 
							case 'Status': content.push(m('span.mark',       {}, '–')); break
 | 
				
			||||||
 | 
							case 'Error':  content.push(m('span.mark.error', {}, '⚠')); break
 | 
				
			||||||
 | 
							case 'Join':   content.push(m('span.mark.join',  {}, '→')); break
 | 
				
			||||||
 | 
							case 'Part':   content.push(m('span.mark.part',  {}, '←')); break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let classes = new Set()
 | 
				
			||||||
 | 
							let flip = c => {
 | 
				
			||||||
 | 
								if (classes.has(c))
 | 
				
			||||||
 | 
									classes.delete(c)
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									classes.add(c)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							line.items.forEach(item => {
 | 
				
			||||||
 | 
								// TODO: Colours.
 | 
				
			||||||
 | 
								switch (item.kind) {
 | 
				
			||||||
 | 
								case 'Text':
 | 
				
			||||||
 | 
									// TODO: Detect and transform links.
 | 
				
			||||||
 | 
									content.push(m('span', {
 | 
				
			||||||
 | 
										class: Array.from(classes.keys()).join(' '),
 | 
				
			||||||
 | 
									}, item.text))
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								case 'Reset':
 | 
				
			||||||
 | 
									classes.clear()
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								case 'FlipBold':       flip('b'); break
 | 
				
			||||||
 | 
								case 'FlipItalic':     flip('i'); break
 | 
				
			||||||
 | 
								case 'FlipUnderline':  flip('u'); break
 | 
				
			||||||
 | 
								case 'FlipInverse':    flip('i'); break
 | 
				
			||||||
 | 
								case 'FlipCrossedOut': flip('s'); break
 | 
				
			||||||
 | 
								case 'FlipMonospace':  flip('m'); break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return m('.content', {}, content)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let Buffer = {
 | 
				
			||||||
 | 
						view: vnode => {
 | 
				
			||||||
 | 
							let lines = []
 | 
				
			||||||
 | 
							let b = buffers.get(bufferCurrent)
 | 
				
			||||||
 | 
							if (b === undefined)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let lastDateMark = undefined
 | 
				
			||||||
 | 
							b.lines.forEach(line => {
 | 
				
			||||||
 | 
								let date = new Date(line.when * 1000)
 | 
				
			||||||
 | 
								let dateMark = date.toLocaleDateString()
 | 
				
			||||||
 | 
								if (dateMark !== lastDateMark) {
 | 
				
			||||||
 | 
									lines.push(m('.date', {}, dateMark))
 | 
				
			||||||
 | 
									lastDateMark = dateMark
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								lines.push(m('.time', {}, date.toLocaleTimeString()))
 | 
				
			||||||
 | 
								lines.push(m(Content, {}, line))
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return m('.buffer-container', {}, [
 | 
				
			||||||
 | 
								m('.filler'),
 | 
				
			||||||
 | 
								m('.buffer', {}, lines),
 | 
				
			||||||
 | 
							])
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: This should be remembered across buffer switches,
 | 
				
			||||||
 | 
					// and we'll probably have to intercept /all/ key presses.
 | 
				
			||||||
 | 
					let Input = {
 | 
				
			||||||
 | 
						view: vnode => {
 | 
				
			||||||
 | 
							return m('textarea', {
 | 
				
			||||||
 | 
								rows: 1,
 | 
				
			||||||
 | 
								onkeydown: e => {
 | 
				
			||||||
 | 
									// TODO: And perhaps on other actions, too.
 | 
				
			||||||
 | 
									send({command: 'Active'})
 | 
				
			||||||
 | 
									if (e.keyCode !== 13)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									send({
 | 
				
			||||||
 | 
										command: 'BufferInput',
 | 
				
			||||||
 | 
										bufferName: bufferCurrent,
 | 
				
			||||||
 | 
										text: e.currentTarget.value,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									e.preventDefault()
 | 
				
			||||||
 | 
									e.currentTarget.value = ''
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let Main = {
 | 
				
			||||||
 | 
						view: vnode => {
 | 
				
			||||||
 | 
							return m('.xP', {}, [
 | 
				
			||||||
 | 
								m('.title', {}, "xP"),
 | 
				
			||||||
 | 
								m('.middle', {}, [m(BufferList), m(Buffer)]),
 | 
				
			||||||
 | 
								m('.status', {}, bufferCurrent),
 | 
				
			||||||
 | 
								m(Input),
 | 
				
			||||||
 | 
							])
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Buffer names should work as routes.
 | 
				
			||||||
 | 
					window.addEventListener('load', () => {
 | 
				
			||||||
 | 
						m.route(document.body, '/', {
 | 
				
			||||||
 | 
							'/': Main,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										2
									
								
								xP/xP.example.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								xP/xP.example.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										186
									
								
								xP/xP.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								xP/xP.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"encoding/binary"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"html/template"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"golang.org/x/net/websocket"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						addressBind    string
 | 
				
			||||||
 | 
						addressConnect string
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func clientToRelay(
 | 
				
			||||||
 | 
						ctx context.Context, ws *websocket.Conn, conn net.Conn) bool {
 | 
				
			||||||
 | 
						var j string
 | 
				
			||||||
 | 
						if err := websocket.Message.Receive(ws, &j); err != nil {
 | 
				
			||||||
 | 
							log.Println("Command receive failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("?> %s\n", j)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var m RelayCommandMessage
 | 
				
			||||||
 | 
						if err := json.Unmarshal([]byte(j), &m); err != nil {
 | 
				
			||||||
 | 
							log.Println("Command unmarshalling failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b, ok := m.AppendTo(make([]byte, 4))
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							log.Println("Command serialization failed")
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						binary.BigEndian.PutUint32(b[:4], uint32(len(b)-4))
 | 
				
			||||||
 | 
						if _, err := conn.Write(b); err != nil {
 | 
				
			||||||
 | 
							log.Println("Command send failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("-> %v\n", b)
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func relayToClient(
 | 
				
			||||||
 | 
						ctx context.Context, ws *websocket.Conn, conn net.Conn) bool {
 | 
				
			||||||
 | 
						var length uint32
 | 
				
			||||||
 | 
						if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
 | 
				
			||||||
 | 
							log.Println("Event receive failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						b := make([]byte, length)
 | 
				
			||||||
 | 
						if _, err := io.ReadFull(conn, b); err != nil {
 | 
				
			||||||
 | 
							log.Println("Event receive failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("<? %v\n", b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var m RelayEventMessage
 | 
				
			||||||
 | 
						if after, ok := m.ConsumeFrom(b); !ok {
 | 
				
			||||||
 | 
							log.Println("Event deserialization failed")
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						} else if len(after) != 0 {
 | 
				
			||||||
 | 
							log.Println("Event deserialization failed: trailing data")
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						j, err := json.Marshal(&m)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Event marshalling failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := websocket.Message.Send(ws, string(j)); err != nil {
 | 
				
			||||||
 | 
							log.Println("Event send failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("<- %s\n", j)
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func errorToClient(ws *websocket.Conn, err error) bool {
 | 
				
			||||||
 | 
						j, err := json.Marshal(&RelayEventMessage{
 | 
				
			||||||
 | 
							EventSeq: 0,
 | 
				
			||||||
 | 
							Data: RelayEventData{
 | 
				
			||||||
 | 
								Interface: RelayEventDataError{
 | 
				
			||||||
 | 
									Event:      RelayEventError,
 | 
				
			||||||
 | 
									CommandSeq: 0,
 | 
				
			||||||
 | 
									Error:      err.Error(),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Event marshalling failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := websocket.Message.Send(ws, string(j)); err != nil {
 | 
				
			||||||
 | 
							log.Println("Event send failed: " + err.Error())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handleWebSocket(ws *websocket.Conn) {
 | 
				
			||||||
 | 
						conn, err := net.Dial("tcp", addressConnect)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							errorToClient(ws, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We don't need to intervene, so it's just two separate pipes so far.
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(ws.Request().Context())
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							for clientToRelay(ctx, ws, conn) {
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cancel()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							for relayToClient(ctx, ws, conn) {
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cancel()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						<-ctx.Done()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var staticHandler = http.FileServer(http.Dir("."))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var page = template.Must(template.New("/").Parse(`<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
						<title>xP</title>
 | 
				
			||||||
 | 
						<meta charset="utf-8" />
 | 
				
			||||||
 | 
						<link rel="stylesheet" href="xP.css" />
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
						<script src="mithril.js">
 | 
				
			||||||
 | 
						</script>
 | 
				
			||||||
 | 
						<script>
 | 
				
			||||||
 | 
						let proxy = '{{ . }}'
 | 
				
			||||||
 | 
						</script>
 | 
				
			||||||
 | 
						<script src="xP.js">
 | 
				
			||||||
 | 
						</script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>`))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handleDefault(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						if r.URL.Path != "/" {
 | 
				
			||||||
 | 
							staticHandler.ServeHTTP(w, r)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wsURI := fmt.Sprintf("ws://%s/ws", r.Host)
 | 
				
			||||||
 | 
						if err := page.Execute(w, wsURI); err != nil {
 | 
				
			||||||
 | 
							log.Println("Template execution failed: " + err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						if len(os.Args) != 3 {
 | 
				
			||||||
 | 
							log.Fatalf("usage: %s BIND CONNECT\n", os.Args[0])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						addressBind, addressConnect = os.Args[1], os.Args[2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						http.Handle("/ws", websocket.Handler(handleWebSocket))
 | 
				
			||||||
 | 
						http.Handle("/", http.HandlerFunc(handleDefault))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s := &http.Server{
 | 
				
			||||||
 | 
							Addr:           addressBind,
 | 
				
			||||||
 | 
							ReadTimeout:    60 * time.Second,
 | 
				
			||||||
 | 
							WriteTimeout:   60 * time.Second,
 | 
				
			||||||
 | 
							MaxHeaderBytes: 32 << 10,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						log.Fatal(s.ListenAndServe())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user