[PATCH v2 6/6] tests: hwsim: add hwsim relay code

Johannes Berg johannes at sipsolutions.net
Tue Oct 6 15:28:49 PDT 2026


On Sat, 2026-09-12 at 00:59 +0200, Johannes Berg wrote:
> 
> +class RelayLayer:
> +    """
> +    Base layer for frame interception - each layer has its own
> +    .intercept() like the HwsimRelay() itself.
> +
> +    Used as "async with XyzLayer(...) as layer:" just like
> +    the relay itself.
> +    """
> +
> +    def __init__(self, upstream):
> +        self._upstream = upstream
> +        self._downstream = self._default
> +
> +    def __enter__(self):
> +        self._prev = self._upstream.intercept
> +        self._upstream.intercept = self._run
> +        return self
> +
> +    def __exit__(self, exc_type, exc_value, traceback):
> +        self._upstream.intercept = self._prev
> +
> +    @property
> +    def intercept(self):
> +        return self._downstream
> +
> +    @intercept.setter
> +    def intercept(self, fn):
> +        self._downstream = fn
> +
> +    def _default(self, src, freq, frame):
> +        return frame
> +
> +    def _run(self, src, freq, frame):
> +        raise NotImplementedError
> +

...

OK I've been revisiting this (in part due to some bugs), and I'm really
not happy with the API .. it works, but it's messy, and difficult to
understand.

So I think I'm going to change it, so that you can either use the
intercept function here or install a layer later, but you have to
perfectly stack layers.

This supported kind of (temporary) forks in the frame flow graph here
and I thought that was more powerful (and it probably _is_), but I've
myself lost track of it a few times now, so ... no good.

So handwaving over the HwsimRelay changes, which will allow only the
intercept function _or_ a layer attached, we'll get:

class RelayLayer:
    """
    Base implementation for frame interception layers.
    Implement subclasses of this that have ._run().

    Used as

        with XyzLayer(prev, ...) as layer:
            ...

    where where prev is either another layer or the relay.
    """

    def __init__(self, upstream):
        assert isinstance(upstream, (RelayLayer, HwsimRelay))
        self._upstream = upstream
        self._next = None

    def __enter__(self):
        assert self._upstream._next is None
        self._upstream._next = self
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self._upstream._next = None

    def _call_next(self, src, freq, frame):
        if self._next:
            frame = self._next._run(src, freq, frame)
        return frame

    def _run(self, src, freq, frame):
        """
        Example:

        if should_drop_frame:
            return None

        # could modify / drop frame here

        frame = self._call_next(src, freq, frame)

        # could also modify / drop frame here, but
        # note it might have been dropped (be None)
        # by the next layer

        return frame
        """
        raise NotImplementedError


Yeah this way you can't build arbitrary layer graphs, but that's a
feature, not a bug ...

It also means the tests do things in the right order, i.e. layers
constructed first are passed through first:

  with Layer1(relay, ...) as l1, \
       Layer2(l1, ...) as l2:
    # in this block relay passes to l1, passes to l2

johannes



More information about the Hostap mailing list