Compare commits
	
		
			No commits in common. "1639235a48dbed75c2563c9a497b41c31a2a1bae" and "3af1765261b7667b3570af8d69c2e1576a4b2d05" have entirely different histories.
		
	
	
		
			1639235a48
			...
			3af1765261
		
	
		
| @ -1,12 +1,10 @@ | |||||||
| # 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 | project (xK VERSION 1.5.0 DESCRIPTION "IRC client, daemon and bot" LANGUAGES C) | ||||||
| 	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) | ||||||
| @ -145,55 +143,28 @@ set (HAVE_EDITLINE "${WANT_LIBEDIT}") | |||||||
| set (HAVE_LUA      "${WITH_LUA}") | set (HAVE_LUA      "${WITH_LUA}") | ||||||
| 
 | 
 | ||||||
| include (GNUInstallDirs) | include (GNUInstallDirs) | ||||||
| set (project_config ${PROJECT_BINARY_DIR}/config.h) | configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${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 | ||||||
| add_custom_command (OUTPUT xD-replies.c xD.msg | add_custom_command (OUTPUT xD-replies.c xD.msg | ||||||
| 	COMMAND env LC_ALL=C awk | 	COMMAND env LC_ALL=C awk -f ${PROJECT_SOURCE_DIR}/xD-gen-replies.awk | ||||||
| 		-f ${PROJECT_SOURCE_DIR}/xD-gen-replies.awk | 		> xD-replies.c < ${PROJECT_SOURCE_DIR}/xD-replies | ||||||
| 		${PROJECT_SOURCE_DIR}/xD-replies > xD-replies.c | 	DEPENDS ${PROJECT_SOURCE_DIR}/xD-replies | ||||||
| 	DEPENDS |  | ||||||
| 		${PROJECT_SOURCE_DIR}/xD-gen-replies.awk |  | ||||||
| 		${PROJECT_SOURCE_DIR}/xD-replies |  | ||||||
| 	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_config}) | 	add_executable (${name} ${name}.c ${PROJECT_BINARY_DIR}/config.h) | ||||||
| 	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 xC-proto) | add_dependencies (xC replies) | ||||||
| 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,9 +1,5 @@ | |||||||
| 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 | ||||||
| @ -14,13 +10,11 @@ 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 | ||||||
| 
 | 
 | ||||||
|  * xC: added a relay interface, enabled through the general.relay_bind option |  * xD: implemented WALLOPS, choosing to make it target even non-operators | ||||||
| 
 |  | ||||||
|  * 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 daemon, bot, terminal | 'xK' (chat kit) is an IRC software suite consisting of a terminal client, | ||||||
| client, and X11/web frontends for the client.  It's all you're ever going to | daemon, and bot.  It's all you're ever going to need for chatting, | ||||||
| need for chatting, so long as you can make do with slightly minimalist software. | 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,18 +20,8 @@ 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, a remote relay interface, or basic support for Lua scripting. | certificates, or basic support for Lua scripting.  As a unique bonus, you can | ||||||
| As a unique bonus, you can launch a full text editor from within. | 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 | ||||||
| -- | -- | ||||||
| @ -48,8 +38,9 @@ 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 https://git.janouch.name/p/haven/src/branch/master/hid[ | This program has been | ||||||
| ported to Go] in a different project, and development continues over there. | https://git.janouch.name/p/haven/src/branch/master/hid[ported to Go], | ||||||
|  | and development continues over there. | ||||||
| 
 | 
 | ||||||
| xB | xB | ||||||
| -- | -- | ||||||
| @ -69,12 +60,11 @@ a package with the latest development version from Archlinux's AUR. | |||||||
| 
 | 
 | ||||||
| Building | Building | ||||||
| -------- | -------- | ||||||
| Build-only dependencies: | Build dependencies: CMake, pkg-config, asciidoctor or asciidoc, awk, | ||||||
|  CMake, pkg-config, asciidoctor or asciidoc, awk, liberty (included) + |                     liberty (included) + | ||||||
| Common runtime dependencies: openssl + | Runtime dependencies: openssl + | ||||||
| Additionally for 'xC': curses, libffi, + | Additionally for 'xC': curses, libffi, lua >= 5.3 (optional), | ||||||
|  readline >= 6.0 or libedit >= 2013-07-12, lua >= 5.3 (optional) + |                        readline >= 6.0 or libedit >= 2013-07-12 | ||||||
| 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 - 2022, Přemysl Eric Janouch <p@janouch.name> |  * Copyright (c) 2014 - 2020, 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,66 +48,6 @@ 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.,
 | ||||||
| @ -134,15 +74,6 @@ 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) | ||||||
| { | { | ||||||
|  | |||||||
| @ -1,325 +0,0 @@ | |||||||
| # 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] |  | ||||||
| } |  | ||||||
| @ -1,447 +0,0 @@ | |||||||
| # 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
									
									
									
									
									
								
							
							
						
						
									
										303
									
								
								xC-gen-proto.awk
									
									
									
									
									
								
							| @ -1,303 +0,0 @@ | |||||||
| # 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
									
									
									
									
									
								
							
							
						
						
									
										120
									
								
								xC-proto
									
									
									
									
									
								
							| @ -1,120 +0,0 @@ | |||||||
| // 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; |  | ||||||
| }; |  | ||||||
							
								
								
									
										39
									
								
								xD.c
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								xD.c
									
									
									
									
									
								
							| @ -853,6 +853,8 @@ 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
 | ||||||
| @ -3093,7 +3095,6 @@ 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; | ||||||
| 		} | 		} | ||||||
| @ -3135,7 +3136,6 @@ 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,10 +3421,16 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd) | |||||||
| 		if (errno == EINTR) | 		if (errno == EINTR) | ||||||
| 			return true; | 			return true; | ||||||
| 
 | 
 | ||||||
| 		if (accept_error_is_transient (errno)) | 		if (errno == EBADF | ||||||
| 			print_warning ("%s: %s", "accept", strerror (errno)); | 		 || errno == EINVAL | ||||||
| 		else | 		 || errno == ENOTSOCK | ||||||
|  | 		 || errno == EOPNOTSUPP) | ||||||
| 			print_fatal ("%s: %s", "accept", strerror (errno)); | 			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)); | ||||||
| 		return true; | 		return true; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -3808,9 +3814,10 @@ irc_lock_pid_file (struct server_context *ctx, struct error **e) | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int | static int | ||||||
| irc_listen (struct addrinfo *ai) | irc_listen (struct addrinfo *gai_iter) | ||||||
| { | { | ||||||
| 	int fd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); | 	int fd = socket (gai_iter->ai_family, | ||||||
|  | 		gai_iter->ai_socktype, gai_iter->ai_protocol); | ||||||
| 	if (fd == -1) | 	if (fd == -1) | ||||||
| 		return -1; | 		return -1; | ||||||
| 	set_cloexec (fd); | 	set_cloexec (fd); | ||||||
| @ -3824,13 +3831,21 @@ irc_listen (struct addrinfo *ai) | |||||||
| #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 (ai->ai_family == AF_INET6) | 	if (gai_iter->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 *address = gai_reconstruct_address (ai); | 	char host[NI_MAXHOST], port[NI_MAXSERV]; | ||||||
| 	if (bind (fd, ai->ai_addr, ai->ai_addrlen)) | 	host[0] = port[0] = '\0'; | ||||||
|  | 	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)); | ||||||
| @ -3850,12 +3865,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 = NULL, *gai_iter = NULL; | 	struct addrinfo *gai_result, *gai_iter; | ||||||
| 	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 ("binding to %s failed: %s: %s", | 		print_error ("bind to %s failed: %s: %s", | ||||||
| 			address, "getaddrinfo", gai_strerror (err)); | 			address, "getaddrinfo", gai_strerror (err)); | ||||||
| 		free (address); | 		free (address); | ||||||
| 		return; | 		return; | ||||||
|  | |||||||
							
								
								
									
										172
									
								
								xF.c
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								xF.c
									
									
									
									
									
								
							| @ -1,172 +0,0 @@ | |||||||
| /*
 |  | ||||||
|  * 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
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								xF.svg
									
									
									
									
									
								
							| @ -1,36 +0,0 @@ | |||||||
| <?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> |  | ||||||
| Before Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										3
									
								
								xP/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								xP/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| /xP |  | ||||||
| /proto.go |  | ||||||
| /public/mithril.js |  | ||||||
							
								
								
									
										14
									
								
								xP/Makefile
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								xP/Makefile
									
									
									
									
									
								
							| @ -1,14 +0,0 @@ | |||||||
| .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) |  | ||||||
| @ -1,5 +0,0 @@ | |||||||
| module janouch.name/xK |  | ||||||
| 
 |  | ||||||
| go 1.18 |  | ||||||
| 
 |  | ||||||
| require golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d |  | ||||||
| @ -1,2 +0,0 @@ | |||||||
| 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
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								xP/public/xP.css
									
									
									
									
									
								
							| @ -1,109 +0,0 @@ | |||||||
| 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
									
									
									
									
									
								
							
							
						
						
									
										188
									
								
								xP/public/xP.js
									
									
									
									
									
								
							| @ -1,188 +0,0 @@ | |||||||
| // 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, |  | ||||||
| 	}) |  | ||||||
| }) |  | ||||||
| @ -1,2 +0,0 @@ | |||||||
| { |  | ||||||
| } |  | ||||||
							
								
								
									
										186
									
								
								xP/xP.go
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								xP/xP.go
									
									
									
									
									
								
							| @ -1,186 +0,0 @@ | |||||||
| 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()) |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user