1// CODYlib		-*- mode:c++ -*-
2// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
3// License: Apache v2.0
4
5#ifndef CODY_HH
6#define CODY_HH 1
7
8// If the user specifies this as non-zero, it must be what we expect,
9// generally only good for requesting no networking
10#if !defined (CODY_NETWORKING)
11// Have a known-good list of networking systems
12#if defined (__unix__) || defined (__MACH__)
13#define CODY_NETWORKING 1
14#else
15#define CODY_NETWORKING 0
16#endif
17#if 0  // For testing
18#undef CODY_NETWORKING
19#define CODY_NETWORKING 0
20#endif
21#endif
22
23// C++
24#include <memory>
25#include <string>
26#include <vector>
27// C
28#include <cstddef>
29// OS
30#include <errno.h>
31#include <sys/types.h>
32#if CODY_NETWORKING
33#include <sys/socket.h>
34#endif
35
36namespace Cody {
37
38// Set version to 1, as this is completely incompatible with 0.
39// Fortunately both versions 0 and 1 will recognize each other's HELLO
40// messages sufficiently to error out
41constexpr unsigned Version = 1;
42
43// FIXME: I guess we need a file-handle abstraction here
44// Is windows DWORDPTR still?, or should it be FILE *? (ew).
45
46namespace Detail  {
47
48// C++11 doesn't have utf8 character literals :(
49
50template<unsigned I>
51constexpr char S2C (char const (&s)[I])
52{
53  static_assert (I == 2, "only single octet strings may be converted");
54  return s[0];
55}
56
57/// Internal buffering class.  Used to concatenate outgoing messages
58/// and Lex incoming ones.
59class MessageBuffer
60{
61  std::vector<char> buffer;  ///< buffer holding the message
62  size_t lastBol = 0;  ///< location of the most recent Beginning Of
63		       ///< Line, or position we've readed when writing
64
65public:
66  MessageBuffer () = default;
67  ~MessageBuffer () = default;
68  MessageBuffer (MessageBuffer &&) = default;
69  MessageBuffer &operator= (MessageBuffer &&) = default;
70
71public:
72  ///
73  /// Finalize a buffer to be written.  No more lines can be added to
74  /// the buffer.  Use before a sequence of Write calls.
75  void PrepareToWrite ()
76  {
77    buffer.push_back (u8"\n"[0]);
78    lastBol = 0;
79  }
80  ///
81  /// Prepare a buffer for reading.  Use before a sequence of Read calls.
82  void PrepareToRead ()
83  {
84    buffer.clear ();
85    lastBol = 0;
86  }
87
88public:
89  /// Begin a message line.  Use before a sequence of Append and
90  /// related calls.
91  void BeginLine ();
92  /// End a message line.  Use after a sequence of Append and related calls.
93  void EndLine () {}
94
95public:
96  /// Append a string to the current line.  No whitespace is prepended
97  /// or appended.
98  ///
99  /// @param str the string to be written
100  /// @param maybe_quote indicate if there's a possibility the string
101  /// contains characters that need quoting.  Defaults to false.
102  /// It is always safe to set
103  /// this true, but that causes an additional scan of the string.
104  /// @param len The length of the string.  If not specified, strlen
105  /// is used to find the length.
106  void Append (char const *str, bool maybe_quote = false,
107	       size_t len = ~size_t (0));
108
109  ///
110  /// Add whitespace word separator.  Multiple adjacent whitespace is fine.
111  void Space ()
112  {
113    Append (Detail::S2C(u8" "));
114  }
115
116public:
117  /// Add a word as with Append, but prefixing whitespace to make a
118  /// separate word
119  void AppendWord (char const *str, bool maybe_quote = false,
120		   size_t len = ~size_t (0))
121  {
122    if (buffer.size () != lastBol)
123      Space ();
124    Append (str, maybe_quote, len);
125  }
126  /// Add a word as with AppendWord
127  /// @param str the string to append
128  /// @param maybe_quote string might need quoting, as for Append
129  void AppendWord (std::string const &str, bool maybe_quote = false)
130  {
131    AppendWord (str.data (), maybe_quote, str.size ());
132  }
133  ///
134  /// Add an integral value, prepending a space.
135  void AppendInteger (unsigned u);
136
137private:
138  /// Append a literal character.
139  /// @param c character to append
140  void Append (char c);
141
142public:
143  /// Lex the next input line into a vector of words.
144  /// @param words filled with a vector of lexed strings
145  /// @result 0 if no errors, an errno value on lexxing error such as
146  /// there being no next line (ENOENT), or malformed quoting (EINVAL)
147  int Lex (std::vector<std::string> &words);
148
149public:
150  /// Append the most-recently lexxed line to a string.  May be useful
151  /// in error messages.  The unparsed line is appended -- before any
152  /// unquoting.
153  /// If we had c++17 string_view, we'd simply return a view of the
154  /// line, and leave it to the caller to do any concatenation.
155  /// @param l string to-which the lexxed line is appended.
156  void LexedLine (std::string &l);
157
158public:
159  /// Detect if we have reached the end of the input buffer.
160  /// I.e. there are no more lines to Lex
161  /// @result True if at end
162  bool IsAtEnd () const
163  {
164    return lastBol == buffer.size ();
165  }
166
167public:
168  /// Read from end point into a read buffer, as with read(2).  This will
169  /// not block , unless FD is blocking, and there is nothing
170  /// immediately available.
171  /// @param fd file descriptor to read from.  This may be a regular
172  /// file, pipe or socket.
173  /// @result on error returns errno.  If end of file occurs, returns
174  /// -1.  At end of message returns 0.  If there is more needed
175  /// returns EAGAIN (or possibly EINTR).  If the message is
176  /// malformed, returns EINVAL.
177  int Read (int fd) noexcept;
178
179public:
180  /// Write to an end point from a write buffer, as with write(2).  As
181  /// with Read, this will not usually block.
182  /// @param fd file descriptor to write to.  This may be a regular
183  /// file, pipe or socket.
184  /// @result on error returns errno.
185  /// At end of message returns 0.  If there is more to write
186  /// returns EAGAIN (or possibly EINTR).
187  int Write (int fd) noexcept;
188};
189
190///
191/// Request codes.  Perhaps this should be exposed?  These are likely
192/// useful to servers that queue requests.
193enum RequestCode
194{
195  RC_CONNECT,
196  RC_MODULE_REPO,
197  RC_MODULE_EXPORT,
198  RC_MODULE_IMPORT,
199  RC_MODULE_COMPILED,
200  RC_INCLUDE_TRANSLATE,
201  RC_HWM
202};
203
204/// Internal file descriptor tuple.  It's used as an anonymous union member.
205struct FD
206{
207  int from;	///< Read from this FD
208  int to;	///< Write to this FD
209};
210
211}
212
213// Flags for various requests
214enum class Flags : unsigned
215{
216  None,
217  NameOnly = 1<<0,  // Only querying for CMI names, not contents
218};
219
220inline Flags operator& (Flags a, Flags b)
221{
222  return Flags (unsigned (a) & unsigned (b));
223}
224inline Flags operator| (Flags a, Flags b)
225{
226  return Flags (unsigned (a) | unsigned (b));
227}
228
229///
230/// Response data for a request.  Returned by Client's request calls,
231/// which return a single Packet.  When the connection is Corked, the
232/// Uncork call will return a vector of Packets.
233class Packet
234{
235public:
236  ///
237  /// Packet is a variant structure.  These are the possible content types.
238  enum Category { INTEGER, STRING, VECTOR};
239
240private:
241  // std:variant is a C++17 thing, so we're doing this ourselves.
242  union
243  {
244    size_t integer;	///< Integral value
245    std::string string; ///< String value
246    std::vector<std::string> vector;  ///< Vector of string value
247  };
248  Category cat : 2;  ///< Discriminator
249
250private:
251  unsigned short code = 0;  ///< Packet type
252  unsigned short request = 0;
253
254public:
255  Packet (unsigned c, size_t i = 0)
256    : integer (i), cat (INTEGER), code (c)
257  {
258  }
259  Packet (unsigned c, std::string &&s)
260    : string (std::move (s)), cat (STRING), code (c)
261  {
262  }
263  Packet (unsigned c, std::string const &s)
264    : string (s), cat (STRING), code (c)
265  {
266  }
267  Packet (unsigned c, std::vector<std::string> &&v)
268    : vector (std::move (v)), cat (VECTOR), code (c)
269  {
270  }
271  // No non-move constructor from a vector.  You should not be doing
272  // that.
273
274  // Only move constructor and move assignment
275  Packet (Packet &&t)
276  {
277    Create (std::move (t));
278  }
279  Packet &operator= (Packet &&t)
280  {
281    Destroy ();
282    Create (std::move (t));
283
284    return *this;
285  }
286  ~Packet ()
287  {
288    Destroy ();
289  }
290
291private:
292  ///
293  /// Variant move creation from another packet
294  void Create (Packet &&t);
295  ///
296  /// Variant destruction
297  void Destroy ();
298
299public:
300  ///
301  /// Return the packet type
302  unsigned GetCode () const
303  {
304    return code;
305  }
306  ///
307  /// Return the packet type
308  unsigned GetRequest () const
309  {
310    return request;
311  }
312  void SetRequest (unsigned r)
313  {
314    request = r;
315  }
316  ///
317  /// Return the category of the packet's payload
318  Category GetCategory () const
319  {
320    return cat;
321  }
322
323public:
324  ///
325  /// Return an integral payload.  Undefined if the category is not INTEGER
326  size_t GetInteger () const
327  {
328    return integer;
329  }
330  ///
331  /// Return (a reference to) a string payload.  Undefined if the
332  /// category is not STRING
333  std::string const &GetString () const
334  {
335    return string;
336  }
337  std::string &GetString ()
338  {
339    return string;
340  }
341  ///
342  /// Return (a reference to) a constant vector of strings payload.
343  /// Undefined if the category is not VECTOR
344  std::vector<std::string> const &GetVector () const
345  {
346    return vector;
347  }
348  ///
349  /// Return (a reference to) a non-conatant vector of strings payload.
350  /// Undefined if the category is not VECTOR
351  std::vector<std::string> &GetVector ()
352  {
353    return vector;
354  }
355};
356
357class Server;
358
359///
360/// Client-side (compiler) object.
361class Client
362{
363public:
364  /// Response packet codes
365  enum PacketCode
366  {
367    PC_CORKED,		///< Messages are corked
368    PC_CONNECT,		///< Packet is integer version
369    PC_ERROR,		///< Packet is error string
370    PC_OK,
371    PC_BOOL,
372    PC_PATHNAME
373  };
374
375private:
376  Detail::MessageBuffer write; ///< Outgoing write buffer
377  Detail::MessageBuffer read;  ///< Incoming read buffer
378  std::string corked; ///< Queued request tags
379  union
380  {
381    Detail::FD fd;   ///< FDs connecting to server
382    Server *server;  ///< Directly connected server
383  };
384  bool is_direct = false;  ///< Discriminator
385  bool is_connected = false;  /// Connection handshake succesful
386
387private:
388  Client ();
389public:
390  /// Direct connection constructor.
391  /// @param s Server to directly connect
392  Client (Server *s)
393    : Client ()
394  {
395    is_direct = true;
396    server = s;
397  }
398  /// Communication connection constructor
399  /// @param from file descriptor to read from
400  /// @param to file descriptor to write to, defaults to from
401  Client (int from, int to = -1)
402    : Client ()
403  {
404    fd.from = from;
405    fd.to = to < 0 ? from : to;
406  }
407  ~Client ();
408  // We have to provide our own move variants, because of the variant member.
409  Client (Client &&);
410  Client &operator= (Client &&);
411
412public:
413  ///
414  /// Direct connection predicate
415  bool IsDirect () const
416  {
417    return is_direct;
418  }
419  ///
420  /// Successful handshake predicate
421  bool IsConnected () const
422  {
423    return is_connected;
424  }
425
426public:
427  ///
428  /// Get the read FD
429  /// @result the FD to read from, -1 if a direct connection
430  int GetFDRead () const
431  {
432    return is_direct ? -1 : fd.from;
433  }
434  ///
435  /// Get the write FD
436  /// @result the FD to write to, -1 if a direct connection
437  int GetFDWrite () const
438  {
439    return is_direct ? -1 : fd.to;
440  }
441  ///
442  /// Get the directly-connected server
443  /// @result the server, or nullptr if a communication connection
444  Server *GetServer () const
445  {
446    return is_direct ? server : nullptr;
447  }
448
449public:
450  ///
451  /// Perform connection handshake.  All othe requests will result in
452  /// errors, until handshake is succesful.
453  /// @param agent compiler identification
454  /// @param ident compilation identifiation (maybe nullptr)
455  /// @param alen length of agent string, if known
456  /// @param ilen length of ident string, if known
457  /// @result packet indicating success (or deferrment) of the
458  /// connection, payload is optional flags
459  Packet Connect (char const *agent, char const *ident,
460		 size_t alen = ~size_t (0), size_t ilen = ~size_t (0));
461  /// std::string wrapper for connection
462  /// @param agent compiler identification
463  /// @param ident compilation identification
464  Packet Connect (std::string const &agent, std::string const &ident)
465  {
466    return Connect (agent.c_str (), ident.c_str (),
467		    agent.size (), ident.size ());
468  }
469
470public:
471  /// Request compiler module repository
472  /// @result packet indicating repo
473  Packet ModuleRepo ();
474
475public:
476  /// Inform of compilation of a named module interface or partition,
477  /// or a header unit
478  /// @param str module or header-unit
479  /// @param len name length, if known
480  /// @result CMI name (or deferrment/error)
481  Packet ModuleExport (char const *str, Flags flags, size_t len = ~size_t (0));
482
483  Packet ModuleExport (char const *str)
484  {
485    return ModuleExport (str, Flags::None, ~size_t (0));
486  }
487  Packet ModuleExport (std::string const &s, Flags flags = Flags::None)
488  {
489    return ModuleExport (s.c_str (), flags, s.size ());
490  }
491
492public:
493  /// Importation of a module, partition or header-unit
494  /// @param str module or header-unit
495  /// @param len name length, if known
496  /// @result CMI name (or deferrment/error)
497  Packet ModuleImport (char const *str, Flags flags, size_t len = ~size_t (0));
498
499  Packet ModuleImport (char const *str)
500  {
501    return ModuleImport (str, Flags::None, ~size_t (0));
502  }
503  Packet ModuleImport (std::string const &s, Flags flags = Flags::None)
504  {
505    return ModuleImport (s.c_str (), flags, s.size ());
506  }
507
508public:
509  /// Successful compilation of a module interface, partition or
510  /// header-unit.  Must have been preceeded by a ModuleExport
511  /// request.
512  /// @param str module or header-unit
513  /// @param len name length, if known
514  /// @result  OK (or deferment/error)
515  Packet ModuleCompiled (char const *str, Flags flags, size_t len = ~size_t (0));
516
517  Packet ModuleCompiled (char const *str)
518  {
519    return ModuleCompiled (str, Flags::None, ~size_t (0));
520  }
521  Packet ModuleCompiled (std::string const &s, Flags flags = Flags::None)
522  {
523    return ModuleCompiled (s.c_str (), flags, s.size ());
524  }
525
526  /// Include translation query.
527  /// @param str header unit name
528  /// @param len name length, if known
529  /// @result  Packet indicating include translation boolean, or CMI
530  /// name (or deferment/error)
531  Packet IncludeTranslate (char const *str, Flags flags,
532			   size_t len = ~size_t (0));
533
534  Packet IncludeTranslate (char const *str)
535  {
536    return IncludeTranslate (str, Flags::None, ~size_t (0));
537  }
538  Packet IncludeTranslate (std::string const &s, Flags flags = Flags::None)
539  {
540    return IncludeTranslate (s.c_str (), flags, s.size ());
541  }
542
543public:
544  /// Cork the connection.  All requests are queued up.  Each request
545  /// call will return a PC_CORKED packet.
546  void Cork ();
547
548  /// Uncork the connection.  All queued requests are sent to the
549  /// server, and a block of responses waited for.
550  /// @result A vector of packets, containing the in-order responses to the
551  /// queued requests.
552  std::vector<Packet> Uncork ();
553  ///
554  /// Indicate corkedness of connection
555  bool IsCorked () const
556  {
557    return !corked.empty ();
558  }
559
560private:
561  Packet ProcessResponse (std::vector<std::string> &, unsigned code,
562			  bool isLast);
563  Packet MaybeRequest (unsigned code);
564  int CommunicateWithServer ();
565};
566
567/// This server-side class is used to resolve requests from one or
568/// more clients.  You are expected to derive from it and override the
569/// virtual functions it provides.  The connection resolver may return
570/// a different resolved object to service the remainder of the
571/// connection -- for instance depending on the compiler that is
572/// making the requests.
573class Resolver
574{
575public:
576  Resolver () = default;
577  virtual ~Resolver ();
578
579protected:
580  /// Mapping from a module or header-unit name to a CMI file name.
581  /// @param module module name
582  /// @result CMI name
583  virtual std::string GetCMIName (std::string const &module);
584
585  /// Return the CMI file suffix to use
586  /// @result CMI suffix, a statically allocated string
587  virtual char const *GetCMISuffix ();
588
589public:
590  /// When the requests of a directly-connected server are processed,
591  /// we may want to wait for the requests to complete (for instance a
592  /// set of subjobs).
593  /// @param s directly connected server.
594  virtual void WaitUntilReady (Server *s);
595
596public:
597  /// Provide an error response.
598  /// @param s the server to provide the response to.
599  /// @param msg the error message
600  virtual void ErrorResponse (Server *s, std::string &&msg);
601
602public:
603  /// Connection handshake.  Provide response to server and return new
604  /// (or current) resolver, or nullptr.
605  /// @param s server to provide response to
606  /// @param version the client's version number
607  /// @param agent the client agent (compiler identification)
608  /// @param ident the compilation identification (may be empty)
609  /// @result nullptr in the case of an error.  An error response will
610  /// be sent.  If handing off to another resolver, return that,
611  /// otherwise this
612  virtual Resolver *ConnectRequest (Server *s, unsigned version,
613				    std::string &agent, std::string &ident);
614
615public:
616  // return 0 on ok, ERRNO on failure, -1 on unspecific error
617  virtual int ModuleRepoRequest (Server *s);
618
619  virtual int ModuleExportRequest (Server *s, Flags flags,
620				   std::string &module);
621  virtual int ModuleImportRequest (Server *s, Flags flags,
622				   std::string &module);
623  virtual int ModuleCompiledRequest (Server *s, Flags flags,
624				     std::string &module);
625  virtual int IncludeTranslateRequest (Server *s, Flags flags,
626				       std::string &include);
627};
628
629
630/// This server-side (build system) class handles a single connection
631/// to a client.  It has 3 states, READING:accumulating a message
632/// block froma client, WRITING:writing a message block to a client
633/// and PROCESSING:resolving requests.  If the server does not spawn
634/// jobs to build needed artifacts, the PROCESSING state will be brief.
635class Server
636{
637public:
638  enum Direction
639  {
640    READING,  // Server is waiting for completion of a (set of)
641	      // requests from client.  The next state will be PROCESSING.
642    WRITING,  // Server is writing a (set of) responses to client.
643	      // The next state will be READING.
644    PROCESSING  // Server is processing client request(s).  The next
645		// state will be WRITING.
646  };
647
648private:
649  Detail::MessageBuffer write;
650  Detail::MessageBuffer read;
651  Resolver *resolver;
652  Detail::FD fd;
653  bool is_connected = false;
654  Direction direction : 2;
655
656public:
657  Server (Resolver *r);
658  Server (Resolver *r, int from, int to = -1)
659    : Server (r)
660  {
661    fd.from = from;
662    fd.to = to >= 0 ? to : from;
663  }
664  ~Server ();
665  Server (Server &&);
666  Server &operator= (Server &&);
667
668public:
669  bool IsConnected () const
670  {
671    return is_connected;
672  }
673
674public:
675  void SetDirection (Direction d)
676  {
677    direction = d;
678  }
679
680public:
681  Direction GetDirection () const
682  {
683    return direction;
684  }
685  int GetFDRead () const
686  {
687    return fd.from;
688  }
689  int GetFDWrite () const
690  {
691    return fd.to;
692  }
693  Resolver *GetResolver () const
694  {
695    return resolver;
696  }
697
698public:
699  /// Process requests from a directly-connected client.  This is a
700  /// small wrapper around ProcessRequests, with some buffer swapping
701  /// for communication.  It is expected that such processessing is
702  /// immediate.
703  /// @param from message block from client
704  /// @param to message block to client
705  void DirectProcess (Detail::MessageBuffer &from, Detail::MessageBuffer &to);
706
707public:
708  /// Process the messages queued in the read buffer.  We enter the
709  /// PROCESSING state, and each message line causes various resolver
710  /// methods to be called.  Once processed, the server may need to
711  /// wait for all the requests to be ready, or it may be able to
712  /// immediately write responses back.
713  void ProcessRequests ();
714
715public:
716  /// Accumulate an error response.
717  /// @param error the error message to encode
718  /// @param elen length of error, if known
719  void ErrorResponse (char const *error, size_t elen = ~size_t (0));
720  void ErrorResponse (std::string const &error)
721  {
722    ErrorResponse (error.data (), error.size ());
723  }
724
725  /// Accumulate an OK response
726  void OKResponse ();
727
728  /// Accumulate a boolean response
729  void BoolResponse (bool);
730
731  /// Accumulate a pathname response
732  /// @param path (may be nullptr, or empty)
733  /// @param rlen length, if known
734  void PathnameResponse (char const *path, size_t plen = ~size_t (0));
735  void PathnameResponse (std::string const &path)
736  {
737    PathnameResponse (path.data (), path.size ());
738  }
739
740public:
741  /// Accumulate a (successful) connection response
742  /// @param agent the server-side agent
743  /// @param alen agent length, if known
744  void ConnectResponse (char const *agent, size_t alen = ~size_t (0));
745  void ConnectResponse (std::string const &agent)
746  {
747    ConnectResponse (agent.data (), agent.size ());
748  }
749
750public:
751  /// Write message block to client.  Semantics as for
752  /// MessageBuffer::Write.
753  /// @result errno or completion (0).
754  int Write ()
755  {
756    return write.Write (fd.to);
757  }
758  /// Initialize for writing a message block.  All responses to the
759  /// incomping message block must be complete  Enters WRITING state.
760  void PrepareToWrite ()
761  {
762    write.PrepareToWrite ();
763    direction = WRITING;
764  }
765
766public:
767  /// Read message block from client.  Semantics as for
768  /// MessageBuffer::Read.
769  /// @result errno, eof (-1) or completion (0)
770  int Read ()
771  {
772    return read.Read (fd.from);
773  }
774  /// Initialize for reading a message block.  Enters READING state.
775  void PrepareToRead ()
776  {
777    read.PrepareToRead ();
778    direction = READING;
779  }
780};
781
782// Helper network stuff
783
784#if CODY_NETWORKING
785// Socket with specific address
786int OpenSocket (char const **, sockaddr const *sock, socklen_t len);
787int ListenSocket (char const **, sockaddr const *sock, socklen_t len,
788		  unsigned backlog);
789
790// Local domain socket (eg AF_UNIX)
791int OpenLocal (char const **, char const *name);
792int ListenLocal (char const **, char const *name, unsigned backlog = 0);
793
794// ipv6 socket
795int OpenInet6 (char const **e, char const *name, int port);
796int ListenInet6 (char const **, char const *name, int port,
797		 unsigned backlog = 0);
798#endif
799
800// FIXME: Mapping file utilities?
801
802}
803
804#endif // CODY_HH
805