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