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:

sequenceDiagram accTitle: 5G NR user-plane protocol stack, SDAP highlighted accDescr: The user-plane layers, top of stack to bottom. SDAP, PDCP, RLC, MAC and PHY each span the UE and the gNB. Below them, the N3 interface, GTP-U carrying the QoS Flow Identifier, spans the gNB and the UPF. SDAP, the subject of this article, is highlighted. participant UE participant gNB participant UPF Note over UE,gNB: SDAP Note over UE,gNB: PDCP Note over UE,gNB: RLC Note over UE,gNB: MAC Note over UE,gNB: PHY Note over gNB,UPF: N3 ยท GTP-U (QFI)
The 5G NR user-plane stack, with SDAP highlighted. SDAP, PDCP, RLC, MAC and PHY span the UE and the gNB. Below the radio stack, an N3 box (GTP-U carrying the QFI) spans the gNB and the UPF โ€” where the QoS flow continues into the core. SDAP's radio peer is the gNB, not the UPF.

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.

sequenceDiagram accTitle: How the QoS Flow Identifier travels across N3 and the Uu radio in both directions accDescr: A downlink packet reaches the gNB from the UPF as a GTP-U G-PDU whose PDU Session Container extension header carries the QFI and a reflective-QoS bit. The gNB forwards it to the UE as a PDCP PDU with no SDAP header, because the downlink SDAP header is configured as absent. In the uplink, the UE sends a PDCP PDU whose SDAP header carries the QFI, and the gNB re-encodes that QFI into the PDU Session Container of an uplink GTP-U G-PDU toward the UPF. participant UPF participant gNB participant UE Note over UPF,gNB: N3 โ€” GTP-U tunnel (UDP 2152) Note over gNB,UE: Uu radio โ€” SDAP over PDCP UPF->>gNB: Downlink G-PDU ยท PDU Session Container [QFI 2, RQI] gNB->>UE: Downlink PDCP PDU ยท no SDAP header (sdap-HeaderDL absent) UE->>gNB: Uplink PDCP PDU ยท SDAP header [QFI 2] gNB->>UPF: Uplink G-PDU ยท PDU Session Container [QFI 2]
How a QoS Flow Identifier travels end to end, in both directions. On N3 it rides a GTP-U extension header between the UPF and the gNB; on the Uu radio it rides the one-byte SDAP header between the gNB and the UE. The gNB is the translation point. Here the downlink SDAP header is absent, so the downlink QFI reaches the gNB but travels no further as a byte on the air.

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:

flowchart LR accTitle: Downlink N3 packet structure, outermost header to innermost payload accDescr: From the outside in, an IP header, then a UDP header with destination port 2152, then a GTP-U G-PDU header carrying the tunnel endpoint identifier and a next-extension-header type of 0x85, then a PDU Session Container extension header carrying the QoS Flow Identifier and the reflective-QoS bit, and finally the user IP packet as the GTP-U payload, carried in the clear. IP["IP"] --> UDP["UDP<br/>dst 2152"] UDP --> GTPU["GTP-U G-PDU<br/>TEID ยท NextExt 0x85"] GTPU --> PSC["PDU Session Container<br/>QFI ยท RQI"] PSC --> TPDU["user IP packet<br/>T-PDU ยท in the clear"]
The downlink N3 packet, outermost header first. The QFI sits in the GTP-U PDU Session Container extension header, above the user IP packet โ€” which rides as the tunnel payload, the T-PDU, 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:

flowchart LR accTitle: Uplink radio packet on DRB 1 in frame 41, first byte to last accDescr: Three bytes of PDCP header reading 80 00 00, then one byte of SDAP header reading 82 whose QFI field is 2, both in the clear, then the ciphered user IP packet under NEA2. PDCP["PDCP header<br/>80 00 00"] --> SDAP["SDAP header<br/>82 ยท QFI 2<br/>in the clear"] SDAP --> CIPH["ciphered user IP packet<br/>NEA2"]
The uplink radio packet on DRB 1, from frame 41 โ€” the same bytes the walk splits. The three-byte PDCP header and the one-byte SDAP header both sit in the clear; only the user IP packet beneath them is ciphered under NEA2. The SDAP byte is the whole of SDAP, nested directly inside the PDCP PDU.

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:

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:

FieldMeaning
D/CData/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.
ROne reserved bit in the uplink header, always zero. Wireshark labels it Reserved.
QFIQoS 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.
RDIReflective 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.
RQIReflective 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:

SettingValue
pdu-Session1
sdap-HeaderULpresent (0)
sdap-HeaderDLabsent (1)
defaultDRBTrue
mappedQoS-FlowsToAdd1 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.

๐Ÿ”ผ [00001] Frame 41 UL SDAP -> QFI 2 2024-04-13T03:06:58.234964Z ๐Ÿ“ฑ UE ๐Ÿ“ก gNB
frame : Frame 41: Packet, 691 bytes on wire (5528 bits), 691 bytes captured (5528 bits)
Encapsulation type : Ethernet (1)
Arrival Time : Apr 12, 2024 20:06:58.234964000 Pacific Daylight Time
UTC Arrival Time : Apr 13, 2024 03:06:58.234964000 UTC
Epoch Arrival Time : 1712977618.234964000
Time shift for this packet : 0.000000000 seconds
Time delta from previous captured frame : 99.231000 milliseconds
Time delta from previous displayed frame : 99.231000 milliseconds
Time since reference or first frame : 2.443236000 seconds
Frame Number : 41
Frame Length : 691 bytes (5528 bits)
Capture Length : 691 bytes (5528 bits)
Frame is marked : False
Frame is ignored : False
Protocols in frame : eth:ethertype:ip:udp:mac-nr:rlc-nr:pdcp-nr:ipv6:icmpv6
Character encoding : ASCII (0)
eth : Ethernet II, Src: 00:00:00:00:00:00, Dst: 00:00:00:00:00:00
Destination : 00:00:00:00:00:00
Destination (resolved) : 00:00:00:00:00:00
Destination OUI : 00:00:00 (Officially Xerox, but 0:0:0:0:0:0 is more common)
Destination OUI (resolved) : Officially Xerox, but 0:0:0:0:0:0 is more common
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 00:00:00:00:00:00
Address (resolved) : 00:00:00:00:00:00
Address OUI : 00:00:00 (Officially Xerox, but 0:0:0:0:0:0 is more common)
Address OUI (resolved) : Officially Xerox, but 0:0:0:0:0:0 is more common
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Source : 00:00:00:00:00:00
Source (resolved) : 00:00:00:00:00:00
Source OUI : 00:00:00 (Officially Xerox, but 0:0:0:0:0:0 is more common)
Source OUI (resolved) : Officially Xerox, but 0:0:0:0:0:0 is more common
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 00:00:00:00:00:00
Address (resolved) : 00:00:00:00:00:00
Address OUI : 00:00:00 (Officially Xerox, but 0:0:0:0:0:0 is more common)
Address OUI (resolved) : Officially Xerox, but 0:0:0:0:0:0 is more common
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Type : IPv4 (0x0800)
Stream index : 0
ip : Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
0100 .... = Version : 4
.... 0101 = Header Length : 20 bytes (5)
Differentiated Services Field : 0x00 (DSCP: CS0, ECN: Not-ECT)
0000 00.. = Differentiated Services Codepoint : Default (0)
.... ..00 = Explicit Congestion Notification : Not ECN-Capable Transport (0)
Total Length : 677
Identification : 0x0028 (40)
010. .... = Flags : 0x2, Don't fragment
0... .... = Reserved bit : Not set
.1.. .... = Don't fragment : Set
..0. .... = More fragments : Not set
...0 0000 0000 0000 = Fragment Offset : 0
Time to Live : 64
Protocol : UDP (17)
Header Checksum : 0xffff [validation disabled]
Header checksum status : Unverified
Source Address : 127.0.0.1
Source or Destination Address : 127.0.0.1
Source Host : 127.0.0.1
Source or Destination Host : 127.0.0.1
Destination Address : 127.0.0.1
Source or Destination Address : 127.0.0.1
Destination Host : 127.0.0.1
Source or Destination Host : 127.0.0.1
Stream index : 0
udp : User Datagram Protocol, Src Port: 13337, Dst Port: 0
Source Port : 13337
Destination Port : 0
Source or Destination Port : 13337
Source or Destination Port : 0
Length : 657
Checksum : 0xffff [unverified]
Checksum Status : Unverified
Stream index : 0
Stream Packet Number : 41
Timestamps
Time since first frame : 2.443236000 seconds
Time since previous frame : 99.231000 milliseconds
UDP payload (649 bytes)
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)
Radio Type : FDD (1)
Direction : Uplink (0)
RNTI : 0x8000 (32768)
RNTI Type : C-RNTI (3)
UEId : 1
System Frame Number : 433
Slot : 5
HarqId : 1
PHR Type2 other cell PHR : False
Subheader : (LCID:4 55 bytes)
0... .... = Reserved : 0x00
.0.. .... = Format : 8 bits
..00 0100 = LCID : 4 (4)
..00 0100 = LCID : 0x04
SDU Length : 55
UL-SCH SDU : c00000800000827dbd454290b324b24d20df7e7b8a1bd627331dd02958c2f254d7123c9fdc9bfaa01de4c13a53e778b6e36c298e13f444
RLC-NR UEId=1 [UL] [AM] DRB:1 [DATA] (P) SN=0 [52-bytes]
Context
Direction : Uplink (0)
RLC Mode : Acknowledged Mode (4)
UEId : 1
Bearer Type : DRB (5)
Bearer Id : 1
PDU Length : 55
Sequence Number length : 18
AM
AM Header (P) SN=0
1... .... = Data/Control : Data PDU
.1.. .... = Polling Bit : Status report is requested
..00 .... = Segmentation Info : Data field contains all bytes of an RLC SDU (0x0)
.... 00.. = Reserved : 0x00
.... ..00 0000 0000 0000 0000 = Sequence Number : 0
AM Data : 800000827dbd454290b324b24d20df7e7b8a1bd627331dd02958c2f254d7123c9fdc9bfaa01de4c13a53e778b6e36c298e13f444
PDCP-NR (SN=0 )
Configuration : UEId= 1 DRB-1 (direction=Uplink, plane=User)
Direction : Uplink (0)
Plane : User (2)
UE : 1
Bearer type : DCCH (1)
Bearer Id : 1
Seqnum length : 18
MAC-I Present : False
Ciphering disabled : False
SDAP header : Present
ROHC Compression : False
UE Security (ciphering=NEA2 (AES), integrity=NIA2 (AES))
Configuration frame : 17
Ciphering Algorithm : NEA2 (AES) (2)
Integrity Algorithm : NIA2 (AES) (2)
BEARER : 0
DIRECTION : Uplink (0)
COUNT : 0
CIPHER KEY : 4CB12A7FB40AE866AE59FC9529E0BC5B
1... .... = PDU Type : Data PDU
.000 00.. = Reserved : 0x00
.... ..00 0000 0000 0000 0000 = Seq Num : 0
Sequence Analysis - OK
Expected SN : 0
OK : True
Deciphered Data : 6000000000083afffe800000000000000000000000000002ff02000000000000000000000000000285007d3500000000
SDAP (Data PDU QFI=2)
1... .... = PDU Type : Data PDU
.0.. .... = Reserved : 0x0
..00 0010 = QFI : 2
Internet Protocol Version 6, Src : fe80::2, Dst: ff02::2
0110 .... = Version : 6
0110 .... = Version : 6 [This field makes the filter match on "ip.version == 6" possible]
.... 0000 0000 .... .... .... .... .... = Traffic Class : 0x00 (DSCP: CS0, ECN: Not-ECT)
.... 0000 00.. .... .... .... .... .... = Differentiated Services Codepoint : Default (0)
.... .... ..00 .... .... .... .... .... = Explicit Congestion Notification : Not ECN-Capable Transport (0)
.... 0000 0000 0000 0000 0000 = Flow Label : 0x00000
Payload Length : 8
Next Header : ICMPv6 (58)
Hop Limit : 255
Source Address : fe80::2
Address Space : Link-Local Unicast
Special-Purpose Allocation : Link-Local Unicast
Source : True
Destination : True
Forwardable : False
Globally Reachable : False
Reserved-by-Protocol : True
Source or Destination Address : fe80::2
Address Space : Link-Local Unicast
Special-Purpose Allocation : Link-Local Unicast
Source : True
Destination : True
Forwardable : False
Globally Reachable : False
Reserved-by-Protocol : True
Source Host : fe80::2
Source or Destination Host : fe80::2
Destination Address : ff02::2
Address Space : Multicast
.... .... 0000 .... = Multicast Flags : 0x0
.... .... 0... .... = Reserved : 0
.... .... .0.. .... = Rendezvous Point (RP) : False
.... .... ..0. .... = Network Prefix : False
.... .... ...0 .... = Transient : False
.... .... .... 0010 = Multicast Scope : Link-Local scope (0x2)
Source or Destination Address : ff02::2
Address Space : Multicast
.... .... 0000 .... = Multicast Flags : 0x0
.... .... 0... .... = Reserved : 0
.... .... .0.. .... = Rendezvous Point (RP) : False
.... .... ..0. .... = Network Prefix : False
.... .... ...0 .... = Transient : False
.... .... .... 0010 = Multicast Scope : Link-Local scope (0x2)
Destination Host : ff02::2
Source or Destination Host : ff02::2
Stream index : 0
Internet Control Message Protocol v6
Type : Router Solicitation (133)
Code : 0
Checksum : 0x7d35 [correct]
Checksum Status : Good
Reserved : 00000000
Subheader : (Short BSR LCG ID=0 BS=0)
00.. .... = Reserved : 0x00
..11 1101 = LCID : Short BSR (61)
..11 1101 = LCID : 0x3d
000. .... = LCG : 0
...0 0000 = Buffer Size for LCG0 : BS = 0 (0)
Subheader : (PHR PH=57 PCMAX_f_c=52)
00.. .... = Reserved : 0x00
..11 1001 = LCID : Single Entry PHR (57)
..11 1001 = LCID : 0x39
00.. .... = Reserved : 0x0
..11 1001 = Power Headroom : 26 dB <= PH < 28 dB (57)
00.. .... = Reserved : 0x0
..11 0100 = Pcmax,c,f : 22 dBm <= Pcmax,f,c < 23 dBm (52)
Subheader : (Padding 561 bytes)
00.. .... = Reserved : 0x00
..11 1111 = LCID : Padding (63)
..11 1111 = LCID : 0x3f
Rendered from a Wireshark PCAP by VisualEther.

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:

UplinkDownlink
sdap-Config settingsdap-HeaderUL: present (0)sdap-HeaderDL: absent (1)
PDCP PDUs on DRB 11814
SDAP PDUs180
QFI values on the wire2, on all 18none
RDI / RQInot in the uplink formatnever 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

  1. 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.
  2. The QFI is per packet, not per bearer. It reads 2 on all 18 PDUs here only because mappedQoS-FlowsToAdd held 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.
  3. Each direction's header is configured separately, and either can be off. Frame 38's sdap-HeaderUL: present and sdap-HeaderDL: absent produced 18 uplink SDAP PDUs and zero downlink ones on a bearer that was busy in both directions.
  4. 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.
  5. The QoS marking rides outside the cipher. Frame 41's 82 is legible in the raw bytes of a NEA2-ciphered PDU, and the Deciphered Data field 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:

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.