client.cc 7.6 KB

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