Skip to main content

netstack3_device/
loopback.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! The loopback device.
6
7use alloc::vec::Vec;
8use core::convert::Infallible as Never;
9use core::fmt::Debug;
10use derivative::Derivative;
11
12use lock_order::lock::{OrderedLockAccess, OrderedLockRef};
13use log::trace;
14use net_types::ethernet::Mac;
15use net_types::ip::{Ipv4, Ipv6, Mtu};
16use netstack3_base::sync::Mutex;
17use netstack3_base::{
18    AnyDevice, BroadcastIpExt, ChecksumOffloadSpec, ChecksumRxOffloading, CoreTimerContext, Device,
19    DeviceIdAnyCompatContext, DeviceIdContext, FrameDestination, NetworkParsingContext,
20    NetworkSerializer, RecvFrameContext, RecvIpFrameMeta, ResourceCounterContext, SendFrameError,
21    SendFrameErrorReason, SendableFrameMeta, StrongDeviceIdentifier, TimerContext,
22    TxMetadataBindingsTypes, WeakDeviceIdentifier,
23};
24use netstack3_ip::{DeviceIpLayerMetadata, IpCounters, IpPacketDestination};
25use packet::{Buf, Buffer as _, BufferMut, FragmentedBuffer as _, NestablePacketBuilder as _};
26use packet_formats::ethernet::{
27    EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, EthernetIpExt,
28};
29
30use crate::internal::base::{
31    DeviceCounters, DeviceLayerTypes, DeviceReceiveFrameSpec, EthernetDeviceCounters,
32};
33use crate::internal::id::{BaseDeviceId, BasePrimaryDeviceId, BaseWeakDeviceId, WeakDeviceId};
34use crate::internal::queue::rx::{
35    ReceiveDequeFrameContext, ReceiveQueue, ReceiveQueueState, ReceiveQueueTypes,
36};
37use crate::internal::queue::tx::{
38    BufVecU8Allocator, TransmitQueue, TransmitQueueHandler, TransmitQueueState,
39    TxQueuePacketMetadataCommon,
40};
41use crate::internal::queue::{DequeueState, DeviceBufferSpec, TransmitQueueFrameError};
42use crate::internal::socket::{
43    DeviceSocketHandler, DeviceSocketMetadata, DeviceSocketSendTypes, EthernetHeaderParams,
44    ReceivedFrame,
45};
46use crate::internal::state::{DeviceStateSpec, IpLinkDeviceState};
47
48/// The MAC address corresponding to the loopback interface.
49const LOOPBACK_MAC: Mac = Mac::UNSPECIFIED;
50
51/// A weak device ID identifying a loopback device.
52///
53/// This device ID is like [`WeakDeviceId`] but specifically for loopback
54/// devices.
55///
56/// [`WeakDeviceId`]: crate::device::WeakDeviceId
57pub type LoopbackWeakDeviceId<BT> = BaseWeakDeviceId<LoopbackDevice, BT>;
58
59/// A strong device ID identifying a loopback device.
60///
61/// This device ID is like [`DeviceId`] but specifically for loopback devices.
62///
63/// [`DeviceId`]: crate::device::DeviceId
64pub type LoopbackDeviceId<BT> = BaseDeviceId<LoopbackDevice, BT>;
65
66/// The primary reference for a loopback device.
67pub type LoopbackPrimaryDeviceId<BT> = BasePrimaryDeviceId<LoopbackDevice, BT>;
68
69/// Loopback device domain.
70#[derive(Copy, Clone)]
71pub enum LoopbackDevice {}
72
73impl Device for LoopbackDevice {}
74
75impl<BT> DeviceBufferSpec<BT> for LoopbackDevice {
76    type TxBuffer = Buf<Vec<u8>>;
77    type TxAllocator = BufVecU8Allocator;
78}
79
80impl DeviceStateSpec for LoopbackDevice {
81    type State<BT: DeviceLayerTypes> = LoopbackDeviceState<WeakDeviceId<BT>, BT>;
82    type External<BT: DeviceLayerTypes> = BT::LoopbackDeviceState;
83    type CreationProperties = LoopbackCreationProperties;
84    type Counters = EthernetDeviceCounters;
85    type TimerId<D: WeakDeviceIdentifier> = Never;
86
87    fn new_device_state<
88        CC: CoreTimerContext<Self::TimerId<CC::WeakDeviceId>, BC> + DeviceIdContext<Self>,
89        BC: DeviceLayerTypes + TimerContext,
90    >(
91        _bindings_ctx: &mut BC,
92        _self_id: CC::WeakDeviceId,
93        LoopbackCreationProperties { mtu }: Self::CreationProperties,
94        tx_allocator: <Self as DeviceBufferSpec<BC>>::TxAllocator,
95    ) -> Self::State<BC>
96    where
97        Self: DeviceBufferSpec<BC>,
98    {
99        LoopbackDeviceState {
100            counters: Default::default(),
101            mtu,
102            rx_queue: Default::default(),
103            tx_queue: TransmitQueue::new(tx_allocator, ChecksumOffloadSpec::generic()),
104        }
105    }
106
107    const IS_LOOPBACK: bool = true;
108    const DEBUG_TYPE: &'static str = "Loopback";
109
110    fn tx_offload_spec<BT: DeviceLayerTypes>(
111        state: &Self::State<BT>,
112    ) -> Option<ChecksumOffloadSpec> {
113        Some(state.tx_queue.tx_offload_spec())
114    }
115}
116
117/// Properties used to create a loopback device.
118#[derive(Debug)]
119pub struct LoopbackCreationProperties {
120    /// The device's MTU.
121    pub mtu: Mtu,
122}
123
124/// State for a loopback device.
125pub struct LoopbackDeviceState<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> {
126    /// Loopback device counters.
127    pub counters: EthernetDeviceCounters,
128    /// The MTU this device was created with (immutable).
129    pub mtu: Mtu,
130    /// Loopback device receive queue.
131    pub rx_queue: ReceiveQueue<LoopbackRxQueueMeta<D, BT>, Buf<Vec<u8>>>,
132    /// Loopback device transmit queue.
133    pub tx_queue: TransmitQueue<
134        LoopbackTxQueueMeta<D, BT>,
135        <LoopbackDevice as DeviceBufferSpec<BT>>::TxBuffer,
136        <LoopbackDevice as DeviceBufferSpec<BT>>::TxAllocator,
137    >,
138}
139
140#[derive(Derivative)]
141#[derivative(Default(bound = ""))]
142/// Metadata associated with a frame in the Loopback TX queue.
143pub struct LoopbackTxQueueMeta<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> {
144    /// Device that should be used to deliver the packet. If not set then the
145    /// packet delivered as if it came from the loopback device.
146    target_device: Option<D>,
147    /// Metadata that is produced and consumed by the IP layer but which traverses
148    /// the device layer through the loopback device.
149    ip_layer_metadata: DeviceIpLayerMetadata<BT>,
150}
151
152impl<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> TxQueuePacketMetadataCommon
153    for LoopbackTxQueueMeta<D, BT>
154{
155    fn set_checksum_offload_result(
156        &mut self,
157        _result: Option<netstack3_base::ChecksumOffloadResult>,
158    ) {
159        // Loopback doesn't need checksum offload result because it skips
160        // verification on receive.
161    }
162}
163
164/// Metadata associated with a frame in the Loopback RX queue.
165#[derive(Derivative)]
166#[derivative(Debug(bound = ""))]
167pub struct LoopbackRxQueueMeta<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> {
168    /// Device that should be used to deliver the packet. If not set then the
169    /// packet delivered as if it came from the loopback device.
170    target_device: Option<D>,
171    /// Metadata that is produced and consumed by the IP layer but which traverses
172    /// the device layer through the loopback device.
173    ip_layer_metadata: DeviceIpLayerMetadata<BT>,
174}
175
176impl<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> From<LoopbackTxQueueMeta<D, BT>>
177    for LoopbackRxQueueMeta<D, BT>
178{
179    fn from(
180        LoopbackTxQueueMeta { target_device, ip_layer_metadata }: LoopbackTxQueueMeta<D, BT>,
181    ) -> Self {
182        Self { target_device, ip_layer_metadata }
183    }
184}
185
186impl<BT: DeviceLayerTypes>
187    OrderedLockAccess<ReceiveQueueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>
188    for IpLinkDeviceState<LoopbackDevice, BT>
189{
190    type Lock = Mutex<ReceiveQueueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>;
191    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
192        OrderedLockRef::new(&self.link.rx_queue.queue)
193    }
194}
195
196impl<BT: DeviceLayerTypes>
197    OrderedLockAccess<DequeueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>
198    for IpLinkDeviceState<LoopbackDevice, BT>
199{
200    type Lock = Mutex<DequeueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>;
201    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
202        OrderedLockRef::new(&self.link.rx_queue.deque)
203    }
204}
205
206impl<BT: DeviceLayerTypes>
207    OrderedLockAccess<
208        TransmitQueueState<
209            LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>,
210            Buf<Vec<u8>>,
211            BufVecU8Allocator,
212        >,
213    > for IpLinkDeviceState<LoopbackDevice, BT>
214{
215    type Lock = Mutex<
216        TransmitQueueState<
217            LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>,
218            Buf<Vec<u8>>,
219            BufVecU8Allocator,
220        >,
221    >;
222    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
223        OrderedLockRef::new(&self.link.tx_queue.queue)
224    }
225}
226
227impl<BT: DeviceLayerTypes>
228    OrderedLockAccess<DequeueState<LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>
229    for IpLinkDeviceState<LoopbackDevice, BT>
230{
231    type Lock = Mutex<DequeueState<LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>;
232    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
233        OrderedLockRef::new(&self.link.tx_queue.deque)
234    }
235}
236
237impl DeviceSocketSendTypes for LoopbackDevice {
238    /// When `None`, data will be sent as a raw Ethernet frame without any
239    /// system-applied headers.
240    type Metadata = Option<EthernetHeaderParams>;
241}
242
243impl<CC, BC> ReceiveDequeFrameContext<LoopbackDevice, BC> for CC
244where
245    CC: DeviceIdContext<LoopbackDevice>
246        + ResourceCounterContext<Self::DeviceId, EthernetDeviceCounters>
247        + ReceiveQueueTypes<
248            LoopbackDevice,
249            BC,
250            Meta = LoopbackRxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
251        >,
252    // Loopback needs to deliver messages to `AnyDevice`.
253    CC: DeviceIdAnyCompatContext<LoopbackDevice>
254        + RecvFrameContext<
255            RecvIpFrameMeta<
256                <CC as DeviceIdContext<AnyDevice>>::DeviceId,
257                DeviceIpLayerMetadata<BC>,
258                Ipv4,
259            >,
260            BC,
261        > + RecvFrameContext<
262            RecvIpFrameMeta<
263                <CC as DeviceIdContext<AnyDevice>>::DeviceId,
264                DeviceIpLayerMetadata<BC>,
265                Ipv6,
266            >,
267            BC,
268        > + ResourceCounterContext<<CC as DeviceIdContext<AnyDevice>>::DeviceId, DeviceCounters>
269        + ResourceCounterContext<<CC as DeviceIdContext<AnyDevice>>::DeviceId, IpCounters<Ipv4>>
270        + ResourceCounterContext<<CC as DeviceIdContext<AnyDevice>>::DeviceId, IpCounters<Ipv6>>
271        + DeviceSocketHandler<AnyDevice, BC>,
272    CC::Buffer: BufferMut + Debug,
273    BC: DeviceLayerTypes,
274{
275    fn handle_frame(
276        &mut self,
277        bindings_ctx: &mut BC,
278        device_id: &Self::DeviceId,
279        rx_meta: Self::Meta,
280        mut buf: Self::Buffer,
281    ) {
282        // NOTE: At the time of writing, netdevice_worker in bindings uses a
283        // Buf<&mut [u8]> to feed receive frames into core. Matching the same
284        // type as bindings gives us the benefit of not generating rx path code
285        // twice.
286        //
287        // TODO(https://fxbug.dev/42051635): This might no longer be true when
288        // we revisit owned netdevice buffers, at which point we must consider
289        // the binary size tradeoff here.
290        let mut buf = Buf::new(buf.as_mut(), ..);
291        let buflen = buf.len();
292
293        let (frame, whole_body) =
294            match buf.parse_with_view::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck) {
295                Err(e) => {
296                    self.increment_both(&device_id.clone().into(), |counters: &DeviceCounters| {
297                        &counters.recv_parse_error
298                    });
299                    trace!("dropping invalid ethernet frame over loopback: {:?}", e);
300                    return;
301                }
302                Ok(e) => e,
303            };
304
305        let LoopbackRxQueueMeta { target_device, ip_layer_metadata } = rx_meta;
306        let target_device: <CC as DeviceIdContext<AnyDevice>>::DeviceId =
307            match target_device.map(|d| d.upgrade()) {
308                // This is a packet that should be delivered on `target_device`.
309                Some(Some(dev)) => dev,
310
311                // `target_device` is gone. Drop the packet.
312                Some(None) => return,
313
314                // This is a packet sent to the loopback device.
315                None => device_id.clone().into(),
316            };
317
318        self.add_both_usize(&target_device, buflen, |counters: &DeviceCounters| {
319            &counters.recv_bytes
320        });
321        self.increment_both(&target_device, |counters: &DeviceCounters| &counters.recv_frame);
322
323        let frame_dest = FrameDestination::from_dest(frame.dst_mac(), Mac::UNSPECIFIED);
324        let ethertype = frame.ethertype();
325
326        DeviceSocketHandler::<AnyDevice, _>::handle_frame(
327            self,
328            bindings_ctx,
329            &target_device,
330            ReceivedFrame::from_ethernet(frame, frame_dest).into(),
331            whole_body,
332        );
333
334        match ethertype {
335            Some(EtherType::Ipv4) => {
336                let local_frame_dst = match frame_dest.check_local() {
337                    Some(dst) => dst,
338                    None => {
339                        self.increment_both(&target_device, |counters: &IpCounters<Ipv4>| {
340                            &counters.drop_ip_packet_other_host
341                        });
342                        return;
343                    }
344                };
345                self.increment_both(&target_device, |counters: &DeviceCounters| {
346                    &counters.recv_ipv4_delivered
347                });
348                self.receive_frame(
349                    bindings_ctx,
350                    RecvIpFrameMeta::<_, _, Ipv4>::new(
351                        target_device,
352                        Some(local_frame_dst),
353                        ip_layer_metadata,
354                        NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded),
355                    ),
356                    buf,
357                );
358            }
359            Some(EtherType::Ipv6) => {
360                let local_frame_dst = match frame_dest.check_local() {
361                    Some(dst) => dst,
362                    None => {
363                        self.increment_both(&target_device, |counters: &IpCounters<Ipv6>| {
364                            &counters.drop_ip_packet_other_host
365                        });
366                        return;
367                    }
368                };
369                self.increment_both(&target_device, |counters: &DeviceCounters| {
370                    &counters.recv_ipv6_delivered
371                });
372                self.receive_frame(
373                    bindings_ctx,
374                    RecvIpFrameMeta::<_, _, Ipv6>::new(
375                        target_device,
376                        Some(local_frame_dst),
377                        ip_layer_metadata,
378                        NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded),
379                    ),
380                    buf,
381                );
382            }
383            Some(ethertype @ (EtherType::Arp | EtherType::Other(_))) => {
384                self.increment_both(device_id, |counters: &EthernetDeviceCounters| {
385                    &counters.recv_unsupported_ethertype
386                });
387                trace!("not handling loopback frame of type {:?}", ethertype)
388            }
389            None => {
390                self.increment_both(device_id, |counters: &EthernetDeviceCounters| {
391                    &counters.recv_no_ethertype
392                });
393                trace!("dropping ethernet frame without ethertype");
394            }
395        }
396    }
397}
398
399impl<CC, BC> SendableFrameMeta<CC, BC>
400    for DeviceSocketMetadata<LoopbackDevice, <CC as DeviceIdContext<LoopbackDevice>>::DeviceId>
401where
402    CC: TransmitQueueHandler<
403            LoopbackDevice,
404            BC,
405            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
406        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
407        + DeviceIdContext<AnyDevice>,
408    BC: DeviceLayerTypes,
409{
410    fn send_meta<S>(
411        self,
412        core_ctx: &mut CC,
413        bindings_ctx: &mut BC,
414        body: S,
415    ) -> Result<(), SendFrameError<S>>
416    where
417        S: NetworkSerializer,
418        S::Buffer: BufferMut,
419    {
420        let Self { device_id, metadata } = self;
421        let tx_meta = LoopbackTxQueueMeta::default();
422        match metadata {
423            Some(EthernetHeaderParams { dest_addr, protocol }) => send_as_ethernet_frame_to_dst(
424                core_ctx,
425                bindings_ctx,
426                &device_id,
427                body,
428                protocol,
429                dest_addr,
430                tx_meta,
431            ),
432            None => send_ethernet_frame(core_ctx, bindings_ctx, &device_id, body, tx_meta),
433        }
434    }
435}
436
437/// Sends an IP frame `packet` over `device_id`.
438pub fn send_ip_frame<CC, BC, I, S>(
439    core_ctx: &mut CC,
440    bindings_ctx: &mut BC,
441    device_id: &<CC as DeviceIdContext<LoopbackDevice>>::DeviceId,
442    destination: IpPacketDestination<I, &<CC as DeviceIdContext<AnyDevice>>::DeviceId>,
443    ip_layer_metadata: DeviceIpLayerMetadata<BC>,
444    packet: S,
445) -> Result<(), SendFrameError<S>>
446where
447    CC: TransmitQueueHandler<
448            LoopbackDevice,
449            BC,
450            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
451        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
452        + DeviceIdContext<AnyDevice>,
453    BC: DeviceLayerTypes,
454    I: EthernetIpExt + BroadcastIpExt,
455    S: NetworkSerializer,
456    S::Buffer: BufferMut,
457{
458    core_ctx.increment_both(device_id, DeviceCounters::send_frame::<I>);
459
460    let target_device = match destination {
461        IpPacketDestination::Loopback(device) => Some(device.downgrade()),
462        IpPacketDestination::Broadcast(_)
463        | IpPacketDestination::Multicast(_)
464        | IpPacketDestination::Neighbor(_) => None,
465    };
466    send_as_ethernet_frame_to_dst(
467        core_ctx,
468        bindings_ctx,
469        device_id,
470        packet,
471        I::ETHER_TYPE,
472        LOOPBACK_MAC,
473        LoopbackTxQueueMeta { target_device, ip_layer_metadata },
474    )
475}
476
477fn send_as_ethernet_frame_to_dst<CC, BC, S>(
478    core_ctx: &mut CC,
479    bindings_ctx: &mut BC,
480    device_id: &<CC as DeviceIdContext<LoopbackDevice>>::DeviceId,
481    packet: S,
482    protocol: EtherType,
483    dst_mac: Mac,
484    meta: LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
485) -> Result<(), SendFrameError<S>>
486where
487    CC: TransmitQueueHandler<
488            LoopbackDevice,
489            BC,
490            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
491        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
492        + DeviceIdContext<AnyDevice>,
493    BC: DeviceLayerTypes,
494    S: NetworkSerializer,
495    S::Buffer: BufferMut,
496{
497    /// The minimum length of bodies of Ethernet frames sent over the loopback
498    /// device.
499    ///
500    /// Use zero since the frames are never sent out a physical device, so it
501    /// doesn't matter if they are shorter than would be required.
502    const MIN_BODY_LEN: usize = 0;
503
504    let frame =
505        EthernetFrameBuilder::new(LOOPBACK_MAC, dst_mac, protocol, MIN_BODY_LEN).wrap_body(packet);
506
507    send_ethernet_frame(core_ctx, bindings_ctx, device_id, frame, meta)
508        .map_err(|err| err.into_inner())
509}
510
511fn send_ethernet_frame<CC, BC, S>(
512    core_ctx: &mut CC,
513    bindings_ctx: &mut BC,
514    device_id: &<CC as DeviceIdContext<LoopbackDevice>>::DeviceId,
515    frame: S,
516    meta: LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
517) -> Result<(), SendFrameError<S>>
518where
519    CC: TransmitQueueHandler<
520            LoopbackDevice,
521            BC,
522            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
523        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
524        + DeviceIdContext<AnyDevice>,
525    S: NetworkSerializer,
526    S::Buffer: BufferMut,
527    BC: DeviceLayerTypes,
528{
529    core_ctx.increment_both(device_id, |counters: &DeviceCounters| &counters.send_total_frames);
530    match TransmitQueueHandler::<LoopbackDevice, _>::queue_tx_frame(
531        core_ctx,
532        bindings_ctx,
533        device_id,
534        meta,
535        frame,
536    ) {
537        Ok(len) => {
538            core_ctx.add_both_usize(device_id, len, |counters| &counters.send_bytes);
539            core_ctx.increment_both(device_id, |counters: &DeviceCounters| &counters.send_frame);
540            Ok(())
541        }
542        Err(TransmitQueueFrameError::NoQueue(err)) => {
543            unreachable!("loopback never fails to send a frame: {err:?}")
544        }
545        Err(TransmitQueueFrameError::QueueFull(serializer)) => {
546            core_ctx
547                .increment_both(device_id, |counters: &DeviceCounters| &counters.send_queue_full);
548            Err(SendFrameError { serializer, error: SendFrameErrorReason::QueueFull })
549        }
550        Err(TransmitQueueFrameError::SerializeError(err)) => {
551            core_ctx.increment_both(device_id, |counters: &DeviceCounters| {
552                &counters.send_serialize_error
553            });
554            Err(err.err_into())
555        }
556    }
557}
558
559impl DeviceReceiveFrameSpec for LoopbackDevice {
560    // Loopback never receives frames from bindings, so make it impossible to
561    // instantiate it.
562    type FrameMetadata<D> = Never;
563}