NAME

Protocol::Imsg - the OpenBSD imsg(3) frame, as bytes

SYNOPSIS

use Protocol::Imsg;

my $codec = Protocol::Imsg->new;

my $bytes = $codec->encode(type => 8, data => $payload);

$codec->append($received_bytes);
while (my $msg = $codec->next_message) {
    # $msg->{type}, $msg->{peerid}, $msg->{pid}, $msg->{data}
}

DESCRIPTION

This module frames and unframes messages of the OpenBSD imsg(3) facility. It takes bytes and returns bytes: it performs no system call, it opens no file, and it names no socket. Fugu::Imsg owns the socket and uses this module for the frame.

State three limits before anything else, because each one bounds where this module is usable.

The header is native-endian, and the format never crosses a host.

Both ends of an imsg connection are the same machine, so imsg writes the header in the sender's own byte order. There is no byte-order negotiation and no network-order variant. Two hosts that disagree on byte order cannot exchange these frames.

The module frames without descriptor passing.

Native imsg can pass a file descriptor as SCM_RIGHTS ancillary data, on a message whose len carries IMSG_FD_MARK. This module masks that mark off and never sets it, so it reads such a message as a plain frame and it can never send one. A caller that needs descriptor passing needs recvmsg and sendmsg as well, which this codec has no way to reach.

encode reads $$ on every call.

Native imsg seeds the pid once, when the buffer is initialized. This module reads it per call instead. The two agree in one process and diverge in a forked child: the child's frames carry the child's pid where native imsg would still carry the parent's. Pass pid explicitly when that matters.

The wire format is in spec/MDNS-Imsg.md in this repository. That document is a curated reference, not an installed manual.

CONSTANTS

HEADER_SIZE

16, the size of struct imsg_hdr.

HEADER_TEMPLATE

The pack template of the header: L4, four native uint32 fields in the order type, len, peerid, pid.

MAX_IMSGSIZE

16384, the bound on the whole message.

MAX_PAYLOAD

16368, which is MAX_IMSGSIZE less the header.

FD_MARK

0x80000000, the reserved high bit of len.

METHODS

new()

Return a codec with an empty decode buffer.

encode(%args)

Return the framed bytes of one message.

type

The message type. Required.

data

The payload. The default is the empty string.

peerid

A correlation value that is opaque to this module. The default is 0, which is what a protocol that does not correlate sends.

pid

The sender pid. The default is $$. A pid of 0 also gives $$, because imsg substitutes the sender's pid for a 0 the caller passes.

The method returns undef, with $! set to EMSGSIZE, for a payload above MAX_PAYLOAD. An encoder must refuse a larger payload rather than truncate it.

append($bytes)

Add received bytes to the decode buffer and return the object. A partial header, a partial payload, and several whole messages at once are all normal.

next_message()

Pop one whole message off the buffer and return a hash reference with type, peerid, pid, and data. Return undef when more bytes are necessary.

An invalid len sets $! to EBADMSG and makes the failure permanent: native imsg drops such a connection, and so must every reader. len is invalid below HEADER_SIZE and above MAX_IMSGSIZE.

is_failed()

Report the permanent framing failure. Nothing clears it but reset.

reset()

Empty the buffer, clear the failure, and return the object. A transport calls it when it closes a connection, so that a frame which arrived before the close cannot come out after it.

SEE ALSO

Fugu::Imsg, spec/MDNS-Imsg.md