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