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 14 namespace Cody { 15 16 // These do not need to be members 17 static Packet ConnectResponse (std::vector<std::string> &words); 18 static Packet PathnameResponse (std::vector<std::string> &words); 19 static Packet OKResponse (std::vector<std::string> &words); 20 static Packet IncludeTranslateResponse (std::vector<std::string> &words); 21 22 // Must be consistently ordered with the RequestCode enum 23 static 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 34 Client::Client () 35 { 36 fd.from = fd.to = -1; 37 } 38 39 Client::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 55 Client::~Client () 56 { 57 } 58 59 Client &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 77 int 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 98 static 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 106 Packet 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 148 Packet 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 163 void Client::Cork () 164 { 165 if (corked.empty ()) 166 corked.push_back (-1); 167 } 168 169 std::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 198 Packet 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] 212 Packet 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 238 Packet 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 248 Packet 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 257 Packet 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] 267 Packet 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] 280 Packet 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] 293 Packet 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] 306 Packet 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 320 Packet 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