Test Run

Testing Custom Transform Streams

Dr. James McCaffrey

Code download available at:  Tes Run 2007_03.exe(156 KB)

Contents

A RotateStream Class
Testing the RotateStream Class
Two More Scenarios
Preserving Size
Conclusion

One of the first things that developers new to the Microsoft .NET Framework learn is how to read from and write to text files using methods from the FileStream class of the System.IO namespace. However, the .NET I/O stream model also provides developers with a powerful way to create custom stream classes. In this month's column, I'll explain key techniques you can use to test such custom stream classes-and specifically those that deal with transforming data read from or written to another stream.

To get a feel for where I'm headed in this column, take a look at the screenshot in Figure 1. The complete source code for the program that generated the output shown in the figure is available in the code download for this column.

A FileStream object is a programming abstraction that allows you to read from and write to a file easily without having to deal with the many low-level details that actually occur behind the scenes. The first few lines of the output in Figure 1 show a typical example of using the FileStream class to read data from a file:

FileStream fs = File.OpenRead("..\\..\\TextFile1.txt);
byte [] bytes = new byte[fs.Length];
fs.Read(bytes, 0, bytes.Length);
Console.WriteLine("TextFile1.txt contains: " + 
    Encoding.UTF8.GetString(buffer));
Console.WriteLine("File in hexadecimal   : " +
    BitConverter.ToString(bytes));

The next few lines of output demonstrate the use of that same FileStream in conjunction with a custom stream I implemented:

fs.Position = 0;
Console.WriteLine("Filtering TextFile1.txt through " +
                   "custom RotateStream");
RotateStream rs = new RotateStream(fs, 2);
int b = rs.ReadByte();
Console.WriteLine(
    "First byte through the transform is " + b.ToString("X") + "h");

After resetting the current position of the FileStream object to the beginning of the stream using the Position property, I create an instance of a program-defined, custom stream class named RotateStream. This performs a bit-wise rotation transformation, which I'll explain shortly. The RotateStream constructor accepts two input arguments. The first argument is a stream (in this case, the FileStream associated with file TextFile1.txt). The second is the number of bit positions the transform will rotate (in this case, 2). When calling the ReadByte method of my custom RotateStream class, the first byte of input will be read and transformed by a 2-bit rotation to the left. So, because the first byte of input is 'H' = 0x48, (01001000 in binary), when read through the RotateStream filter, the result will be 0x21 (00100001 in binary).

One reason why many application developers and testers are not familiar with custom stream classes is that custom stream classes are not absolutely essential. By this I mean you can achieve the result of a custom stream implementation without actually creating a custom stream class. For example, the output in Figure 1 could have been produced by code along the lines of:

create a FileStream object, fs
associate fs with file TextFile1.txt
read a byte with fs.ReadByte()
rotate the byte 2 positions to the left
display the transformed byte

Figure 1 Custom I/O Stream Demo

Figure 1** Custom I/O Stream Demo **(Click the image for a larger view)

In other words, you can achieve the same effect produced by a custom stream class by using built-in .NET stream classes combined with byte-level programming techniques.

So why write a custom stream class? There are two main reasons that make it worth considering. First, because a .NET stream is an abstraction of a sequence of bytes-such as a file or a TCP/IP socket-and .NET stream objects can be easily composed with each other (typically through their constructors), a custom stream class can provide a generic view of all types of input and output, isolating you from the specific details of the underlying devices, transports, or algorithms. Second, by wrapping byte-level input, output, and transformation routines into a class, you modularize your code, making it easier to test and maintain.

In this column, I will first present the implementation of a custom RotateStream class so you can understand exactly what it is we want to test. Then I will describe the details of how to test the custom stream class. I'll conclude with a brief discussion of how you can adapt and extend the code and techniques presented here to meet your own needs. I believe that you'll find this a useful addition to your personal skill set regardless of your job role.

A RotateStream Class

A common type of custom stream class is one that performs some sort of byte-level transformation. For example, the CryptoStream class of the System.Security.Cryptography namespace can encode and decode bytes using several techniques, including the Advanced Encryption Standard (AES). Stream classes usually implement both read and write methods. Typically, transformation stream classes use the write method to encode bytes and the read method to decode bytes. (I'm using the terms "encode" and "decode" rather loosely here, but they're easier to understand than slightly more accurate phrases such as "the byte transformation performed by the write operation" and "the byte transformation which reverses the transformation operation performed by the write operation.")

In this section, I will implement a custom RotateStream stream class that performs a bit-level rotate-right transformation ("encode") in the class Write method and a bit-level rotate-left transformation ("decode") in the class Read method. The overall structure of my RotateStream class is shown in Figure 2.

Figure 2 Custom Stream Class Structure

public class RotateStream : Stream
{
  private Stream stream;
  private int n;
    
  public RotateStream(Stream stream, int n) { ... }

  public override bool CanRead { ... }
  public override bool CanWrite { ... }
  public override bool CanSeek { ... }
  public override long Length { ... }
  public override long Position { ... }

  public override void Flush() { ... }
  public override void Close(){ ... }
  public override long Seek(long offset, SeekOrigin origin) { ... }
  public override void SetLength(long value) { ... }

  public override int Read(byte[] array, int offset, int count) { ... }
  public override void Write(byte[] buffer, int offset, int count) { ... }

  private static byte ROR(byte b, int n) { ... }
  private static byte ROL(byte b, int n) { ... }
}

Custom stream classes are derived directly or indirectly from the System.IO.Stream class. The Stream class is an abstract base class that forms the foundation for all .NET stream classes.

I need two private field members for my RotateStream class. The Stream object named stream represents a generic sequence of bytes that will act as a source stream for Read operations and a destination stream for Write operations; the field named n holds the number of bit positions the class Read and Write methods will rotate. The custom RotateStream constructor accepts an existing stream that will be associated with my byte transform and an integer that instructs the Read and Write methods how many bit positions to rotate. In some programming languages, notably Java, stream classes are typically either read-classes or write-classes, but not both. However, .NET-based custom stream classes may be both read and write classes.

When creating a custom stream class, you must implement the get accessors on the CanRead and CanWrite properties so calling programs can query for these capabilities. In addition, you must implement a CanSeek property so calling code can determine whether your custom stream class supports seeking to random positions within the stream. Length and Position are additional abstract base properties you must implement-though if your stream is not "seekable," these will typically be implemented simply to throw an exception.

When creating a custom stream, you must implement a total of five methods. The Read and Write methods are, of course, the primary methods of a custom stream class. You must also implement the Flush, Seek, and SetLength methods. I have also chosen to implement a Close method, even though it's not strictly necessary. Finally, I've included two private helper methods, ROR (rotate right) and ROL (rotate left), for use in my Write and Read methods, respectively.

From this discussion, it seems like having to override so many routines is a lot of work, but as you'll see shortly, most of these overrides are just single lines of code.

My custom RotateStream constructor simply copies its input arguments to the corresponding class fields:

public RotateStream(Stream stream, int n)
{
  if (stream == null) throw new ArgumentNullException("stream");
  if (n <= 0 || n >= 8) throw new ArgumentOutOfRangeException("n");
  this.stream = stream;
  this.n = n;
}

Let's begin the custom RotateStream implementation by coding the Write and Read methods. My Write method places bytes into the output stream, performing a rotate-right bit transformation:

public override void Write(byte[] buffer, int offset, int count)
{
  byte[] temp = new byte[count];
  for (int i = offset, j = 0; i < offset + count; ++i, ++j)
    temp[j] = ROR(buffer[i], this.n);
  stream.Write(temp, 0, count);
}

Because I'm overriding the Write method in the abstract base Stream class, my Write method's signature corresponds to other Write signatures that you are probably familiar with, such as FileStream.Write. The buffer parameter holds an array of untransformed bytes to place into the output stream. The offset parameter is a zero-based index, which points into the buffer array telling the Write method where to begin fetching bytes. The count parameter tells the Write method how many bytes from array buffer to place into the output stream. In the body of my Write method, I first allocate a local byte array of the correct size.

(Note that because this column is primarily instructional, I have removed most error-checking code to keep the main ideas of custom streams as clear as possible. In a production situation, you would need to add all the usual error-checking code, such as verifying that the buffer is not empty.)

After allocating my scratch byte array, I iterate through the input byte array, starting at position offset, and store each rotate-right transformed byte into the scratch array. Once the scratch array has been filled, it is placed into the output stream-this is done by calling the Write method of the stream that is connected to the RotateStream (recall that the RotateStream constructor associates an existing stream to RotateStream). The ROR helper method uses classical bit manipulation operations:

private static byte[] _mask = 
    { 0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F };

private static byte ROR(byte b, int n)
{
    int val = b;
    return (byte)
        (((val &  _mask[n]) << (8 - n)) | 
         ((val & ~_mask[n]) >> n));
}

Let's quickly review the difference between a shift-right operation and a rotate-right operation. Shift-right moves bits to the right, from the high-order bit to the low-order bit, and fills the missing high-order bits with 0s. (This would be modified slightly if dealing with signed bytes, but the System.Byte type in .NET is unsigned). For example, if you start with byte 11011101 and right-shift two positions, the result is 00110111. Notice the low-order bits fall off the right side and are lost. A rotate-right operation, on the other hand, checks the value of the low-order bits and then performs a right-shift. Rather than filling the missing high-order bits with 0s, a rotate-right operation fills the high-order bits with the old low-order bits (effectively rotating the bits). For example, if you start with 11011101 and rotate-right one position, the result is 11101110 and if you rotate that result once more to the right, you get 01110111. Figure 3 illustrates the concept of rotate-right.

Figure 3 Rotate-Right One Position

Figure 3** Rotate-Right One Position **(Click the image for a larger view)

There are several ways to implement this in code. A simple solution is to make a copy of the lowest bit, shift the whole value right by one bit, and then put the lowest bit back as the highest bit. This can be done n times, where n is the number of bit positions I want to rotate. But this approach is quite slow. Since this operation is executed for every byte read from the stream, it exists on a "hot" path and therefore should be streamlined.

A faster solution, as shown in the previous code snippet, uses a lookup table to quickly swap the two sides of the byte (where the division between sides is guided by the amount of rotation required). For example, with the bit sequence 11011101, and with n=5, the two sides are 110 and 11101. I want to swap those in order to perform the rotation, resulting in the value 11101110.

To achieve this, my _mask array stores a bit mask with eight values, each representing an additional bit on the right side of the number set to 1 (00000000, 00000001, 00000011, 00000111, 00001111, and so on). The array is used as a lookup table to retrieve the appropriate mask, based on the value of n.

The Read method of my custom RotateStream class implements a decode transformation by performing a left-rotate as it extracts bytes from the input stream (undoing the result of RotateStream.Write calls that perform rotate-right "encode" operations):

public override int Read(byte[] buffer, int offset, int count)
{
  int numRead = stream.Read(buffer, offset, count);
  for (int i = offset; i < numRead + offset; ++i)
    buffer[i] = ROL(buffer[i], this.n);
  return numRead;
}

As with the Write method, my Read method has a standard signature that you're probably familiar with. The buffer parameter holds the transformed bytes, which are read from the input stream; the offset parameter tells Read where to begin placing input into the buffer parameter; and the count parameter tells Read how many bytes to attempt to read from input. The return value is the actual number of bytes read from input. My Read implementation reads from the associated input stream into array and then iterates through buffer, performing a rotate-left transformation using the ROL helper method. The ROL helper method is almost identical to the ROR helper, except with different shift directions and a different mask array (bits are filled in from the left rather than the right):

private static byte[] _leftMask = 
    { 0, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE };

private static byte ROL(byte b, int n)
{
    int val = b;
    return (byte)
        (((val &  _leftMask[n]) >> (8 - n)) |
         ((val & ~_leftMask[n]) << n));
}

When writing a custom stream class that wraps another stream, some methods will simply delegate to the same method on the contained stream. For example, the Seek method in my class is implemented as follows:

public override long Seek(long offset, SeekOrigin origin)
{
  return stream.Seek(offset, origin);
}

All I do is call the Seek method of the underlying stream, which is associated with my custom RotateStream stream in the constructor. However, not all streams support a Seek operation. Seek is primarily intended for use with files. Streams like Console I/O streams, streams derived from pipes, and NetworkStream objects do not support a Seek method. If I associate my RotateStream with one of these types of streams and attempt to call Seek, a NotSupportedExeception exception will be thrown. Thus, any tests should be aware of the seeking capabilities of a stream. An alternative for custom streams that don't support seeking is to provide a placeholder implementation:

public override long Seek(long offset, SeekOrigin origin)
{
  throw new NotSupportedException("Seek not supported.");
}

The same approach is possible with derived Read and Write methods, too. Because derived Read, Write, and Seek methods may or may not be supported, you must implement CanRead, CanWrite, and CanSeek properties when writing a custom stream class so that calling programs (and test frameworks) can query your custom stream. My three read/write/seek query properties are:

public override bool CanRead { get { return stream.CanRead; } }
public override bool CanWrite { get { return stream.CanWrite; } }
public override bool CanSeek { get { return stream.CanSeek; } }

Since my custom RotateStream is associated with some existing source stream (for Read) or destination stream (for Write), I can just check to see if the associated stream supports each operation. This is usually a good approach for common transformation-type streams, such as RotateStream. But if you are writing a stream class that is primarily intended to act as connector between a source and destination (such as the FileStream and NetworkStream classes), then you will likely return an explicit true or false value. Say I definitely do not want to support a Seek operation. I can write:

public override bool CanSeek { get { return false; } }

Implementing the Length and Position properties in a custom stream class is similar to the CanRead, CanWrite, and CanSeek properties. If your custom stream class is a transform and associated with an existing source or destination stream, you can simply return the underlying Length and Position properties. Alternatively, you can decide not to support these operations by throwing an exception. Note that if you decide not to support the Length property then for consistency you should not support the SetLength method either.

The final required override is the Flush method:

public override void Flush() { stream.Flush(); }

As before, I simply invoke the associated stream's Flush method. This clears all buffers for the stream and forces any buffered data to be written to output.

And finally, in order to ensure that the underlying stream is closed when my stream is closed, I override the Close method:

public override void Close()
{
  stream.Close();
  base.Close();
}

Testing the RotateStream Class

Now that you know how to create a custom transform-type stream class, let's look at how you can test its functionality. Testing a custom stream class is simple in principle: you test the properties and methods that you've implemented. In this regard, testing a custom stream class is similar to API/unit testing. The most fundamental test is a Read test, as shown in the top part of Figure 4. Then Figure 5 shows the code that produced the Read test output.

Figure 5 Read Test

Console.WriteLine(“\n==== RotateStream.Read test”);
byte[] inputBytes = new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F };
byte[] resultBytes = new byte[inputBytes.Length];

using(MemoryStream ms = new MemoryStream(inputBytes))
using(RotateStream rs = new RotateStream(ms, 1))
{
  int nr = rs.Read(resultBytes, 0, resultBytes.Length);
  Console.WriteLine(
    “\nReading “ + nr + “ bytes from memory through RotateStream”);
}

string input = BitConverter.ToString(inputBytes);
string expected = “90-CA-D8-D8-DE”;
string actual = BitConverter.ToString(resultBytes);
Console.WriteLine(“\nTest input      = “ + input);
Console.WriteLine(“Expected result = “ + expected);
Console.WriteLine(“Actual result   = “ + actual + “\n”);
Console.WriteLine(actual == expected ? “Pass” : “Fail”);

Figure 4 Read and Write Tests

Figure 4** Read and Write Tests **(Click the image for a larger view)

As with all test cases, this requires an input and an expected result. I create my input as a sequence of hardcoded bytes (0x48, 0x65, 0x6C, 0x6C, and 0x6F). However, I could have created my input from a string like this:

string s = "Hello";
byte[] inputBytes = Encoding.ASCII.GetBytes(s);

This is usually not a good idea, though, since strings can't generate most byte patterns.

Before I can instantiate an instance of my RotateStream class and call Read, I need to establish an underlying source stream. You can do this by creating a MemoryStream object. (This is a good approach because the MemoryStream constructor can accept a byte array.) Then I instantiate a RotateStream object, associating it with the MemoryStream and specifying a 1-bit left-rotation.

With the preparation in place, I call RotateStream.Read and capture the result into array resultBytes. In order to compare my actual result with an expected result, I use the BitConverter class to convert bytes into a formatted string and then do a string comparison. This approach is not very efficient, but it is somewhat more readable than the alternative of comparing raw byte values. Using this test case as a guide, I can create additional test cases that examine different bit-rotation values and different inputs.

Testing the Write method of a custom stream class is similar to testing the Read method. When run, the code in Figure 6 produces the output shown in the lower part of Figure 4. Notice that for this test case, I use the expected output from the Read test case example as test input. Because Read performs a rotate-left operation and Write performs a rotate-right operation, my expected result will be the original 0x48, 0x65, 0x6C, 0x6C, 0x6F byte sequence. I use a MemoryStream object to act as a destination stream for the bytes transformed by my RotateStream object, and I do a simple string comparison to check my actual result with my expected result. This serves as a model for the many other test cases I would go on to create (including specifying different values of byte input and different rotate arguments, writing just part of input, attempting to write more output than available bytes, and so on).

Figure 6 Write Test

Console.WriteLine("\n\n==== RotateStream.Write test");
byte[] inputBytes2 = new byte[] { 0x90, 0xCA, 0xD8, 0xD8, 0xDE };
byte[] resultBytes2 = new byte[inputBytes2.Length];
using(MemoryStream ms2 = new MemoryStream(resultBytes2))
using(RotateStream rs2 = new RotateStream(ms2, 1))
{
  rs2.Write(inputBytes2, 0, inputBytes2.Length);
  Console.WriteLine("\nWriting " + (inputBytes2.Length - 0) +
    " bytes to memory through RotateStream");
}

string input2 = BitConverter.ToString(inputBytes2);
string expected2 = "48-65-6C-6C-6F";
string actual2 = BitConverter.ToString(resultBytes2);
Console.WriteLine("\nTest input      = " + input2);
Console.WriteLine("Expected result = " + expected2);
Console.WriteLine("Actual result   = " + actual2 + "\n");
Console.WriteLine(actual2 == expected2 ? "Pass" : "Fail");

In order to test my Seek method, I need to associate a RotateStream instance with a source stream that supports Seek. The FileStream class makes a good choice here (see Figure 7). I create a text file, named TextFile1.txt, that contains "Hello", and I instantiate a FileStream object that references the file to act as a source stream. Next, I instantiate an instance of my RotateStream class under test with a bit-rotation argument of 2. I specify a seek offset of 3, which points at the second lower-case l character in TextFile1.txt. Then I call RotateStream.Seek, checking the return value to make sure Seek did not fail.

Figure 7 Using the FileStream Class

Console.WriteLine("\n==== Seek test");
using(FileStream fs = File.OpenRead("..\\..\\TextFile1.txt")
using(RotateStream rs = new RotateStream(fs, 2))
{
  int inputSeek = 3;
  long x = rs.Seek(inputSeek, SeekOrigin.Begin);
  if (x == -1) throw new Exception("Seek operation error");
  int actualByte = rs.ReadByte();
  rs.Close();

  int expectedByte = 0xB1;
  Console.WriteLine("\nInput Seek    = " + inputSeek);
  Console.WriteLine("Expected byte = " + expectedByte);
  Console.WriteLine("Actual byte   = " + actualByte + "\n");
  Console.WriteLine(actualByte == expectedByte ? "Pass" : "Fail");
}

At this point, I could simply do a FileStream.Read to fetch the current byte and check to see if it is 0x6C, or 'l'. Here, however, I instead use my inherited RotateStream.ReadByte method. The ReadByte method essentially calls its Read method with a count value of 1 and returns the resulting byte. If my input is 0x6C, then my expected result will be that input after a 2-bit rotate-right transformation, or 0xB1. As a general rule of thumb, you shouldn't test two different things at the same time-in this case, the Seek method and the ReadByte method-because if your test case fails, the cause of the failure isn't revealed. But as with any rule of thumb, this is just a guideline. A double test like this can be more efficient than separate tests, especially when performing regression tests where you expect all test cases to pass the majority of the time.

Of course, you'll need a bunch of additional tests; this is only a start. For example, for the Seek method, if the expected result is an exception (due to testing an error condition or to Seek not being supported), you should catch any returned exceptions and report success accordingly.

Two More Scenarios

This sample RotateStream custom stream class and the techniques used to test it are representative of the kind of simple byte-transform custom stream classes and tests that are common in application programming scenarios. The information presented here should allow you to understand and test your own custom transform stream. However, there are two major scenarios I did not cover. In addition to custom transform streams, which generally assume the existence of source and destination streams, you can create what I call custom end-point streams. These are intended to act as conduits between a custom byte source and destination. In fact, most of the .NET stream classes you are familiar with, including FileStream and NetworkStream, are end-point streams. But creating these types of streams is more common in a systems programming environment than in an application programming environment, and the topic is outside the scope of this column.

The second major custom stream topic I haven't covered here is the creation and testing of asynchronous Read and Write methods. However, asynchronous methods are generally implemented in end-point custom stream classes, but not in transform custom stream classes.

Preserving Size

The custom RotateStream class presented in this column is what I call a size-preserving transform stream. By this I mean that the transformation performed by the Read and Write methods preserves the size of input and output. But not all transform streams preserve size. Consider a hypothetical PaddingStream class, which is similar to the RotateStream class in the sense that the PaddingStream constructor associates the PaddingStream object with an existing source stream like a FileStream. Now suppose the PaddingStream.Write method encodes by adding 0 bytes to every other byte of raw input, and that the PaddingStream.Read method decodes by stripping away every other 0-byte. If some raw byte source consists of, say, 0x11223344 then the Write method will place 0x0011002200330044 into the output stream. And the Read method would undo this padding.

Custom streams that do not preserve size can be very, very tricky. Fortunately, in application development scenarios, the need to create custom streams that do not preserve size is rare.

Conclusion

To summarize, the .NET environment lets you easily create custom stream classes. In an application development environment, the most common type of custom stream classes are those that perform a byte-level transformation with a Write method acting as an encoder and a Read method acting as a decoder.

Because the effect of a custom transform stream can be created using existing .NET stream classes combined with bit manipulation techniques, custom stream classes are not widely used or understood by application developers and testers. However, creating a custom stream class can modularize transform code, making it easier to test, modify, and maintain.

Send your questions and comments for James to testrun@microsoft.com.

Dr. James McCaffrey works for Volt Information Sciences, Inc., where he manages technical training for software engineers working at Microsoft. He has worked on several Microsoft products, including Internet Explorer and MSN Search. James is the author of .NET Test Automation Recipes (Apress, 2006). He can be reached at jmccaffrey@volt.com or v-jammc@microsoft.com.