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 {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 (u8"malformed string '");
114 msg.append (str: words[0]);
115 msg.append (s: u8"'");
116 return Packet (Client::PC_ERROR, std::move (msg));
117 }
118 else
119 return Packet (Client::PC_ERROR, u8"missing response");
120 }
121
122 Assert (!words.empty ());
123 if (words[0] == u8"ERROR")
124 return Packet (Client::PC_ERROR,
125 words.size () == 2 ? words[1]: u8"malformed error response");
126
127 if (isLast && !read.IsAtEnd ())
128 return Packet (Client::PC_ERROR,
129 std::string (u8"unexpected extra response"));
130
131 Assert (code < Detail::RC_HWM);
132 Packet result (responseTable[code] (words));
133 result.SetRequest (code);
134 if (result.GetCode () == Client::PC_ERROR && result.GetString ().empty ())
135 {
136 std::string msg {u8"malformed response '"};
137
138 read.LexedLine (l&: msg);
139 msg.append (s: u8"'");
140 result.GetString () = std::move (msg);
141 }
142 else if (result.GetCode () == Client::PC_CONNECT)
143 is_connected = true;
144
145 return result;
146}
147
148Packet Client::MaybeRequest (unsigned code)
149{
150 if (IsCorked ())
151 {
152 corked.push_back (c: code);
153 return Packet (PC_CORKED);
154 }
155
156 if (int err = CommunicateWithServer ())
157 return CommunicationError (err);
158
159 std::vector<std::string> words;
160 return ProcessResponse(words, code, isLast: true);
161}
162
163void Client::Cork ()
164{
165 if (corked.empty ())
166 corked.push_back (c: -1);
167}
168
169std::vector<Packet> Client::Uncork ()
170{
171 std::vector<Packet> result;
172
173 if (corked.size () > 1)
174 {
175 if (int err = CommunicateWithServer ())
176 result.emplace_back (args: CommunicationError (err));
177 else
178 {
179 std::vector<std::string> words;
180 for (auto iter = corked.begin () + 1; iter != corked.end ();)
181 {
182 char code = *iter;
183 ++iter;
184 result.emplace_back (args: ProcessResponse (words, code,
185 isLast: iter == corked.end ()));
186 }
187 }
188 }
189
190 corked.clear ();
191
192 return result;
193}
194
195// Now the individual message handlers
196
197// HELLO $vernum $agent $ident
198Packet Client::Connect (char const *agent, char const *ident,
199 size_t alen, size_t ilen)
200{
201 write.BeginLine ();
202 write.AppendWord (str: u8"HELLO");
203 write.AppendInteger (u: Version);
204 write.AppendWord (str: agent, maybe_quote: true, len: alen);
205 write.AppendWord (str: ident, maybe_quote: true, len: ilen);
206 write.EndLine ();
207
208 return MaybeRequest (code: Detail::RC_CONNECT);
209}
210
211// HELLO $version $agent [$flags]
212Packet ConnectResponse (std::vector<std::string> &words)
213{
214 if (words[0] == u8"HELLO" && (words.size () == 3 || words.size () == 4))
215 {
216 char *eptr;
217 unsigned long val = strtoul (nptr: words[1].c_str (), endptr: &eptr, base: 10);
218
219 unsigned version = unsigned (val);
220 if (*eptr || version != val || version < Version)
221 return Packet (Client::PC_ERROR, u8"incompatible version");
222 else
223 {
224 unsigned flags = 0;
225 if (words.size () == 4)
226 {
227 val = strtoul (nptr: words[3].c_str (), endptr: &eptr, base: 10);
228 flags = unsigned (val);
229 }
230 return Packet (Client::PC_CONNECT, flags);
231 }
232 }
233
234 return Packet (Client::PC_ERROR, u8"");
235}
236
237// MODULE-REPO
238Packet Client::ModuleRepo ()
239{
240 write.BeginLine ();
241 write.AppendWord (str: u8"MODULE-REPO");
242 write.EndLine ();
243
244 return MaybeRequest (code: Detail::RC_MODULE_REPO);
245}
246
247// PATHNAME $dir | ERROR
248Packet PathnameResponse (std::vector<std::string> &words)
249{
250 if (words[0] == u8"PATHNAME" && words.size () == 2)
251 return Packet (Client::PC_PATHNAME, std::move (words[1]));
252
253 return Packet (Client::PC_ERROR, u8"");
254}
255
256// OK or ERROR
257Packet OKResponse (std::vector<std::string> &words)
258{
259 if (words[0] == u8"OK")
260 return Packet (Client::PC_OK);
261 else
262 return Packet (Client::PC_ERROR,
263 words.size () == 2 ? std::move (words[1]) : "");
264}
265
266// MODULE-EXPORT $modulename [$flags]
267Packet Client::ModuleExport (char const *module, Flags flags, size_t mlen)
268{
269 write.BeginLine ();
270 write.AppendWord (str: u8"MODULE-EXPORT");
271 write.AppendWord (str: module, maybe_quote: true, len: mlen);
272 if (flags != Flags::None)
273 write.AppendInteger (u: unsigned (flags));
274 write.EndLine ();
275
276 return MaybeRequest (code: Detail::RC_MODULE_EXPORT);
277}
278
279// MODULE-IMPORT $modulename [$flags]
280Packet Client::ModuleImport (char const *module, Flags flags, size_t mlen)
281{
282 write.BeginLine ();
283 write.AppendWord (str: u8"MODULE-IMPORT");
284 write.AppendWord (str: module, maybe_quote: true, len: mlen);
285 if (flags != Flags::None)
286 write.AppendInteger (u: unsigned (flags));
287 write.EndLine ();
288
289 return MaybeRequest (code: Detail::RC_MODULE_IMPORT);
290}
291
292// MODULE-COMPILED $modulename [$flags]
293Packet Client::ModuleCompiled (char const *module, Flags flags, size_t mlen)
294{
295 write.BeginLine ();
296 write.AppendWord (str: u8"MODULE-COMPILED");
297 write.AppendWord (str: module, maybe_quote: true, len: mlen);
298 if (flags != Flags::None)
299 write.AppendInteger (u: unsigned (flags));
300 write.EndLine ();
301
302 return MaybeRequest (code: Detail::RC_MODULE_COMPILED);
303}
304
305// INCLUDE-TRANSLATE $includename [$flags]
306Packet Client::IncludeTranslate (char const *include, Flags flags, size_t ilen)
307{
308 write.BeginLine ();
309 write.AppendWord (str: u8"INCLUDE-TRANSLATE");
310 write.AppendWord (str: include, maybe_quote: true, len: ilen);
311 if (flags != Flags::None)
312 write.AppendInteger (u: unsigned (flags));
313 write.EndLine ();
314
315 return MaybeRequest (code: Detail::RC_INCLUDE_TRANSLATE);
316}
317
318// BOOL $knowntextualness
319// PATHNAME $cmifile
320Packet IncludeTranslateResponse (std::vector<std::string> &words)
321{
322 if (words[0] == u8"BOOL" && words.size () == 2)
323 {
324 if (words[1] == u8"FALSE")
325 return Packet (Client::PC_BOOL, 0);
326 else if (words[1] == u8"TRUE")
327 return Packet (Client::PC_BOOL, 1);
328 else
329 return Packet (Client::PC_ERROR, u8"");
330 }
331 else
332 return PathnameResponse (words);
333}
334
335}
336
337

source code of libcody/client.cc