1// CODYlib -*- mode:c++ -*-
2// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
3// License: Apache v2.0
4
5// Cody
6#include "internal.hh"
7// C
8#include <cerrno>
9#include <cstdlib>
10#include <cstring>
11
12// Client code
13
14namespace Cody {
15
16// These do not need to be members
17static Packet ConnectResponse (std::vector<std::string> &words);
18static Packet PathnameResponse (std::vector<std::string> &words);
19static Packet OKResponse (std::vector<std::string> &words);
20static Packet IncludeTranslateResponse (std::vector<std::string> &words);
21
22// Must be consistently ordered with the RequestCode enum
23static Packet (*const responseTable[Detail::RC_HWM])
24 (std::vector<std::string> &) =
25 {
26 &ConnectResponse,
27 &PathnameResponse,
28 &PathnameResponse,
29 &PathnameResponse,
30 &OKResponse,
31 &IncludeTranslateResponse,
32 };
33
34Client::Client ()
35{
36 fd.from = fd.to = -1;
37}
38
39Client::Client (Client &&src)
40 : write (std::move (src.write)),
41 read (std::move (src.read)),
42 corked (std::move (src.corked)),
43 is_direct (src.is_direct),
44 is_connected (src.is_connected)
45{
46 if (is_direct)
47 server = src.server;
48 else
49 {
50 fd.from = src.fd.from;
51 fd.to = src.fd.to;
52 }
53}
54
55Client::~Client ()
56{
57}
58
59Client &Client::operator= (Client &&src)
60{
61 write = std::move (src.write);
62 read = std::move (src.read);
63 corked = std::move (src.corked);
64 is_direct = src.is_direct;
65 is_connected = src.is_connected;
66 if (is_direct)
67 server = src.server;
68 else
69 {
70 fd.from = src.fd.from;
71 fd.to = src.fd.to;
72 }
73
74 return *this;
75}
76
77int Client::CommunicateWithServer ()
78{
79 write.PrepareToWrite ();
80 read.PrepareToRead ();
81 if (IsDirect ())
82 server->DirectProcess (from&: write, to&: read);
83 else
84 {
85 // Write the write buffer
86 while (int e = write.Write (fd: fd.to))
87 if (e != EAGAIN && e != EINTR)
88 return e;
89 // Read the read buffer
90 while (int e = read.Read (fd: fd.from))
91 if (e != EAGAIN && e != EINTR)
92 return e;
93 }
94
95 return 0;
96}
97
98static Packet CommunicationError (int err)
99{
100 std::string e {(const char *) u8"communication error:"};
101 e.append (s: strerror (errnum: err));
102
103 return Packet (Client::PC_ERROR, std::move (e));
104}
105
106Packet Client::ProcessResponse (std::vector<std::string> &words,
107 unsigned code, bool isLast)
108{
109 if (int e = read.Lex (words))
110 {
111 if (e == EINVAL)
112 {
113 std::string msg ((const char *) u8"malformed string '");
114 msg.append (str: words[0]);
115 msg.append (s: (const char *) u8"'");
116 return Packet (Client::PC_ERROR, std::move (msg));
117 }
118 else
119 return Packet (Client::PC_ERROR, (const char *) u8"missing response");
120 }
121
122 Assert (!words.empty ());
123 if (words[0] == (const char *) u8"ERROR")
124 return Packet (Client::PC_ERROR,
125 words.size () == 2 ? words[1]
126 : (const char *) u8"malformed error response");
127
128 if (isLast && !read.IsAtEnd ())
129 return Packet (Client::PC_ERROR,
130 std::string ((const char *) u8"unexpected extra response"));
131
132 Assert (code < Detail::RC_HWM);
133 Packet result (responseTable[code] (words));
134 result.SetRequest (code);
135 if (result.GetCode () == Client::PC_ERROR && result.GetString ().empty ())
136 {
137 std::string msg {(const char *) u8"malformed response '"};
138
139 read.LexedLine (l&: msg);
140 msg.append (s: (const char *) u8"'");
141 result.GetString () = std::move (msg);
142 }
143 else if (result.GetCode () == Client::PC_CONNECT)
144 is_connected = true;
145
146 return result;
147}
148
149Packet Client::MaybeRequest (unsigned code)
150{
151 if (IsCorked ())
152 {
153 corked.push_back (c: code);
154 return Packet (PC_CORKED);
155 }
156
157 if (int err = CommunicateWithServer ())
158 return CommunicationError (err);
159
160 std::vector<std::string> words;
161 return ProcessResponse(words, code, isLast: true);
162}
163
164void Client::Cork ()
165{
166 if (corked.empty ())
167 corked.push_back (c: -1);
168}
169
170std::vector<Packet> Client::Uncork ()
171{
172 std::vector<Packet> result;
173
174 if (corked.size () > 1)
175 {
176 if (int err = CommunicateWithServer ())
177 result.emplace_back (args: CommunicationError (err));
178 else
179 {
180 std::vector<std::string> words;
181 for (auto iter = corked.begin () + 1; iter != corked.end ();)
182 {
183 char code = *iter;
184 ++iter;
185 result.emplace_back (args: ProcessResponse (words, code,
186 isLast: iter == corked.end ()));
187 }
188 }
189 }
190
191 corked.clear ();
192
193 return result;
194}
195
196// Now the individual message handlers
197
198// HELLO $vernum $agent $ident
199Packet Client::Connect (char const *agent, char const *ident,
200 size_t alen, size_t ilen)
201{
202 write.BeginLine ();
203 write.AppendWord (str: (const char *) u8"HELLO");
204 write.AppendInteger (u: Version);
205 write.AppendWord (str: agent, maybe_quote: true, len: alen);
206 write.AppendWord (str: ident, maybe_quote: true, len: ilen);
207 write.EndLine ();
208
209 return MaybeRequest (code: Detail::RC_CONNECT);
210}
211
212// HELLO $version $agent [$flags]
213Packet ConnectResponse (std::vector<std::string> &words)
214{
215 if (words[0] == (const char *) u8"HELLO"
216 && (words.size () == 3 || words.size () == 4))
217 {
218 char *eptr;
219 unsigned long val = strtoul (nptr: words[1].c_str (), endptr: &eptr, base: 10);
220
221 unsigned version = unsigned (val);
222 if (*eptr || version != val || version < Version)
223 return Packet (Client::PC_ERROR, u8"incompatible version");
224 else
225 {
226 unsigned flags = 0;
227 if (words.size () == 4)
228 {
229 val = strtoul (nptr: words[3].c_str (), endptr: &eptr, base: 10);
230 flags = unsigned (val);
231 }
232 return Packet (Client::PC_CONNECT, flags);
233 }
234 }
235
236 return Packet (Client::PC_ERROR, u8"");
237}
238
239// MODULE-REPO
240Packet Client::ModuleRepo ()
241{
242 write.BeginLine ();
243 write.AppendWord (str: u8"MODULE-REPO");
244 write.EndLine ();
245
246 return MaybeRequest (code: Detail::RC_MODULE_REPO);
247}
248
249// PATHNAME $dir | ERROR
250Packet PathnameResponse (std::vector<std::string> &words)
251{
252 if (words[0] == (const char *) u8"PATHNAME" && words.size () == 2)
253 return Packet (Client::PC_PATHNAME, std::move (words[1]));
254
255 return Packet (Client::PC_ERROR, u8"");
256}
257
258// OK or ERROR
259Packet OKResponse (std::vector<std::string> &words)
260{
261 if (words[0] == (const char *) u8"OK")
262 return Packet (Client::PC_OK);
263 else
264 return Packet (Client::PC_ERROR,
265 words.size () == 2 ? std::move (words[1]) : "");
266}
267
268// MODULE-EXPORT $modulename [$flags]
269Packet Client::ModuleExport (char const *module, Flags flags, size_t mlen)
270{
271 write.BeginLine ();
272 write.AppendWord (str: u8"MODULE-EXPORT");
273 write.AppendWord (str: module, maybe_quote: true, len: mlen);
274 if (flags != Flags::None)
275 write.AppendInteger (u: unsigned (flags));
276 write.EndLine ();
277
278 return MaybeRequest (code: Detail::RC_MODULE_EXPORT);
279}
280
281// MODULE-IMPORT $modulename [$flags]
282Packet Client::ModuleImport (char const *module, Flags flags, size_t mlen)
283{
284 write.BeginLine ();
285 write.AppendWord (str: u8"MODULE-IMPORT");
286 write.AppendWord (str: module, maybe_quote: true, len: mlen);
287 if (flags != Flags::None)
288 write.AppendInteger (u: unsigned (flags));
289 write.EndLine ();
290
291 return MaybeRequest (code: Detail::RC_MODULE_IMPORT);
292}
293
294// MODULE-COMPILED $modulename [$flags]
295Packet Client::ModuleCompiled (char const *module, Flags flags, size_t mlen)
296{
297 write.BeginLine ();
298 write.AppendWord (str: u8"MODULE-COMPILED");
299 write.AppendWord (str: module, maybe_quote: true, len: mlen);
300 if (flags != Flags::None)
301 write.AppendInteger (u: unsigned (flags));
302 write.EndLine ();
303
304 return MaybeRequest (code: Detail::RC_MODULE_COMPILED);
305}
306
307// INCLUDE-TRANSLATE $includename [$flags]
308Packet Client::IncludeTranslate (char const *include, Flags flags, size_t ilen)
309{
310 write.BeginLine ();
311 write.AppendWord (str: u8"INCLUDE-TRANSLATE");
312 write.AppendWord (str: include, maybe_quote: true, len: ilen);
313 if (flags != Flags::None)
314 write.AppendInteger (u: unsigned (flags));
315 write.EndLine ();
316
317 return MaybeRequest (code: Detail::RC_INCLUDE_TRANSLATE);
318}
319
320// BOOL $knowntextualness
321// PATHNAME $cmifile
322Packet IncludeTranslateResponse (std::vector<std::string> &words)
323{
324 if (words[0] == (const char *) u8"BOOL" && words.size () == 2)
325 {
326 if (words[1] == (const char *) u8"FALSE")
327 return Packet (Client::PC_BOOL);
328 else if (words[1] == (const char *) u8"TRUE")
329 return Packet (Client::PC_BOOL, 1);
330 else
331 return Packet (Client::PC_ERROR, u8"");
332 }
333 else
334 return PathnameResponse (words);
335}
336
337}
338
339

source code of libcody/client.cc