336 lines
7.6 KiB
C++
336 lines
7.6 KiB
C++
// 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);
|
|
}
|
|
|
|
}
|
|
|