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 (x: 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 (c: Detail::S2C(s: 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: str.data (), maybe_quote, len: 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 (t: std::move (t));
278 }
279 Packet &operator= (Packet &&t)
280 {
281 Destroy ();
282 Create (t: 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: agent.c_str (), ident: ident.c_str (),
467 alen: agent.size (), ilen: 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: Flags::None, len: ~size_t (0));
486 }
487 Packet ModuleExport (std::string const &s, Flags flags = Flags::None)
488 {
489 return ModuleExport (str: s.c_str (), flags, len: 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: Flags::None, len: ~size_t (0));
502 }
503 Packet ModuleImport (std::string const &s, Flags flags = Flags::None)
504 {
505 return ModuleImport (str: s.c_str (), flags, len: 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: Flags::None, len: ~size_t (0));
520 }
521 Packet ModuleCompiled (std::string const &s, Flags flags = Flags::None)
522 {
523 return ModuleCompiled (str: s.c_str (), flags, len: 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: Flags::None, len: ~size_t (0));
537 }
538 Packet IncludeTranslate (std::string const &s, Flags flags = Flags::None)
539 {
540 return IncludeTranslate (str: s.c_str (), flags, len: 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: error.data (), elen: 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: path.data (), plen: 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: agent.data (), alen: 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: 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: 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

source code of libcody/cody.hh