@@ -1860,6 +1860,187 @@ class DatagramChannelTests: XCTestCase {
18601860
18611861 return hasGoodGROSupport
18621862 }
1863+
1864+ // MARK: - Per-Message GSO Tests
1865+
1866+ func testLargeScalarWriteWithPerMessageGSO( ) throws {
1867+ try XCTSkipUnless ( System . supportsUDPSegmentationOffload, " UDP_SEGMENT (GSO) is not supported on this platform " )
1868+
1869+ // We're going to send one large buffer with per-message GSO metadata.
1870+ // The kernel should split it into multiple segments.
1871+ let segmentSize = 1000
1872+ let segments = 10
1873+
1874+ // Form a handful of segments
1875+ let buffers = ( 0 ..< segments) . map { i in
1876+ ByteBuffer ( repeating: UInt8 ( i) , count: segmentSize)
1877+ }
1878+
1879+ // Coalesce the segments into a single buffer.
1880+ var buffer = self . firstChannel. allocator. buffer ( capacity: segments * segmentSize)
1881+ for segment in buffers {
1882+ buffer. writeImmutableBuffer ( segment)
1883+ }
1884+
1885+ // Write the single large buffer with per-message GSO metadata.
1886+ let writeData = AddressedEnvelope (
1887+ remoteAddress: self . secondChannel. localAddress!,
1888+ data: buffer,
1889+ metadata: . init( ecnState: . transportNotCapable, packetInfo: nil , segmentSize: segmentSize)
1890+ )
1891+ XCTAssertNoThrow ( try self . firstChannel. writeAndFlush ( writeData) . wait ( ) )
1892+
1893+ // The receiver will receive separate segments.
1894+ let receivedBuffers = try self . secondChannel. waitForDatagrams ( count: segments)
1895+ let receivedBytes = receivedBuffers. map { $0. data. readableBytes } . reduce ( 0 , + )
1896+ XCTAssertEqual ( segmentSize * segments, receivedBytes)
1897+
1898+ var unusedIndexes = Set ( buffers. indices)
1899+ for envelope in receivedBuffers {
1900+ if let index = buffers. firstIndex ( of: envelope. data) {
1901+ XCTAssertNotNil ( unusedIndexes. remove ( index) )
1902+ } else {
1903+ XCTFail ( " No matching buffer " )
1904+ }
1905+ }
1906+ }
1907+
1908+ func testLargeVectorWriteWithPerMessageGSO( ) throws {
1909+ try XCTSkipUnless ( System . supportsUDPSegmentationOffload, " UDP_SEGMENT (GSO) is not supported on this platform " )
1910+
1911+ // Similar to the test above, but with multiple writes using different segment sizes.
1912+ let segmentSize1 = 1000
1913+ let segments1 = 10
1914+ let segmentSize2 = 500
1915+ let segments2 = 5
1916+
1917+ // Form segments for first write
1918+ let buffers1 = ( 0 ..< segments1) . map { i in
1919+ ByteBuffer ( repeating: UInt8 ( i) , count: segmentSize1)
1920+ }
1921+ var buffer1 = self . firstChannel. allocator. buffer ( capacity: segments1 * segmentSize1)
1922+ for segment in buffers1 {
1923+ buffer1. writeImmutableBuffer ( segment)
1924+ }
1925+
1926+ // Form segments for second write
1927+ let buffers2 = ( 0 ..< segments2) . map { i in
1928+ ByteBuffer ( repeating: UInt8 ( 100 + i) , count: segmentSize2)
1929+ }
1930+ var buffer2 = self . firstChannel. allocator. buffer ( capacity: segments2 * segmentSize2)
1931+ for segment in buffers2 {
1932+ buffer2. writeImmutableBuffer ( segment)
1933+ }
1934+
1935+ // Write both buffers with different segment sizes.
1936+ let writeData1 = AddressedEnvelope (
1937+ remoteAddress: self . secondChannel. localAddress!,
1938+ data: buffer1,
1939+ metadata: . init( ecnState: . transportNotCapable, packetInfo: nil , segmentSize: segmentSize1)
1940+ )
1941+ let writeData2 = AddressedEnvelope (
1942+ remoteAddress: self . secondChannel. localAddress!,
1943+ data: buffer2,
1944+ metadata: . init( ecnState: . transportNotCapable, packetInfo: nil , segmentSize: segmentSize2)
1945+ )
1946+ let write1 = self . firstChannel. write ( writeData1)
1947+ let write2 = self . firstChannel. write ( writeData2)
1948+ self . firstChannel. flush ( )
1949+ XCTAssertNoThrow ( try write1. wait ( ) )
1950+ XCTAssertNoThrow ( try write2. wait ( ) )
1951+
1952+ // The receiver will receive separate segments from both writes.
1953+ let receivedBuffers = try self . secondChannel. waitForDatagrams ( count: segments1 + segments2)
1954+ XCTAssertEqual ( receivedBuffers. count, segments1 + segments2)
1955+ }
1956+
1957+ func testMixedGSOAndNonGSO( ) throws {
1958+ try XCTSkipUnless ( System . supportsUDPSegmentationOffload, " UDP_SEGMENT (GSO) is not supported on this platform " )
1959+
1960+ // Send some messages with GSO and some without.
1961+ let segmentSize = 1000
1962+ let segments = 5
1963+
1964+ // GSO message
1965+ var gsoBuffer = self . firstChannel. allocator. buffer ( capacity: segments * segmentSize)
1966+ for _ in 0 ..< segments {
1967+ gsoBuffer. writeRepeatingByte ( 1 , count: segmentSize)
1968+ }
1969+ let gsoEnvelope = AddressedEnvelope (
1970+ remoteAddress: self . secondChannel. localAddress!,
1971+ data: gsoBuffer,
1972+ metadata: . init( ecnState: . transportNotCapable, packetInfo: nil , segmentSize: segmentSize)
1973+ )
1974+
1975+ // Non-GSO message
1976+ var normalBuffer = self . firstChannel. allocator. buffer ( capacity: 100 )
1977+ normalBuffer. writeRepeatingByte ( 2 , count: 100 )
1978+ let normalEnvelope = AddressedEnvelope (
1979+ remoteAddress: self . secondChannel. localAddress!,
1980+ data: normalBuffer
1981+ )
1982+
1983+ // Send both
1984+ let write1 = self . firstChannel. write ( gsoEnvelope)
1985+ let write2 = self . firstChannel. write ( normalEnvelope)
1986+ self . firstChannel. flush ( )
1987+ XCTAssertNoThrow ( try write1. wait ( ) )
1988+ XCTAssertNoThrow ( try write2. wait ( ) )
1989+
1990+ // Should receive 5 segments + 1 normal message
1991+ let received = try self . secondChannel. waitForDatagrams ( count: segments + 1 )
1992+ XCTAssertEqual ( received. count, segments + 1 )
1993+ }
1994+
1995+ func testPerMessageGSOOverridesChannelLevel( ) throws {
1996+ try XCTSkipUnless ( System . supportsUDPSegmentationOffload, " UDP_SEGMENT (GSO) is not supported on this platform " )
1997+
1998+ // Set channel-level GSO to one value
1999+ XCTAssertNoThrow ( try self . firstChannel. setOption ( . datagramSegmentSize, value: CInt ( 500 ) ) . wait ( ) )
2000+
2001+ // But use per-message GSO with a different value
2002+ let segmentSize = 1000
2003+ let segments = 5
2004+
2005+ var buffer = self . firstChannel. allocator. buffer ( capacity: segments * segmentSize)
2006+ for i in 0 ..< segments {
2007+ buffer. writeRepeatingByte ( UInt8 ( i) , count: segmentSize)
2008+ }
2009+
2010+ let envelope = AddressedEnvelope (
2011+ remoteAddress: self . secondChannel. localAddress!,
2012+ data: buffer,
2013+ metadata: . init( ecnState: . transportNotCapable, packetInfo: nil , segmentSize: segmentSize)
2014+ )
2015+
2016+ XCTAssertNoThrow ( try self . firstChannel. writeAndFlush ( envelope) . wait ( ) )
2017+
2018+ // Should receive segments of size 1000, not 500
2019+ let received = try self . secondChannel. waitForDatagrams ( count: segments)
2020+ for datagram in received {
2021+ XCTAssertEqual ( datagram. data. readableBytes, segmentSize)
2022+ }
2023+ }
2024+
2025+ func testPerMessageGSOThrowsOnNonLinux( ) throws {
2026+ #if os(Linux)
2027+ try XCTSkipIf ( true , " This test only runs on non-Linux platforms " )
2028+ #else
2029+ // On non-Linux platforms, setting segmentSize in metadata should throw an error
2030+ var buffer = self . firstChannel. allocator. buffer ( capacity: 1000 )
2031+ buffer. writeRepeatingByte ( 1 , count: 1000 )
2032+
2033+ let envelope = AddressedEnvelope (
2034+ remoteAddress: self . secondChannel. localAddress!,
2035+ data: buffer,
2036+ metadata: . init( ecnState: . transportNotCapable, packetInfo: nil , segmentSize: 500 )
2037+ )
2038+
2039+ XCTAssertThrowsError ( try self . firstChannel. writeAndFlush ( envelope) . wait ( ) ) { error in
2040+ XCTAssertEqual ( error as? ChannelError , . operationUnsupported)
2041+ }
2042+ #endif
2043+ }
18632044}
18642045
18652046extension System {
0 commit comments