|
| 1 | +// SPDX-License-Identifier: BSD-2-Clause |
| 2 | +// Copyright CM4all GmbH |
| 3 | +// author: Max Kellermann <max.kellermann@ionos.com> |
| 4 | + |
| 5 | +#include "AllocatorPtr.hxx" |
| 6 | +#include "translation/Protocol.hxx" |
| 7 | +#include "translation/PReader.hxx" |
| 8 | +#include "translation/String.hxx" |
| 9 | +#include "net/ConnectSocket.hxx" |
| 10 | +#include "net/LocalSocketAddress.hxx" |
| 11 | +#include "net/SocketError.hxx" |
| 12 | +#include "net/SocketProtocolError.hxx" |
| 13 | +#include "net/UniqueSocketDescriptor.hxx" |
| 14 | +#include "util/SpanCast.hxx" |
| 15 | +#include "util/StringSplit.hxx" |
| 16 | +#include "util/PrintException.hxx" |
| 17 | + |
| 18 | +#include <fmt/core.h> |
| 19 | + |
| 20 | +#include <sysexits.h> // for EX_* |
| 21 | + |
| 22 | +using std::string_view_literals::operator""sv; |
| 23 | + |
| 24 | +static void |
| 25 | +AppendPacket(std::string &request, const TranslationHeader &header, std::string_view payload) noexcept |
| 26 | +{ |
| 27 | + request += ToStringView(ReferenceAsBytes(header)); |
| 28 | + request += payload; |
| 29 | +} |
| 30 | + |
| 31 | +static std::string |
| 32 | +ParseRequest(std::span<const char * const> args) |
| 33 | +{ |
| 34 | + std::string request; |
| 35 | + |
| 36 | + // Add implicit BEGIN packet |
| 37 | + static constexpr TranslationHeader begin_header{ |
| 38 | + .length = 0, |
| 39 | + .command = TranslationCommand::BEGIN, |
| 40 | + }; |
| 41 | + AppendPacket(request, begin_header, {}); |
| 42 | + |
| 43 | + // Parse and add command line packets |
| 44 | + for (const char *arg : args) { |
| 45 | + std::string_view arg_view = arg; |
| 46 | + auto [cmd_str, payload] = Split(arg_view, '='); |
| 47 | + if (payload.data() == nullptr) |
| 48 | + throw std::runtime_error(fmt::format("Invalid packet format (expected COMMAND=PAYLOAD): {:?}", arg)); |
| 49 | + |
| 50 | + TranslationCommand command = ParseTranslationCommand(cmd_str); |
| 51 | + |
| 52 | + TranslationHeader header{ |
| 53 | + .length = static_cast<uint16_t>(payload.size()), |
| 54 | + .command = command, |
| 55 | + }; |
| 56 | + AppendPacket(request, header, payload); |
| 57 | + } |
| 58 | + |
| 59 | + // Add END packet |
| 60 | + static constexpr TranslationHeader end_header{ |
| 61 | + .length = 0, |
| 62 | + .command = TranslationCommand::END, |
| 63 | + }; |
| 64 | + AppendPacket(request, end_header, {}); |
| 65 | + |
| 66 | + return request; |
| 67 | +} |
| 68 | + |
| 69 | +static void |
| 70 | +DumpPacket(TranslationCommand command, std::span<const std::byte> payload) |
| 71 | +{ |
| 72 | + if (payload.empty()) |
| 73 | + fmt::print("{}\n", ToString(command)); |
| 74 | + else |
| 75 | + fmt::print("{} = {:?}\n", ToString(command), ToStringView(payload)); |
| 76 | +} |
| 77 | + |
| 78 | +static void |
| 79 | +ReadAndProcessResponse(SocketDescriptor socket, AllocatorPtr alloc) |
| 80 | +{ |
| 81 | + TranslatePacketReader reader; |
| 82 | + |
| 83 | + while (true) { |
| 84 | + std::array<std::byte, 4096> buffer; |
| 85 | + ssize_t bytes_read = socket.Read(buffer); |
| 86 | + if (bytes_read <= 0) [[unlikely]] { |
| 87 | + if (bytes_read < 0) |
| 88 | + throw MakeSocketError("Failed to read from socket"); |
| 89 | + else |
| 90 | + throw SocketClosedPrematurelyError{}; |
| 91 | + } |
| 92 | + |
| 93 | + auto received = std::span{buffer}.first(bytes_read); |
| 94 | + while (true) { |
| 95 | + const auto consumed = reader.Feed(alloc, received); |
| 96 | + if (consumed == 0) |
| 97 | + // Need more data |
| 98 | + break; |
| 99 | + |
| 100 | + received = received.subspan(consumed); |
| 101 | + |
| 102 | + if (reader.IsComplete()) { |
| 103 | + const auto command = reader.GetCommand(); |
| 104 | + DumpPacket(command, reader.GetPayload()); |
| 105 | + |
| 106 | + if (command == TranslationCommand::END) |
| 107 | + return; |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | + |
| 114 | +int |
| 115 | +main(int argc, char **argv) |
| 116 | +try { |
| 117 | + if (argc < 2) { |
| 118 | + fmt::print(stderr, |
| 119 | + "Usage: {} SOCKET_PATH [COMMAND=PAYLOAD] ...\nExample: {} /tmp/translation.sock HOST=example.com URI=/path"sv, |
| 120 | + argv[0], argv[0]); |
| 121 | + return EX_USAGE; |
| 122 | + } |
| 123 | + |
| 124 | + const char *const socket_path = argv[1]; |
| 125 | + const auto request = ParseRequest({argv + 2, static_cast<size_t>(argc - 2)}); |
| 126 | + |
| 127 | + auto socket = CreateConnectSocket(LocalSocketAddress{socket_path}, SOCK_STREAM); |
| 128 | + socket.FullWrite(AsBytes(request)); |
| 129 | + |
| 130 | + Allocator allocator_instance; |
| 131 | + AllocatorPtr alloc{allocator_instance}; |
| 132 | + ReadAndProcessResponse(socket, alloc); |
| 133 | + |
| 134 | + return EXIT_SUCCESS; |
| 135 | +} catch (...) { |
| 136 | + PrintException(std::current_exception()); |
| 137 | + return EXIT_FAILURE; |
| 138 | +} |
0 commit comments