Custom Stream Upgrades

Stream-oriented transports such as TCP and Named Pipes operate on a continuous stream of bytes between the client and server. This stream is realized by a Stream object. In a stream upgrade, the client wants to add an optional protocol layer to the channel stack, and asks the other end of the communication channel to do so. The stream upgrade consists in replacing the original Stream object with an upgraded one.

For example, you can build a compression stream directly on top of the transport stream. In this case the original transport Stream is replaced with one that wraps the compression Stream around the original one.

You can apply multiple stream upgrades, each wrapping the preceding one.

How Stream Upgrades Work

There are four components to the stream upgrade process.

  1. An upgrade stream Initiator begins the process: at run-time it can initiate a request to the other end of its connection to upgrade the channel transport layer.

  2. An upgrade stream Acceptor carries out the upgrade: at run-time it receives the upgrade request from the other machine, and if possible, accepts the upgrade.

  3. An upgrade Provider creates the Initiator on the client and the Acceptor on the server.

  4. A stream upgrade Binding Element is added to the bindings on the service and the client, and creates the provider at runtime.

Note that in the case of multiple upgrades, the Initiator and Acceptor encapsulate state machines to enforce which upgrade transitions are valid for each Initiation.

How to Implement a Stream Upgrade

Windows Communication Foundation (WCF) provides four abstract classes that you can implement:

To implement a custom stream upgrade, do the following. This procedure implements a minimal stream upgrade process on both the client and server machines.

  1. Create a class that implements StreamUpgradeInitiator.

    1. Override the InitiateUpgrade method to take in the stream to be upgraded, and return the upgraded stream. This method works synchronously; there are analogous methods to initiate the upgrade asynchronously.

    2. Override the GetNextUpgrade method to check for additional upgrades.

  2. Create a class that implements StreamUpgradeAcceptor.

    1. Override the AcceptUpgrade method to take in the stream to be upgraded, and return the upgraded stream. This method works synchronously; there are analogous methods to accept the upgrade asynchronously.

    2. Override the CanUpgrade method to determine if the upgrade requested is supported by this upgrade acceptor at this point in the upgrade process.

  3. Create a class the implements StreamUpgradeProvider. Override the CreateUpgradeAcceptor and the CreateUpgradeInitiator methods to return instances of the acceptor and initiator defined in steps 2 and 1.

  4. Create a class that implements StreamUpgradeBindingElement.

    1. Override the BuildClientStreamUpgradeProvider method on the client and the BuildServerStreamUpgradeProvider method on the service.

    2. Override the BuildChannelFactory method on the client and the BuildChannelListener method on the service to add the upgrade Binding Element to BindingParameters.

  5. Add the new stream upgrade binding element to bindings on the server and client machines.

Security Upgrades

Adding a security upgrade is a specialized version of the general stream upgrade process.

WCF already provides two binding elements for upgrading stream security. The configuration of transport-level security is encapsulated by the WindowsStreamSecurityBindingElement and the SslStreamSecurityBindingElement which can be configured and added to a custom binding. These binding elements extend the StreamUpgradeBindingElement class that builds the client and server stream upgrade providers. These binding elements have methods that create the specialized security stream upgrade provider classes, which are not public, so for these two cases all you need to do is to add the binding element to the binding.

For security scenarios not met by the above two binding elements, three security-related abstract classes are derived from the above initiator, acceptor and provider base classes:

  1. System.ServiceModel.Channels.StreamSecurityUpgradeInitiator

  2. System.ServiceModel.Channels.StreamSecurityUpgradeAcceptor

  3. System.ServiceModel.Channels.StreamSecurityUpgradeProvider

The process of implementing a security stream upgrade is the same as before, with the difference that you would derive from these three classes. Override the additional properties in these classes to supply security information to the runtime.

Multiple Upgrades

To create additional upgrade requests repeat the above process: create additional extensions of StreamUpgradeProvider and binding elements. Add the binding elements to the binding. The additional binding elements are processed sequentially, starting with the first binding element added to the binding. In BuildChannelFactory and BuildChannelListener each upgrade provider can determine how to layer itself on any pre-existing upgrade binding parameters. It should then replace the existing upgrade binding parameter with a new composite upgrade binding parameter.

Alternatively, one upgrade provider can support multiple upgrades. For example, you might want to implement a custom stream upgrade provider that supports both security and compression. Do the following steps:

  1. Subclass StreamSecurityUpgradeProvider to write the provider class that creates the Initiator and Acceptor.

  2. Subclass the StreamSecurityUpgradeInitiator making sure to override the GetNextUpgrade method to return the content types for the compression stream and the secure stream in order.

  3. Subclass the StreamSecurityUpgradeAcceptor that understands the custom content types in its CanUpgrade method.

  4. The stream will be upgraded after each call to GetNextUpgrade and CanUpgrade.

See also