Search code examples
drange

Reading packets off a fragmented byte stream with ranges?


I believe I sort of know ranges, but I have no real idea for when and where to use them, or how. I fail to "get" ranges. Consider this example:


Let's assume we have a network handler, that we have no control over, that calls our callback function in some thread whenever there's some new data for us:

void receivedData(ubyte[] data)

This stream of data contains packets with the layout

{
    ushort size;
    ubyte[size] body;
}

However, the network handler doesn't know about this, so a call to dataReceived() may contain one or two partial packets, one or several complete packets, or a combination. For simplicity, let's assume there can be no corrupt packets and that we'll receive a data.length == 0 when the connection is closed.

What we'd like now is some beautiful D code that turns all this chaos into appropriate calls to

void receivedPacket(ubyte[] body)

I can surely think of "brute-force" ways of accomplishing this. But here's perhaps where my confusion comes in: Can ranges play a role in this? Can we wrap up receivedData() into a nice range-thingy? How? Or is this just not the kind of problems where you'd use ranges? Why not?

(If it would make more sense for using ranges, feel free to redefine the example.)


Solution

  • what I'd do is

    ubyte[1024] buffer=void;//temp buffer set the size as needed...
    ushort filledPart;//length of the part of the buffer containing partial packet
    union{ushort nextPacketLength=0;ubyte[2] packetLengtharr;}//length of the next packet
    
    void receivedData(ubyte[] data){
        if(!data.length)return;
    
        if(nextPacketLength){
            dataPart = min(nextPacketLength-filledPart.length,data.length);
    
            buffer[filledPart..nextPacketLength] = data[0..dataPart];
            filledPart += dataPart;
    
            if(filledPart == nextPacketLength){
                receivedPacket(buffer[0..nextPacketLength]);//the call
                filledPart=nextPacketLength=0;//reset state
                receivedData(datadataPart..$]);//recurse
            }
        } else{
            packetLengtharr[]=data[0..2];//read length of next packet
    
            if(nextPacketLength<data.length){//full paket in data -> avoid unnecessary copies
                 receivedPacket(data[2..2+nextPacketLength]);
                 receivedData(data[2+nextPacketLength..$]);//recurse
            }else
                receivedData(data[2..$]);//recurse to use the copy code above
        }
    }
    

    it's recursive with 3 possible paths:

    1. data is empty -> no action

    2. nextPacketLength is set to a value != 0 -> copy as much data into the buffer as possible and if the packet is complete call the callback and reset filledPart and nextPacketLength and recurse with rest of data

    3. nextPacketLength ==0 read the packet length (here with a union) if full packet available call callback then recurse with rest of data

    there's only 1 bug still in there when data only hold the first byte of length but I'll let you figure that out (and I'm too lazy to do it now)