SDAP in a Real 5G Capture
One layer of the 5G NR attach walkthrough, followed on its own. SDAP is a single byte on top of PDCP โ a QoS Flow Identifier and little else. This page reads all 18 of them on the data radio bearer, works out why every one is uplink and none downlink, and follows the same QFI off the air and across N3, where it rides a GTP-U extension header to the core.
๐ก Read the walkthrough and the diagram side by side. Open the interactive diagram (or PDF) in a split-screen window so the captions stay in view as you read. In Edge, Chrome, or Firefox, right-click the link and pick your browser's split-screen option (Edge labels it "Open link in split screen window"). On macOS, prefer Chrome or Firefox โ Safari has no in-browser split screen. Otherwise, open the diagram in a new tab and snap the two windows side by side.
Where SDAP sits
PDCP, RLC and MAC each appear in both planes of the 5G radio stack. SDAP does not. It exists only in the user plane, as the topmost radio layer on a data bearer, so this page is the one that gets the user-plane picture:
Two things in that picture matter for the walk. SDAP's radio peer is the gNB โ the box spans UE to gNB, not UE to UPF. But the thing SDAP is labeling, the QoS flow, does not stop at the gNB: it continues over the N3 interface inside GTP-U, which carries the same QoS Flow Identifier in its own extension header. SDAP is the piece of that end-to-end marking that survives the last hop, where there is no room for a GTP-U tunnel, and the packet has to be squeezed into a radio bearer instead.
Across N3: how the QFI travels
The QoS flow the SDAP byte marks neither begins nor ends at the gNB. On the core side of the gNB โ over the N3 interface to the UPF โ the same QoS Flow Identifier travels inside a GTP-U tunnel, carried not in the user's packet but in a GTP-U PDU Session Container extension header (TS 38.415, next-extension-header type 0x85). The gNB is the hinge: it is the one node holding the GTP-U extension header on one side and the SDAP byte on the other, and moving the QFI between them is its job.
Read it in both directions. Downlink, a packet arrives from the UPF as a GTP-U G-PDU whose extension header names the QFI โ and, downlink only, an RQI bit for reflective QoS (covered with the header fields below). The gNB maps it to a DRB and sends it down the radio stack, but in this capture, with no SDAP header at all: sdap-HeaderDL is absent, so the downlink QFI reaches the gNB and travels no further as a byte on the air. That downlink RQI has nowhere to ride onto the air for the same reason โ reflective QoS needs the downlink SDAP header this bearer never turns on. Uplink runs the reverse: the UE's SDAP header carries the QFI to the gNB, and the gNB re-encodes it into the PDU Session Container of a GTP-U packet toward the UPF. The QFI is present on N3 in both directions; on the radio it is present only where a header is configured โ the same asymmetry the rest of this page keeps circling.
The two interfaces carry the identical marking in entirely different envelopes. On N3, the QFI is a field in a GTP-U extension header, and the user's IP packet rides beneath it as the tunnel payload, in the clear at the GTP-U layer:
On the radio, the same QFI is the one-byte SDAP header, and it sits directly on top of PDCP โ the SDAP byte is the first byte of the PDCP SDU. PDCP ciphers what lies below the SDAP header and leaves the header itself in the clear, which is why frame 41's 82 is legible while the IP packet beneath it is not:
The QFI never travels far, but it never travels the same way twice. Between the UPF and the gNB it is a field in a GTP-U extension header, wrapping a cleartext IP packet; between the gNB and the UE it is a byte on top of PDCP, wrapping a ciphered one. SDAP carries the marking across the one hop where a GTP-U tunnel cannot follow.
What SDAP does
SDAP โ Service Data Adaptation Protocol (TS 37.324) is the thin layer 5G added that LTE never had: one byte on the wire, and a mapping table.
The problem it solves comes from the way 5G models quality of service. A PDU session โ the UE's connection to a data network โ is subdivided into QoS flows, each with its own treatment: a latency budget, a loss target, a priority, a guaranteed bit rate or none. A QoS flow is the finest granularity of QoS the 5G core knows about, and everything from the UPF inward is organized around it.
The radio, however, does not schedule QoS flows. It schedules data radio bearers, and a bearer is expensive: it costs an RLC entity, a PDCP entity, a logical channel, and scheduler state at both ends. So the network maps several QoS flows onto one DRB when their requirements are close enough โ and something has to remember which packet belonged to which flow. That something is SDAP:
- Downlink, the gNB receives a packet from the UPF with a QFI in the GTP-U header, decides which DRB it goes on, and โ if configured to โ prepends an SDAP header so the UE can recover the flow.
- Uplink, the UE looks up the packet's QoS flow in the rules it was given, picks the DRB, and โ if configured to โ prepends an SDAP header so the gNB can tell the UPF which flow to bill and police it as.
One SDAP entity is configured per PDU session (TS 37.324 ยง4.2.1), and it is the only radio-layer entity that knows what a QoS flow is. Everything below it sees an undifferentiated stream of bytes on a bearer.
The uplink header is one byte and holds three things; the downlink header is a different byte holding three different things. Learn all five fields before the walk, because the asymmetry is the whole story of this page:
| Field | Meaning |
|---|---|
D/C | Data/Control, the first bit of an uplink SDAP header. 1 marks an ordinary Data PDU. 0 marks the one Control PDU the spec defines โ the end-marker, which a UE sends on the old bearer after a QoS flow is remapped to a different DRB. |
R | One reserved bit in the uplink header, always zero. Wireshark labels it Reserved. |
QFI | QoS Flow Identifier, six bits, so 0โ63 flows per PDU session. This is the payload of the whole layer: the number that says which QoS flow this packet belongs to. |
RDI | Reflective QoS flow to DRB mapping Indication โ downlink only. When set, the UE updates its uplink flow-to-DRB mapping to mirror the one the network just used. |
RQI | Reflective QoS Indication โ downlink only. When set, the UE derives an uplink QoS rule from this packet rather than waiting to be told one. |
Note what the two directions have in common: only the QFI. The first two bits are D/C and R going up, and RDI and RQI coming down. The same byte position means different things depending on which way the packet is traveling, which is one reason each direction's header is configured โ and can be switched off โ independently.
One more property makes this layer readable in a capture nobody has the payload keys for. The SDAP header is not ciphered. TS 38.323 ยง5.8 puts the data part of a PDCP PDU and its MAC-I under the cipher but explicitly excludes the SDAP header, so the QoS marking sits in the clear on a bearer running AES. The PDCP companion shows the same boundary from below; on this page you can see it in the raw bytes of frame 41.
And a scope note, because this layer is right at a boundary. SDAP's job ends at the QFI. What the bytes above the SDAP header turn out to be once PDCP has been deciphered โ IP, and what is riding on it โ is the subject of Decrypting a 5G NR radio capture, which follows the payload the rest of the way. This page stops at the header.
The conversation we're following
๐ก Where this diagram comes from. One session, one diagram. VisualEther โ the tool that decoded this capture and generated the diagrams here โ splits a trace into per-protocol sessions and names each one; this is the SDAP session it labels sdap-bearer/timeout/00016, flow key sdap-bearer::0x8000|2|1, 18 messages over frames 41 to 114.
That is every SDAP PDU in the capture โ not a selection. The trace holds three RRC connections, but only the first ever opens a data radio bearer, so the entire SDAP story of the file fits on one timeline.
The bearer was created by a single message. Frame 38's RRC Reconfiguration added drb-Identity: 1 with an sdap-Config reading:
| Setting | Value |
|---|---|
pdu-Session | 1 |
sdap-HeaderUL | present (0) |
sdap-HeaderDL | absent (1) |
defaultDRB | True |
mappedQoS-FlowsToAdd | 1 item, holding QFI: 2 |
Five lines, and they predict everything the diagram shows: which direction carries a header, how many distinct QFIs you will see, and what happens to a packet whose flow has no rule of its own. defaultDRB: True is the catch-all โ TS 37.324 ยง5.2.1 says that when the UE has no stored mapping rule for a packet's QoS flow, it goes on the default bearer. With one flow mapped and this bearer marked default, there is nowhere else for uplink user data to go.
The three layers underneath are walked in their own companions: the bearer is DRB 1 on LCID 4, running 18-bit RLC-AM sequence numbers under an 18-bit PDCP counter. This page only looks at the byte on top.
The walk
Frame 41 โ the whole layer in one byte. The first SDAP PDU of the capture. Unwrap the transmission from the bottom, and the header falls out of the raw bytes: MAC-NR carries a 55-byte SDU on LCID: 4; RLC-NR reads it as DRB 1, AM, uplink, SN=0, poll set, 52 bytes of data beginning 800000827dbd4542.
Split those bytes. 80 00 00 is the three-byte PDCP header for an 18-bit sequence number โ PDU Type: Data PDU, Seq Num: 0. The very next byte, 82, is the entire SDAP layer: 1000 0010, which Wireshark expands as PDU Type: Data PDU (the D/C bit), Reserved: 0x0, and QFI: 2.
frame : Frame 41: Packet, 691 bytes on wire (5528 bits), 691 bytes captured (5528 bits)
eth : Ethernet II, Src: 00:00:00:00:00:00, Dst: 00:00:00:00:00:00
Destination : 00:00:00:00:00:00
Source : 00:00:00:00:00:00
ip : Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
Differentiated Services Field : 0x00 (DSCP: CS0, ECN: Not-ECT)
010. .... = Flags : 0x2, Don't fragment
udp : User Datagram Protocol, Src Port: 13337, Dst Port: 0
Timestamps
mac-nr : MAC-NR UL-SCH (LCID:4 55 bytes) (Short BSR LCG ID=0 BS=0) (PHR PH=57 PCMAX_f_c=52) (Padding 561 bytes)
Context (RNTI=32768)
Subheader : (LCID:4 55 bytes)
RLC-NR UEId=1 [UL] [AM] DRB:1 [DATA] (P) SN=0 [52-bytes]
Context
AM Header (P) SN=0
PDCP-NR (SN=0 )
Configuration : UEId= 1 DRB-1 (direction=Uplink, plane=User)
UE Security (ciphering=NEA2 (AES), integrity=NIA2 (AES))
Sequence Analysis - OK
SDAP (Data PDU QFI=2)
Internet Protocol Version 6, Src : fe80::2, Dst: ff02::2
.... 0000 0000 .... .... .... .... .... = Traffic Class : 0x00 (DSCP: CS0, ECN: Not-ECT)
Source Address : fe80::2
Special-Purpose Allocation : Link-Local Unicast
Source or Destination Address : fe80::2
Special-Purpose Allocation : Link-Local Unicast
Destination Address : ff02::2
.... .... 0000 .... = Multicast Flags : 0x0
Source or Destination Address : ff02::2
.... .... 0000 .... = Multicast Flags : 0x0
Internet Control Message Protocol v6
Subheader : (Short BSR LCG ID=0 BS=0)
Subheader : (PHR PH=57 PCMAX_f_c=52)
Subheader : (Padding 561 bytes)
Frame 41's field tree, opened at MAC-NR. Expand along the path to reach the QoS marking โ mac-nr โ RLC-NR โ PDCP-NR โ SDAP. The SDAP node sits just after Deciphered Data, one byte holding the whole layer; everything below it is the decryption companion's territory.
Now look at where the ciphertext starts. This bearer runs NEA2 (AES), yet that 82 is legible in the clear, and Wireshark's Deciphered Data for the same PDU begins 6000000000083aff โ an IP header, not the SDAP byte. The QoS marking is outside the encrypted region, exactly as TS 38.323 ยง5.8 specifies. A receiver can classify the packet before it has decrypted a single byte of it.
Frame 47 โ the second PDU, 1219.994 ms later, and the QFI has not moved. RLC SN=1, 84 bytes, data 80000182be762cf9: the same 82 in the same position, one byte past a PDCP header whose sequence number has advanced to 1.
The QFI is constant for this entire session, and it is worth being precise about why. It is not that SDAP marks bearers โ it marks packets, and it re-marks every one. The constant is an artifact of the configuration: mappedQoS-FlowsToAdd held exactly one item. Map a second flow onto this same DRB and the byte would start alternating between two values, which is the case the header exists for.
Frame 50 โ three RLC PDUs in one transport block, and exactly one SDAP PDU. MAC packs a 3-byte status PDU, a whole 64-byte data PDU (SN=2), and the first 539 bytes of SN=3 into one uplink grant. Only SN=2 completes here, so only one PDCP SDU is delivered upward, and only one SDAP header is dissected: QFI: 2.
Look at the bytes of the other one anyway. SN=3 starts 800003 82 โ its SDAP header is already on the wire in this frame. It is simply not deliverable yet. SDAP sees a packet only after RLC has reassembled it and PDCP has deciphered it, which is why this diagram has 18 arrows where RLC's uplink on the same bearer has 27 data PDUs.
Frames 52 and 53 โ the SDAP PDU appears where the last segment landed. Frame 52's first RLC PDU is SN=3 at SO=1153, the third and final segment of an SDU that began in frame 50 and continued through frame 51; that completes PDCP SN=3, QFI: 2. The frame's second RLC PDU opens the next SDU โ SN=4, 800004 82 โ with 498 bytes of it. Frame 53 then contributes SN=4's last 16 bytes at SO=498 and completes PDCP SN=4, QFI: 2.
Two different SDUs were in flight inside one transmission, and the layer above could not tell: SDAP is handed whole packets or nothing. Four SDAP PDUs have now been delivered since frame 47, at 19.999 ms, 6.995 ms and 0.996 ms apart โ the uplink running as fast as the scheduler grants โ and then it stops.
Frame 63 โ a new burst, 1222.999 ms after the last one. RLC SN=5, 64 bytes whole, 800005 82, PDCP SN=5, QFI: 2. The gap is the application's cadence, not SDAP's. The layer has no timers, no keepalives, and no state of its own to maintain. When there is nothing to map, SDAP emits nothing โ which is why a bearer can sit silent for a second and then resume mid-count with no bookkeeping in between.
Frames 66 to 75 โ the monotony, which is the point. PDCP SN=6 (88.996 ms after the last), SN=7 piggybacked behind an RLC status PDU, SN=8 at 108 bytes rather than 64, and after a 1030.999 ms gap, SN=9. Ten SDAP PDUs so far, ten identical headers. The packet sizes change, the grants change, the direction of the status reports changes โ the SDAP byte does not. This is what a correctly configured single-flow DRB looks like, and all the interesting variation lives in the layers below.
One thing that never reaches this layer: the RLC status PDUs sharing those transport blocks. They are RLC's own bookkeeping; they carry no SDAP header, and they appear on this diagram not at all.
Frames 78 to 81 โ two segmented SDUs, back to back. Frame 78 is one of only two frames in the session carrying three RLC PDUs โ frame 50 was the other, and every remaining uplink transmission on this bearer carries one or two. It holds an RLC status PDU, SN=10, whole at 88 bytes, and the first 515 bytes of SN=11. Only PDCP SN=10 completes, so again one SDAP header comes out of a three-PDU frame. SN=11 finishes at SO=1129 in frame 80, 6.993 ms later โ three RLC transmissions across frames 78, 79 and 80 for one SDAP PDU carrying one QFI. In the same frame, SN=12 starts with 80000c 82, and finishes at SO=450 with 538 bytes in frame 81, 0.997 ms after that.
Segmentation is invisible from up here, and so is the number of transmissions it cost. That is the boundary working as designed: RLC's job is to fit packets into grants, and SDAP's job is to label packets. Neither needs to know how the other did it.
Frame 91 โ fourteen down, and not one Control PDU. PDCP SN=13, QFI: 2, 272.008 ms later: an 88-byte SDU behind a status PDU, the fourteenth message of the session. The D/C bit has read Data PDU on every PDU so far, and it will on every remaining one. There is no SDAP Control PDU anywhere in this capture. There is no reason for one โ the only Control PDU the spec defines is the end-marker a UE sends after a QoS flow is remapped to a different DRB (TS 37.324), and with one flow, one bearer, and no handover, nothing here is ever remapped.
Frame 97 โ two SDAP PDUs in one frame. The frame's opening RLC PDU is SN=14 at SO=1227, the last 61 bytes of a three-segment SDU that started back in frame 95; that completes PDCP SN=14, QFI: 2. Riding in the same transport block, SN=15 arrives whole at 332 bytes with the poll set, data beginning 80000f82 โ PDCP Seq Num: 15, then the familiar 82, QFI: 2 again.
The diagram splits this frame into two arrows because they are two genuinely independent packets: two sequence numbers, two SDAP headers, one radio transmission. It is exactly the case that the per-packet header exists to keep unambiguous โ and note that it stays unambiguous even though both packets belong to the same flow. SDAP does not compress, elide, or infer; every packet carries its own label.
Frames 107 and 114 โ the last two. PDCP SN=16, QFI: 2, 93.002 ms after frame 97, an 88-byte SDU behind a status PDU with data 800010 82. Then frame 114: RLC SN=17 completes at SO=613 with 371 bytes and the poll bit; PDCP SN=17 is delivered, and the SDAP header reads QFI: 2 โ 5225.955 ms after the first one.
The totals: 18 SDAP PDUs across 17 frames, every one uplink, every one a Data PDU, every one carrying QFI: 2 โ the single QoS flow that frame 38 mapped onto DRB 1. Six of those 18 SDUs were segmented by RLC and needed 27 uplink RLC data PDUs between them; SDAP saw 18 packets regardless.
The header that is not there
Read the last paragraph again, and one word should snag: uplink โ all 18 of them.
That is not a filter applied to the diagram, and it is not a quiet bearer. The same DRB carried 14 downlink PDCP PDUs in the same window, and not one of them has an SDAP header at all.
Frame 38 said so, before the first packet went out:
| Uplink | Downlink | |
|---|---|---|
sdap-Config setting | sdap-HeaderUL: present (0) | sdap-HeaderDL: absent (1) |
| PDCP PDUs on DRB 1 | 18 | 14 |
| SDAP PDUs | 18 | 0 |
QFI values on the wire | 2, on all 18 | none |
RDI / RQI | not in the uplink format | never transmitted |
The uplink/downlink asymmetry is not a quirk of this capture either โ it is the ordinary configuration, and the reason is in the direction of the ignorance. The gNB already knows which QoS flow it is sending on, because the packet arrived from the UPF over N3 with the QFI in its GTP-U extension header; putting the QFI back on for the last hop tells the UE something it may not need. The UE has no equivalent shortcut going the other way: the gNB must reconstruct the flow before it can hand the packet to N3 with the right marking, and once more than one flow shares a bearer, the header is the only thing that can tell it which. That is why the uplink header is the one usually configured present.
There is a second-order consequence worth naming, because it is the usual reason a network turns the downlink header on. RDI and RQI exist only in the downlink format. Switch sdap-HeaderDL to present, and the network gains the ability to steer the UE's uplink mapping reflectively, one packet at a time, with no signaling at all. Switch it off โ as here โ and the network trades that capability away for one saved byte per downlink packet. Wireshark's sdap.rdi and sdap.rqi fields are empty on every frame of this file, and now you know it was decided in frame 38 rather than never coming up. (The RRC companion has that frame in full, sdap-Config and all.)
The practical habit: when a QFI is missing in one direction, read the sdap-Config before you doubt the decode. It is two enumerated values; they are independent, and either one can be absent.
The later connections
This page's session ends with the first RRC connection, and that is itself a finding.
The capture holds three RRC connections on the same cell. Frame 115 is the last DRB transmission of any kind in the file. The two later connections run signaling bearers only: no drb-ToAddModList, no sdap-Config, no data radio bearer, and therefore no SDAP. Where the MAC, RLC and PDCP companions all have a second act in the later connections, this one ends.
Takeaways
- SDAP is one byte, and that byte is the whole layer on the wire.
D/C, one reserved bit, and six bits of QFI going up;RDI,RQI, and six bits of QFI coming down. There is no sequence number, no timer, and no state to keep. - The QFI is per packet, not per bearer. It reads
2on all 18 PDUs here only becausemappedQoS-FlowsToAddheld one item. The header exists for the case where several flows share one DRB, and it re-labels every packet whether or not that case applies. - Each direction's header is configured separately, and either can be off. Frame 38's
sdap-HeaderUL: presentandsdap-HeaderDL: absentproduced 18 uplink SDAP PDUs and zero downlink ones on a bearer that was busy in both directions. - SDAP counts packets; the layers below count transmissions. 18 SDAP PDUs, six of them segmented, 27 uplink RLC data PDUs. A PDU surfaces here only on the frame where its last segment lands.
- The QoS marking rides outside the cipher. Frame 41's
82is legible in the raw bytes of a NEA2-ciphered PDU, and theDeciphered Datafield beside it starts at the IP header. You can read what class of traffic a packet is without being able to read the packet.
Where this sits in the stack
This page follows one layer of a much larger trace. The layers around it tell different parts of the same story:
- A 5G NR Attach on the Air Interface, Layer by Layer โ the full walkthrough this article is a slice of: random access through to two mobile-terminated returns, all six layers on one timeline.
- PDCP-NR: sequence numbers, COUNT, and ciphering โ the layer directly below, and the one that carries every packet on this page. It is also where the ciphering boundary that leaves the SDAP header in the clear is drawn.
- RLC-NR Acknowledged Mode: sequence numbers, polls, and retransmission โ two layers down, and the reason 18 SDAP PDUs cost 27 transmissions. Frame 38 configured its 18-bit sequence numbers in the same message that configured this page's
sdap-Config. - MAC-NR: scheduling and multiplexing โ three layers down, on
LCID 4. Every "three RLC PDUs in one transport block" moment above was a MAC decision. - Decrypting a 5G NR radio capture โ where this page hands off. It picks up the bytes immediately above the SDAP header and follows them to what the user plane actually turned out to be.
Try it on your own capture
Every arrow in this diagram was decoded and captioned by VisualEther from a raw Wireshark PCAP โ including the session split that isolated the SDAP bearer onto its own timeline. Point it at your own 5G, LTE, or IMS trace and read it the same way.