views:

6414

answers:

11

I have a byte array that represents a complete TCP/IP packet. For clarification, the byte array is ordered like this:

(IP Header - 20 bytes)(TCP Header - 20 bytes)(Payload - X bytes)

I have a Parse function that accepts a byte array and returns a TCPHeader object. It looks like this:

TCPHeader Parse( byte[] buffer );

Given the original byte array, here is the way I'm calling this function right now.

byte[] tcpbuffer = new byte[ 20 ];
System.Buffer.BlockCopy( packet, 20, tcpbuffer, 0, 20 );
TCPHeader tcp = Parse( tcpbuffer );

Is there a convenient way to pass the TCP byte array, i.e., bytes 20-39 of the complete TCP/IP packet, to the Parse function without extracting it to a new byte array first?

In C++, I could do the following:

TCPHeader tcp = Parse( &packet[ 20 ] );

Is there anything similar in C#? I'm wanting to avoid the creation and subsequent garbage collection of the temporary byte array if possible.

A: 

I don't think you can do something like that in C#. You could either make the Parse() function use an offset, or create 3 byte arrays to begin with; one for the IP Header, one for the TCP Header and one for the Payload.

Aistina
A better solution IMO would be to use ArraySegment<T> which does the bounds checking for you, so that you don't have to duplicate it everywhere.
casperOne
+3  A: 

If an IEnumerable<byte> is acceptable as an input rather than byte[], and you're using C# 3.0, then you could write:

tcpbuffer.Skip(20).Take(20);

Note that this still allocates enumerator instances under the covers, so you don't escape allocation altogether, and so for a small number of bytes it may actually be slower than allocating a new array and copying the bytes into it.

I wouldn't worry too much about allocation and GC of small temporary arrays to be honest though. The .NET garbage collected environment is extremely efficient at this type of allocation pattern, particularly if the arrays are short lived, so unless you've profiled it and found GC to be a problem then I'd write it in the most intuitive way and fix up performance issues when you know you have them.

Greg Beech
Thanks, Greg. In truth, I haven't profiled it. But common sense says allocating the new array and copying the 20 bytes is less efficient than simply using the array as is. Given the number of packets, I need to be as efficient as possible. Plus, it looks 'neater' without the allocation and copy.
Matt Davis
+16  A: 

A common practice you can see in the .NET framework, and that I recommend using here, is specifying the offset and length. So make your Parse function also accept the offset in the passed array, and the number of elements to use.

Of course, the same rules apply as if you were to pass a pointer like in C++ - the array shouldn't be modified or else it may result in undefined behavior if you are not sure when exactly the data will be used. But this is no problem if you are no longer going to be modifying the array.

Spodi
Yeah that's a better answer than mine.
Greg Beech
I'd do that. +1.
Martinho Fernandes
+1  A: 

If you really need these kind of control, you gotta look at unsafe feature of C#. It allows you to have a pointer and pin it so that GC doesn't move it:

fixed(byte* b = &bytes[20]) {
}

However this practice is not suggested for working with managed only code if there are no performance issues. You could pass the offset and length as in Stream class.

Mehrdad Afshari
A: 

There is no way using verifiable code to do this. If your Parse method can deal with having an IEnumerable<byte> then you can use a LINQ expression

TCPHeader tcp = Parse(packet.Skip(20));
JaredPar
+9  A: 

I would pass an ArraySegment in this case.

You would change your Parse method to this:

// Changed TCPHeader to TcpHeader to adhere to public naming conventions.
TcpHeader Parse(ArraySegment<byte> buffer)

And then you would change the call to this:

// Create the array segment.
ArraySegment<byte> seg = new ArraySegment<byte>(packet, 20, 20);

// Call parse.
TcpHeader header = Parse(seg);

Using the ArraySegment will not copy the array, and it will do the bounds checking for you in the constructor (so that you don't specify incorrect bounds). Then you change your Parse method to work with the bounds specified in the segment, and you should be ok.

casperOne
ArraySegment<byte> seg = new ArraySegment<byte>(packet, 20, packet.Length-1);
gimel
ooops! ArraySegment<byte> b2 = new ArraySegment<byte>(b1, 20, b1.Length-20);
gimel
A: 

You could use LINQ to do something like:

tcpbuffer.Skip(20).Take(20);

But System.Buffer.BlockCopy / System.Array.Copy are probably more efficient.

Steven Robbins
+2  A: 

If you can change the parse() method, change it to accept the offset where the processing should begin. TCPHeader Parse( byte[] buffer , int offset);

DotNET
A: 
headsling
+1  A: 

This is how I solved it coming from being a c programmer to a c# programmer. I like to use MemoryStream to convert it to a stream and then BinaryReader to break apart the binary block of data. Had to add the two helper functions to convert from network order to little endian. Also for building a byte[] to send see http://stackoverflow.com/questions/360111 which has a function that allow for converting from an array of objects to a byte[].

  Hashtable parse(byte[] buf, int offset )
  {

     Hashtable tcpheader = new Hashtable();

     if(buf.Length < (20+offset)) return tcpheader;

     System.IO.MemoryStream stm = new System.IO.MemoryStream( buf, offset, buf.Length-offset );
     System.IO.BinaryReader rdr = new System.IO.BinaryReader( stm );

     tcpheader["SourcePort"]    = ReadUInt16BigEndian(rdr);
     tcpheader["DestPort"]      = ReadUInt16BigEndian(rdr);
     tcpheader["SeqNum"]        = ReadUInt32BigEndian(rdr);
     tcpheader["AckNum"]        = ReadUInt32BigEndian(rdr);
     tcpheader["Offset"]        = rdr.ReadByte() >> 4;
     tcpheader["Flags"]         = rdr.ReadByte() & 0x3f;
     tcpheader["Window"]        = ReadUInt16BigEndian(rdr);
     tcpheader["Checksum"]      = ReadUInt16BigEndian(rdr);
     tcpheader["UrgentPointer"] = ReadUInt16BigEndian(rdr);

     // ignoring tcp options in header might be dangerous

     return tcpheader;
  } 

  UInt16 ReadUInt16BigEndian(BinaryReader rdr)
  {
     UInt16 res = (UInt16)(rdr.ReadByte());
     res <<= 8;
     res |= rdr.ReadByte();
     return(res);
  }

  UInt32 ReadUInt32BigEndian(BinaryReader rdr)
  {
     UInt32 res = (UInt32)(rdr.ReadByte());
     res <<= 8;
     res |= rdr.ReadByte();
     res <<= 8;
     res |= rdr.ReadByte();
     res <<= 8;
     res |= rdr.ReadByte();
     return(res);
  }
Rex Logan
That is certainly a simple, elegant way to do it. I've defined classes for the IP, TCP, and UDP headers. Internally, I use the BitConverter functions to extract the values and IPAddress.NetworkToHostOrder to swap the bytes. I may run some tests to see which approach is more efficient.
Matt Davis
you might want to look at http://stackoverflow.com/questions/2871 if performance is what you are after and switch from classes to structs. I would also leave everything in network order and only convert when you need it.
Rex Logan
A: 

There are .net libraries that do this kind of stuff for you like SharpPcap, http://sharppcap.sf.net

Chris Morgan