Purpose

iPXE-style interfaces were created to fulfil the need of transmitting and transforming messages. For example, an http_connection interface would send data delivery messages to a tcp_socket interface, which can send some other data delivery messages in return later on.

Implementation

Interfaces both act as a message handlers and carrier: The http interface would pipe any message to the tcp interface, even though it may itself handle messages. It's done this way because a source object does not need to send messages to iself (it can just directly perform the job). Each interface can be seen as a unidirectional message pipe 1). Two interfaces may be their mutual destination (the dhcp interface would send messages to the udp socket, which would send messages back).

Messages are strongly typed, and are identified by the address of the utility function sending one through an interface. Typing is enforced using a mymessage_TYPE macro, which builds a function pointer for the appropriate object type.

Interfaces are always bound to an object, which contains arbitrary data. Reciprocally, an object may contain multiple interfaces. Objets are structures, and interfaces are fields of type struct interface

Each interface contains a list of message handlers operating on the object 2).

As an additional feature, when an interface does not implement a handler for some message type, it may know another interface from the same object (called the passthrough interface), used as a carrier to another object's interface, which will hopefuly handle the message (if not, the process may be repeated until there is no passthrough interface).

This can be used to implement message transparent message filters.

Example

struct xor_filter {
       /* ... */
       struct interface xfer;
       struct interface backend;
       uint8_t xor_constant;
};

static struct interface_operation xor_xfer_operations[] = {
        INTF_OP(xfer_deliver,  /* ... */  xor_filter_deliver),
        /* ... */
};

static struct interface_descriptor xor_xfer_desc =
        INTF_DESC_PASSTHRU(struct xor_filter *, xfer,
                           xor_xfer_operations, backend);

static struct interface_operation xor_backend_operations[] = {
        /* ... */
};

static struct interface_descriptor xor_backend_desc =
        INTF_DESC_PASSTHRU(struct xor_filter *, backend,
                           xor_backend_operations, xfer);

static int xor_filter_deliver(struct xor_filter *xfilter,
                              struct io_buffer *io_buf,
                              struct xfer_metadata *meta) {
        /* ... */
        xfer_deliver(&xfer->backend, io_buf, meta);
}

int xor_filter(struct interface *xfer, struct interface *backend, uint8_t c) {
        struct xor_filter *xfilter = zalloc(sizeof (*xfilter));
        if (!xfilter)
                return -ENOMEM;

        /* ... */

        intf_init(&xfilter->xfer, &xor_xfer_desc /* ... */);
        intf_init(&xfilter->backend, &xor_backend_desc /* ... */);

        xfilter->xor_constant = c;

        intf_plug_plug(xfer, &xfilter->xfer);
        intf_plug_plug(backend, &xfilter->backend);

        /* ... */

        return 0;
}

FAQ

  • In the previous example, the backend passes through unknown messages to xfer, and vice versa. Why doesn't it generate a loop ?

Because messages are transferred to the destination of the passthrough interface. If it weren't the case, interfaces would be useless, as the original interface receiving the message would have the same ability to handle the message as the one it forwards the message to, as they both are from the same object.

  • How is memory managed ? I didn't see any reference counting.

For the sake of clarity, any reference to reference counting was trimmed off this example. However, actual objects do include proper reference counting: any interface holds a bound reference counter 3), and each time an interface is plugged into another, the reference count of the destination is incremented.

In order to handle deallocating networks containing reference loops, any object from the network may send an intf_close message to its neighbors, which will disconnect all its interfaces and send the same message to all its neighbors.

  • Where can I find some more examples ?

src/net/udp/ntp.c src/net/udp/syslog.c src/net/tcp/syslogs.c src/core/pinger.c

References

src/include/ipxe/interface.h src/core/interface.c

1)
Messages aren't related to networking, but can rather be seen as function calls. Transmitting a message means dispatching the function call.
2)
Each interface contains an offset to the beginning of the object, which is used to retrieve its address when calling a method
3)
most likely the one of the object containing it
appnote/interfaces.txt ยท Last modified: 2019/05/15 10:11 by mcb30
Recent changes RSS feed CC Attribution-Share Alike 4.0 International Driven by DokuWiki
All uses of this content must include an attribution to the iPXE project and the URL https://ipxe.org
References to "iPXE" may not be altered or removed.