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 (write, read);
83  else
84    {
85      // Write the write buffer
86      while (int e = write.Write (fd.to))
87	if (e != EAGAIN && e != EINTR)
88	  return e;
89      // Read the read buffer
90      while (int e = read.Read (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 (strerror (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 (words[0]);
115	  msg.append (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 (msg);
139      msg.append (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 (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, true);
161}
162
163void Client::Cork ()
164{
165  if (corked.empty ())
166    corked.push_back (-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 (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 (ProcessResponse (words, code,
185						    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 (u8"HELLO");
203  write.AppendInteger (Version);
204  write.AppendWord (agent, true, alen);
205  write.AppendWord (ident, true, ilen);
206  write.EndLine ();
207
208  return MaybeRequest (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 (words[1].c_str (), &eptr, 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 (words[3].c_str (), &eptr, 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 (u8"MODULE-REPO");
242  write.EndLine ();
243
244  return MaybeRequest (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 (u8"MODULE-EXPORT");
271  write.AppendWord (module, true, mlen);
272  if (flags != Flags::None)
273    write.AppendInteger (unsigned (flags));
274  write.EndLine ();
275
276  return MaybeRequest (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 (u8"MODULE-IMPORT");
284  write.AppendWord (module, true, mlen);
285  if (flags != Flags::None)
286    write.AppendInteger (unsigned (flags));
287  write.EndLine ();
288
289  return MaybeRequest (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 (u8"MODULE-COMPILED");
297  write.AppendWord (module, true, mlen);
298  if (flags != Flags::None)
299    write.AppendInteger (unsigned (flags));
300  write.EndLine ();
301
302  return MaybeRequest (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 (u8"INCLUDE-TRANSLATE");
310  write.AppendWord (include, true, ilen);
311  if (flags != Flags::None)
312    write.AppendInteger (unsigned (flags));
313  write.EndLine ();
314
315  return MaybeRequest (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