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

source code of libcody/cody.hh