| // CODYlib -*- mode:c++ -*- | |
| // Copyright (C) 2020 Nathan Sidwell, nathan@acm.org | |
| // License: Apache v2.0 | |
| // Cody | |
| #include "internal.hh" | |
| // C | |
| #include <cerrno> | |
| #include <cstdlib> | |
| #include <cstring> | |
| // Client code | |
| namespace Cody { | |
| // These do not need to be members | |
| static Packet ConnectResponse (std::vector<std::string> &words); | |
| static Packet PathnameResponse (std::vector<std::string> &words); | |
| static Packet OKResponse (std::vector<std::string> &words); | |
| static Packet IncludeTranslateResponse (std::vector<std::string> &words); | |
| // Must be consistently ordered with the RequestCode enum | |
| static Packet (*const responseTable[Detail::RC_HWM]) | |
| (std::vector<std::string> &) = | |
| { | |
| &ConnectResponse, | |
| &PathnameResponse, | |
| &PathnameResponse, | |
| &PathnameResponse, | |
| &OKResponse, | |
| &IncludeTranslateResponse, | |
| }; | |
| Client::Client () | |
| { | |
| fd.from = fd.to = -1; | |
| } | |
| Client::Client (Client &&src) | |
| : write (std::move (src.write)), | |
| read (std::move (src.read)), | |
| corked (std::move (src.corked)), | |
| is_direct (src.is_direct), | |
| is_connected (src.is_connected) | |
| { | |
| if (is_direct) | |
| server = src.server; | |
| else | |
| { | |
| fd.from = src.fd.from; | |
| fd.to = src.fd.to; | |
| } | |
| } | |
| Client::~Client () | |
| { | |
| } | |
| Client &Client::operator= (Client &&src) | |
| { | |
| write = std::move (src.write); | |
| read = std::move (src.read); | |
| corked = std::move (src.corked); | |
| is_direct = src.is_direct; | |
| is_connected = src.is_connected; | |
| if (is_direct) | |
| server = src.server; | |
| else | |
| { | |
| fd.from = src.fd.from; | |
| fd.to = src.fd.to; | |
| } | |
| return *this; | |
| } | |
| int Client::CommunicateWithServer () | |
| { | |
| write.PrepareToWrite (); | |
| read.PrepareToRead (); | |
| if (IsDirect ()) | |
| server->DirectProcess (write, read); | |
| else | |
| { | |
| // Write the write buffer | |
| while (int e = write.Write (fd.to)) | |
| if (e != EAGAIN && e != EINTR) | |
| return e; | |
| // Read the read buffer | |
| while (int e = read.Read (fd.from)) | |
| if (e != EAGAIN && e != EINTR) | |
| return e; | |
| } | |
| return 0; | |
| } | |
| static Packet CommunicationError (int err) | |
| { | |
| std::string e {u8"communication error:"}; | |
| e.append (strerror (err)); | |
| return Packet (Client::PC_ERROR, std::move (e)); | |
| } | |
| Packet Client::ProcessResponse (std::vector<std::string> &words, | |
| unsigned code, bool isLast) | |
| { | |
| if (int e = read.Lex (words)) | |
| { | |
| if (e == EINVAL) | |
| { | |
| std::string msg (u8"malformed string '"); | |
| msg.append (words[0]); | |
| msg.append (u8"'"); | |
| return Packet (Client::PC_ERROR, std::move (msg)); | |
| } | |
| else | |
| return Packet (Client::PC_ERROR, u8"missing response"); | |
| } | |
| Assert (!words.empty ()); | |
| if (words[0] == u8"ERROR") | |
| return Packet (Client::PC_ERROR, | |
| words.size () == 2 ? words[1]: u8"malformed error response"); | |
| if (isLast && !read.IsAtEnd ()) | |
| return Packet (Client::PC_ERROR, | |
| std::string (u8"unexpected extra response")); | |
| Assert (code < Detail::RC_HWM); | |
| Packet result (responseTable[code] (words)); | |
| result.SetRequest (code); | |
| if (result.GetCode () == Client::PC_ERROR && result.GetString ().empty ()) | |
| { | |
| std::string msg {u8"malformed response '"}; | |
| read.LexedLine (msg); | |
| msg.append (u8"'"); | |
| result.GetString () = std::move (msg); | |
| } | |
| else if (result.GetCode () == Client::PC_CONNECT) | |
| is_connected = true; | |
| return result; | |
| } | |
| Packet Client::MaybeRequest (unsigned code) | |
| { | |
| if (IsCorked ()) | |
| { | |
| corked.push_back (code); | |
| return Packet (PC_CORKED); | |
| } | |
| if (int err = CommunicateWithServer ()) | |
| return CommunicationError (err); | |
| std::vector<std::string> words; | |
| return ProcessResponse(words, code, true); | |
| } | |
| void Client::Cork () | |
| { | |
| if (corked.empty ()) | |
| corked.push_back (-1); | |
| } | |
| std::vector<Packet> Client::Uncork () | |
| { | |
| std::vector<Packet> result; | |
| if (corked.size () > 1) | |
| { | |
| if (int err = CommunicateWithServer ()) | |
| result.emplace_back (CommunicationError (err)); | |
| else | |
| { | |
| std::vector<std::string> words; | |
| for (auto iter = corked.begin () + 1; iter != corked.end ();) | |
| { | |
| char code = *iter; | |
| ++iter; | |
| result.emplace_back (ProcessResponse (words, code, | |
| iter == corked.end ())); | |
| } | |
| } | |
| } | |
| corked.clear (); | |
| return result; | |
| } | |
| // Now the individual message handlers | |
| // HELLO $vernum $agent $ident | |
| Packet Client::Connect (char const *agent, char const *ident, | |
| size_t alen, size_t ilen) | |
| { | |
| write.BeginLine (); | |
| write.AppendWord (u8"HELLO"); | |
| write.AppendInteger (Version); | |
| write.AppendWord (agent, true, alen); | |
| write.AppendWord (ident, true, ilen); | |
| write.EndLine (); | |
| return MaybeRequest (Detail::RC_CONNECT); | |
| } | |
| // HELLO $version $agent [$flags] | |
| Packet ConnectResponse (std::vector<std::string> &words) | |
| { | |
| if (words[0] == u8"HELLO" && (words.size () == 3 || words.size () == 4)) | |
| { | |
| char *eptr; | |
| unsigned long val = strtoul (words[1].c_str (), &eptr, 10); | |
| unsigned version = unsigned (val); | |
| if (*eptr || version != val || version < Version) | |
| return Packet (Client::PC_ERROR, u8"incompatible version"); | |
| else | |
| { | |
| unsigned flags = 0; | |
| if (words.size () == 4) | |
| { | |
| val = strtoul (words[3].c_str (), &eptr, 10); | |
| flags = unsigned (val); | |
| } | |
| return Packet (Client::PC_CONNECT, flags); | |
| } | |
| } | |
| return Packet (Client::PC_ERROR, u8""); | |
| } | |
| // MODULE-REPO | |
| Packet Client::ModuleRepo () | |
| { | |
| write.BeginLine (); | |
| write.AppendWord (u8"MODULE-REPO"); | |
| write.EndLine (); | |
| return MaybeRequest (Detail::RC_MODULE_REPO); | |
| } | |
| // PATHNAME $dir | ERROR | |
| Packet PathnameResponse (std::vector<std::string> &words) | |
| { | |
| if (words[0] == u8"PATHNAME" && words.size () == 2) | |
| return Packet (Client::PC_PATHNAME, std::move (words[1])); | |
| return Packet (Client::PC_ERROR, u8""); | |
| } | |
| // OK or ERROR | |
| Packet OKResponse (std::vector<std::string> &words) | |
| { | |
| if (words[0] == u8"OK") | |
| return Packet (Client::PC_OK); | |
| else | |
| return Packet (Client::PC_ERROR, | |
| words.size () == 2 ? std::move (words[1]) : ""); | |
| } | |
| // MODULE-EXPORT $modulename [$flags] | |
| Packet Client::ModuleExport (char const *module, Flags flags, size_t mlen) | |
| { | |
| write.BeginLine (); | |
| write.AppendWord (u8"MODULE-EXPORT"); | |
| write.AppendWord (module, true, mlen); | |
| if (flags != Flags::None) | |
| write.AppendInteger (unsigned (flags)); | |
| write.EndLine (); | |
| return MaybeRequest (Detail::RC_MODULE_EXPORT); | |
| } | |
| // MODULE-IMPORT $modulename [$flags] | |
| Packet Client::ModuleImport (char const *module, Flags flags, size_t mlen) | |
| { | |
| write.BeginLine (); | |
| write.AppendWord (u8"MODULE-IMPORT"); | |
| write.AppendWord (module, true, mlen); | |
| if (flags != Flags::None) | |
| write.AppendInteger (unsigned (flags)); | |
| write.EndLine (); | |
| return MaybeRequest (Detail::RC_MODULE_IMPORT); | |
| } | |
| // MODULE-COMPILED $modulename [$flags] | |
| Packet Client::ModuleCompiled (char const *module, Flags flags, size_t mlen) | |
| { | |
| write.BeginLine (); | |
| write.AppendWord (u8"MODULE-COMPILED"); | |
| write.AppendWord (module, true, mlen); | |
| if (flags != Flags::None) | |
| write.AppendInteger (unsigned (flags)); | |
| write.EndLine (); | |
| return MaybeRequest (Detail::RC_MODULE_COMPILED); | |
| } | |
| // INCLUDE-TRANSLATE $includename [$flags] | |
| Packet Client::IncludeTranslate (char const *include, Flags flags, size_t ilen) | |
| { | |
| write.BeginLine (); | |
| write.AppendWord (u8"INCLUDE-TRANSLATE"); | |
| write.AppendWord (include, true, ilen); | |
| if (flags != Flags::None) | |
| write.AppendInteger (unsigned (flags)); | |
| write.EndLine (); | |
| return MaybeRequest (Detail::RC_INCLUDE_TRANSLATE); | |
| } | |
| // BOOL $knowntextualness | |
| // PATHNAME $cmifile | |
| Packet IncludeTranslateResponse (std::vector<std::string> &words) | |
| { | |
| if (words[0] == u8"BOOL" && words.size () == 2) | |
| { | |
| if (words[1] == u8"FALSE") | |
| return Packet (Client::PC_BOOL, 0); | |
| else if (words[1] == u8"TRUE") | |
| return Packet (Client::PC_BOOL, 1); | |
| else | |
| return Packet (Client::PC_ERROR, u8""); | |
| } | |
| else | |
| return PathnameResponse (words); | |
| } | |
| } | |