LIBPKG(3)

πŸ“¦
version 7.44.0 Β· 2 versions
, in nixpkgs unstable

Okay bestie, this is LIBPKG(3) from the Library Functions Manual β€” the manual page for libpkg β€” and it's part 1 of 3, so buckle up πŸ’–

πŸ’– NAME πŸ’–

libpkg β€” a message passing abstraction library that supports communication between cooperating processes over TCP connections ✨

🌸 BACKGROUND 🌸

So the lore is this: the description of the libpkg routines was kindly provided by Peter B. Shames on 26 Sept 1986, and then updated on 14 Nov 1986 by PC Dykstra to reflect the BRL extensions that had landed since the first writing 🫢. The plan was apparently to publish an RFC at some point, but at this point this document (plus the source code) is literally all we have 😭. In the CAD distribution, the remote framebuffer interface (libfb/if_remote.c) and the server (fbserv) communicate using this protocol β€” iykyk πŸ’….

✨ OVERVIEW ✨

A prototype interface has been developed by Mike Muuss, Charles Kennedy, and Phillip Dykstra at BRL that implements a fairly simple message passing abstraction between cooperating processes running over TCP connections 🌈. Some experiments have been run at STScI to implement this interface on a DECnet link with good success β€” slay ✨. A few changes have been suggested to improve network throughput for data flows that consist of short message traffic or byte-stream traffic πŸ‘€.

This note describes the motivation for developing such an interface and describes the subroutine interfaces and functionality πŸ’–. The interface routine descriptions and portions of the textual descriptions have been taken directly from the source code provided by Muuss and Kennedy, which has been the only available description to date β€” no cap, that's the whole archive vibe 🫢.

πŸ¦‹ WHY A MESSAGE INTERFACE? πŸ¦‹

The interface β€” PKG β€” provides a simple interface for managing client/server connections in a distributed environment πŸ‘‘. A server task is initialized to wait for incoming requests from clients. Clients open a connection to the server task, which executes on some host processor waiting for incoming requests. Messages with identifying type codes are passed between processes, with optional appended data blocks. The interface is non-blocking and accepts arbitrary data messages of arbitrary length β€” literally arbitrary length, bestie πŸ’….

Here's an example: interaction with a remote data archive may require a request for services, user authentication, permission granting, index hierarchy queries β€” with manipulation of results, archive request processing, delivery of output products β€” electronically or otherwise πŸ’–. The archive is a server that responds to service requests from clients; how many may be served at one time and whether public access and private services are supported is an implementation issue. Since no direct interactive support is required (if smart client agents are in use), the message paradigm can support the requirements ✨.

A [counter] example: Athena's XWindow uses a client-server model to provide screen/window management services for distributed processes 🌸. XWindow is very permissive of different window management policies and provides hooks for implementing various user interfaces. At the lowest level, XWindow requires a reliable duplex byte-stream, which can be implemented in many operating environments. Because XWindow wants to manage all process-window communications directly, it builds its own block stream protocol to manage the connection. This permits the interactive update required by some processes, but basically is the same paradigm as PKG, defined within the window system πŸ‘€.

πŸ‘‘ PKG INTERFACE DESCRIPTION πŸ‘‘

The PKG interface implements a client-server network connection that multiplexes synchronous and asynchronous messages across stream connections πŸ’«. Connection control is provided by open/close routines, where the client authentication is handled prior to establishing the connection πŸ”. Server side actions include initialization, client connection, and client request servicing. Client side actions include server request, reply processing, possible connection negotiation, and data transfer processing. So both sides have stuff to do β€” it's giving full-stack but make it 1986 πŸ’….

A connection is established when a client issues an open to a server, running on some host. Which hosts provide required services must be determined prior to connection requests, using some service directory β€” yes, you gotta know where to knock before you knock 🫢. Default connection processing is stream oriented, where a process may get data as it is available on the connection. A blocking read may also be issued, waiting for any complete message, or just for messages of a specified type. The connection remains open until explicitly closed by either client or server. No ghosting this connection, okay? 😭

Once the connection is established it may be used to process a full-duplex asynchronous byte stream, or a buffered, synchronous query/reply exchange πŸ’–. Message type multiplexing supports user actions based upon message type, where the multiplexing is handled in the interface. Different message type streams may operate using different flow control over the same connection. Multiplexing is handled in the interface by pre-pending a header block to the message before transmitting it to the reader. Data is transmitted as received, but the header is transmitted in Internet network byte/bit order β€” that detail matters, bestie ⭐.

A package connection may incur significant overhead if many short messages are passed between processes, so a buffered stream connection may be requested πŸ‘€. The application calls the pkg_stream interface rather than the usual pkg_send request, and PKG builds a message buffer and passes it to the client as it gets filled. A stream buffer may be flushed with an explicit call to the pkg_flush routine β€” flush it, slay πŸ’….

πŸŽ€ PKG INTERFACE DEFINITION πŸŽ€

okay bestie this is the actual menu of everything the interface gives you. Three flavor groups: control, I/O, and the handler stuff. Buckle up πŸ’…


✨ Control Interfaces ✨

like, the "who are you and are we connected" girlies πŸ¦‹

PKG_OPEN β€” Open a connection to a host

pkg_open( host, service, protocol, uname, passwd, pkg_switch, errlog )
returns ptr_to_connection

so we are the client here β€” we're the one reaching out, making a connection to a server running on some host. the return value is literally the connection handle, that's your new bestie for the rest of the convo 🫢

protocol, username, and password are all optional, no pressure.

PKG_CLOSE β€” Close a connection

pkg_close( ptr_to_connection )
returns VOID

this is when either side β€” client or server β€” wants to gracefully close a connection. no drama, just a clean goodbye πŸ‘‹

PKG_TRANSERVER β€” Become a one-time network server on the already accepted connection

pkg_transerver( pkg_switch, errlog )
returns ptr_to_connection

okay so picture this: you already have an established connection, and now you want to turn it into a server situation. this routine creates a pkg_conn structure for it and initializes it to use the given pkg_switch and error logging function. 🧚

it's needed by servers that get created by some other process after the connection is already established. the classic example? processes started by the UNIX "inetd". iconic honestly.

PKG_PERMSERVER β€” Create a network server and listen for connections

pkg_permserver( service, protocol, backlog )
returns listen_file_desc

the service port gets determined, and a listen is hung on the port that's bound to the socket. the return value is the file descriptor of the socket. ⭐

for this kind of server, you use pkg_getclient to accept connections from clients β€” that's the next one, keep reading bestie.

PKG_GETCLIENT β€” Used by a server to accept a client connection

pkg_getclient( listen_file_desc, pkg_switch, errlog, nodelay_flag )
returns ptr_to_connection or NULL or ERROR

if possible, the connection request from the client gets accepted, a pkg_connection block is created, and a ptr to it is handed back to the server. πŸ’–

the server can request non-blocked service by setting the nodelay_flag to TRUE. slay, no waiting around.


🌸 I/O Interfaces 🌸

the actual "sending and receiving bytes" part, aka the main character energy of this whole page πŸ‘‘

PKG_GET β€” Read bytes from connection, assembling message

pkg_get( ptr_to_connection ) returns
returns message_arrived, more_data_coming or ERROR.

get waits until a message header is received, and then calls the user-specified message handler for that message type. πŸ¦‹

your program should call this routine whenever there's data waiting on a connection and the program isn't otherwise waiting on a specific message type. it returns directly to the caller if no header is available, or if only a partial message came in β€” otherwise it calls the user-specified message type handler. ngl that's kind of the whole vibe.

PKG_SEND β€” Send a message on the connection

pkg_send( message_type, data_buff, buff_len, ptr_to_connection )
returns bytes_sent or ERROR

send constructs a message header for the data buffer and transmits it on the connection. if only part of the message can be sent, the actual number of bytes transmitted is what comes back to you. lowkey important, don't ignore it.

any data in the stream buffer gets flushed first. 🫧 and here's the cute trick: if the stream buffer needs flushing, and the message is less than MAXQLEN (currently 512) bytes long, and there's sufficient room left in the stream buffer β€” this message gets "piggybacked" on by copying it to the buffer before flushing. efficient queen.

and if available, "writev" is used to send the header and data with one syscall. πŸ”₯

PKG_2SEND β€” Send a two part message on the connection

pkg_2send( message_type, data_buff1, buff1_len, data_buff2, buff2_len, ptr_to_connection )
returns bytes_sent or ERROR

2send does the exact same job as pkg_send except the data arrives in two parts. this happens a lot when it's, like, a command followed by user data. ✨

the point? user data copies can be avoided while still allowing a single trip in and out of the kernel on the receiving end. we love a shortcut that's also correct πŸ’…

PKG_STREAM β€” Send a buffered data stream on the connection

pkg_stream( message_type, data_buff, buff_len, ptr_to_connection )
returns bytes_sent or ERROR

Pkg_stream connections give you a low network overhead connection β€” the tradeoff being that interactive responsiveness isn't required. think "batch mode, not chat mode" 🌈

connections have a PKG_STREAMLEN (currently 4096) byte stream buffer. if the current message is less than MAXQLEN (currently 512) bytes in length, and space remains for it, it gets appended onto the end of that buffer. otherwise the buffer is implicitly flushed by sending this message via PKG_SEND. πŸ“

PKG_FLUSH β€” Flush the stream buffer out on the connection

pkg_flush( ptr_to_connection )
returns bytes_sent or ERROR

flush takes the current contents of the stream buffer that stream is building up and flushes it out to the connection. this gives programs explicit control over the stream interface where it's required. πŸŽ€

PKG_BLOCK β€” Wait for a complete message of any type

pkg_block( ptr_to_connection )
returns message_arrived or ERROR

this routine blocks, waiting for a complete message of any type to arrive on the connection. then the user message handler gets called to process it. πŸ’€

you can call this in a loop when you're waiting for asynchronous messages, or for the arrival of messages whose type you're not sure about. iykyk.

PKG_WAITFOR β€” Wait for a message of specific type, return in user buffer

pkg_waitfor( message_type, user_buff, buff_len, ptr_to_connection )
returns buff_len or ERROR

this one does a blocking read on the connection until a message of message_type is received. while it waits, asynchronous messages and messages of other types get processed. the message itself comes back in the user's buffer. 🫢

PKG_BWAITFOR β€” Wait for a message of specific type, return in allocated buffer

pkg_bwaitfor( message_type, ptr_to_connection )
returns ptr_to_buffer or ERROR

same blocking-read deal as before β€” it waits on the connection until a message of message_type shows up, processing asynchronous messages and other types in the meantime. 🌸

the difference? the message comes back in a newly allocated buffer that the caller must free. yes, that's on you. don't forget or it's a leak bestie 😭


🧚 User Message Handler Interface 🧚

the part where you get to define what happens when a message lands πŸŽ€

PKG_SWITCH β€” Table of User Message Handler Pointers

struct pkg_switch {
unsigned short  pks_type;       /* Type code */
int     (*pks_handler)();       /* Message Handler */
char    *pks_title;             /* Description */
};
pks_handler( ptr_to_connection, message_buff )
returns ignored int

the user may specify handlers for one or more message types. when these routines get called, they're passed a pointer to the connection and a pointer to a buffer containing the message. πŸ’–

heads up: the user message handler routine is responsible for freeing the message buffer after processing it. if there's no message handler for that message type, the message is discarded and the buffer is freed. πŸ™ˆ

the switch routine uses an external message handler array, created by the caller, that defines message types, handler entry points, and a message descriptor field.

and the pkg_switch list that gets passed to some of the functions above? it's a NULL terminated array of structures containing the message type, a pointer to a function which handles messages of that type, and a descriptive string. ⭐

a separate pkg_switch array is kept for each connection, so a single application can carry out several entirely different PKG conversations at the same time. multitalented behavior, we love to see it πŸ‘‘

PKG_CONN β€” Connection block structure

struct pkg_conn {
pkg_file_desc, pkg_magic, mess_len, ptr_to_buffer,
curr_buffer_pos, pkg_switch, errlog, stream_buff,
curr_stream_pos
};

connection block structures get created by pkg_open or pkg_getclient when a connection is established, and they're freed when the connection is closed. πŸ¦‹

this connection block gets used during all I/O requests, and it contains the pointer to the start of the current buffer and to the current position in the buffer for incomplete reads or writes. the mess_len field specifies the number of bytes expected by get. the pkg_switch array, plus a pointer to an error logging function for this connection, is included too. and the stream buffer lives in the structure as well. 🌈

see pkg.h for the actual names of the structure elements β€” don't come for me, that's just where they live πŸ’…

πŸ’€ IMPLEMENTATION NOTES πŸ’€

at least two implementations of the PKG interface have been done β€” one based on TCP/IP connections, the other based on DECnet connections. future releases of PKG will be layered on top of a virtual stream library known as NET. 🧚

the implementation running on 4.2BSD UNIX takes advantage of the "writev" syscall, which can output the header and data in a single syscall/TCP_PUSH cycle. πŸ”₯

ngl though β€” the advantage of the short data copy is realized only when writev can not be used, or when a pkg_flush is not required (a la PKG_STREAM). read that twice, it matters ✨


🌈 FUTURE DEVELOPMENTS 🌈

further development of the lower levels of the interface will be provided so that this message passing abstraction can be provided on top of both DECnet and TCP/IP network layers, with other implementations provided as required. πŸ’œ

the program interface layer is to be as described in the body of this note, and the lower layers will interface to the network layers using whatever subroutine interfaces are most appropriate for the system hosting the interface. a simple read/write/open/close abstraction may be provided at a lower layer to further isolate the network layers from the message handling layer. πŸŽ€

where it's necessary to cross network protocol boundaries, message relay processes will be created to reside on a Janus host that straddles the two domains. domain routing and relay point information will be maintained above the network layer within the extended PKG interface. ⭐

clients and servers will need to determine server host id information from some available database. server names must either carry domain information, or such information must be available as part of the server database. within the extended PKG interface, routing information for domain boundary transits must be maintained, and appropriate Janus host systems connected to provide relay services. πŸ¦‹

and here's the honest part: it is not anticipated that full asynchronous I/O implementation will work particularly well across domain boundaries 😭 β€” but all other forms of message transfer should work quite well. as already noted, buffered stream I/O may be perfectly satisfactory for many applications where full interaction isn't required. where applicable, this mode of interface is to be preferred over full asynchronous I/O. πŸ’…

further development of this interface layer on other protocol families is possible, perhaps even on low level networks like BITNET, and certainly on connection oriented X.25 or OSI networks. the range, honestly πŸ”₯


πŸ‘‘ AUTHOR πŸ‘‘

Peter B. Shames

πŸ› BUG REPORTS (yes, this is your moment) πŸ›

okay so bestie, if you spot anything in here that's wrong β€” like straight-up inaccurate, not just "hmm I'd have phrased it cuter" β€” you gotta report it. And the way you do that is electronic mail, sent to:

devs@brlcad.org

that's it. that's the whole vibe. reports of inaccuracies should be submitted via electronic mail to devs@brlcad.org πŸ’Œ

ngl, accuracy matters, so if something in this page lied to you, the devs genuinely wanna know. don't just suffer in silence with a wrong manpage, that's not the main character energy we're going for 🫢


and that's a wrap on LIBPKG(3)! ✨ you made it to the end, icon behavior honestly πŸ‘‘

just so we're keeping it πŸ’― to the very last line β€” this page is from BRL-CAD (that's the source/version situation, periodt) and its manual page reference is LIBPKG(3), section 3 and all πŸŽ€

now go forth and write some iconic messages, bestie πŸ’…πŸŒΈπŸ¦‹

πŸ“¦ where this page comes from