(root)/
gcc-13.2.0/
libcody/
client.cc
// 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);
}

}