This article is about turning a collection of hardly reuseable classes into a handy framework. My series of simple proof-of-concept applications and tutorial articles here on CodeProject left me with a heap of functionality. To make it useful, I have built a framework that you can plug into your applications as a black box. There also is a demo application that shows all the framework can do.
The Back End: Workers and Helpers
First I made up a list of properties and methods that all
*Utility classes from the existing applications already supported, in order to define an interface from them. Then I realized that some methods (for example
GetMessagePart()) appeared in several classes or were implemented somewhere else in the application. So I decided to create an abstract class
FileUtility instead of an empty interface and place the common code there.
Why should an application care about which
FileUtility to use? I wanted the framework to take a filename and find the right utility from the extension or content. That's what the
FileType singleton is there for. It decides which type a given file is, then the concrete
*FileType returns the utility object plus some information about what can be done with the carrier file. That means, a framework user retrieves a file type object and a utility object via the filename and can start hiding or extracting data right away.
The result were two base classes, each with a couple of derived classes, and a couple of specialised classes which are being used only by certain utilities.
FileUtility actually is abstract.
FileType does not represent a concrete type, therefor it should have been abstract, but to avoid "static dirt" I implemented it as a singleton with a few public factory methods.
All these classes are the namespace
SteganoDotNet.Action which does the actual steganography. The other framework namespace is
SteganoDotNet.UserControls; it makes the configuration of carriers comfortable, but it's not essential. For example, with the
Action namespace it is possible to waterwark a song without telling the framework that it's a standard MIDI file:
string originalFileName = @"C:\Somewhere\music.mid";
string resultFileName = @"C:\Somewhere\result.mid";
FileType fileType = FileType.Current.GetFileType(originalFileName);
FileUtility fileUtility = fileType.CreateUtility(originalFileName);
fileUtility.CountBytesToHide = watermarkText.Length;
fileUtility.CountUsedBitsPerUnit = watermarkText.Length/fileUtility.CountUnits;
fileUtility.OutputFileName = resultFileName;
using (Stream message = streamboxMessage.Stream)
Stream formattedMessage = fileUtility.GetMessagePart(message);
fileUtility.Hide(formattedMessage, new MemoryStream());
The Front End: Forms and Boxes
Every file utility needs some basic information like input data stream (not every carrier must be stored in a file!), and output file name. Depending on the file type's stegano-abilities, additional properties need to be configured. For example, a bitmap file utility has to know which regions of the image to use, or a wave file utility needs the count of bits to change per sample. But don't worry, a framework user does not need to know everything about every file type: The namespace
SteganoDotNet.UserControls contains GUI parts for every kind of carrier. You don't even have to choose which control to use. Every
FileUtility has got a property
CarrierConfigurationControl which returns a
UserControl with a complete configuration GUI. That control raises the event
CancelSettings, when the user cancels the configuration and the file utility is not ready to hide data. When the user has entered and confirmed all settings, the file utility raises its
The following example creates a
FileUtility for a bitmap and displays the configuration control. If the user clicks the control's cancel button, the control gets removed and nothing happens. If the apply button is clicked instead,
FileUtility_SettingsChanged embeds a message in the bitmap. All configuration information like output file name, whether or not to add random noise, the image regions to use, are collected by the
CarrierConfigurationControl. As long as you don't have any cool ideas about the perfect carrier configuration GUI, you can rely on the framework instead of messing with the forms designer.
FileUtility fileUtility = <BR> BitmapFileType.Current.CreateUtility(@"C:\Somewhere\original.tif");
fileUtility.SettingsChanged += new EventHandler(fileUtility_SettingsChanged);
fileUtility.CarrierConfigurationControl.CancelSettings += <BR> new EventHandler(config_CancelSettings);
fileUtility.CarrierConfigurationControl.ExpectedMessageLength = <BR> sbMessage.Stream.Length;
((BitmapConfiguration)<BR> fileUtility.CarrierConfigurationControl).PercentOfMessage = 100;
private void CarrierConfiguration_CancelSettings(object sender, EventArgs e)
private void FileUtility_SettingsChanged(object sender, EventArgs e)
FileUtility fileUtility = (FileUtility)sender;
Stream fullMessage = fileUtility.GetMessagePart(message);
sbMessage.Stream stands for
StreamBox. This little control encapsulates a GUI part that I needed quite often. No matter how the user decides to enter a piece of content, by filename or plain text, the
StreamBox returns the content as a
Lossy Sound: The End of Comfortable Controls
CarrierConfiguration architecture worked fine for all carrier types, except for the loss resistant audio stuff. I wanted to keep the framework free from third party components, but without support for reading/writing audio discs, the GUI would not seem complete. That's why
TapeWaveFileUtility.CarrierConfiguration returns a
TapeWaveConfiguration, but as soon as you place it on a form, the utility raises its
OpenTapeForm event. Then the application has to show its own modal configuration dialog and copy the settings into the file utility. Take a look at
WaveHideForm in the demo application for a complete example. That form reads tracks from an audio disc, allows you to configure each track in a dialog you might know from an older article ^, distributes a message over these audio tracks and finally burns a new audio CD.
WaveExtractForm also reads tracks from an audio CD, then it shows a quite equal configuration dialog to make
TapeWaveFileUtility find the frequency markers:
Those wave processing dialogs are so complex and user-unfriendly that they did not become parts of the framework. The task of filtering the wave with a bandpass filter before calling
TapeWaveUtility.FindAnything() is up to the application itself.
FindAnything() is the first step of data extraction: It finds the frequency markers. The application is informed about every single
Beep with the
BeepFound event. In the demo,
WaveDisplayControl handles those callbacks to give the user a last chance for manual corrections (which means, removing false positives). Finally,
WaveExtractForm uses the (maybe manually fixed) result to fill
TapeWaveFileUtility.Seconds and extract the actual message.
I assume you have not read the entire last preceding paragraph. You're right. Preparing a wave for message extraction is a little too complicated and I'll try to make things easier in the next version. Until then, I suggest you copy
WaveDisplayControl into your own application, if you want to use loss resistant audio steganography.
Back to the Roots
At this point, I had a useful framework and a huge demo application which I called S4U (Steganography for You). I presented it as my exam project at evening school and got top grades. Then school was over and I had too much spare time. Why not turn the whole thing into an open source project? Nice idea, but three components I integrated for audio features are not published under GPL: The wave recorder^ and the CD ripper^ are samples from CodeProject, Steve McMahon's IMAPI-Wrapper ^ is a Creative Commons library.
I did not really solve that problem. That means, i did - if you call deletion of features a solution. When I had removed all CD ripping/burning and wave live recording dialogs, the framework and the demo application were free from third party code. Both still call Sound Exchange^, but this one is not compiled into or referenced by my assemblies, and anyway, it is GPL software.
Now that my steganography framework was ready to become a GPL licensed open source project, I needed a version control system, a bug tracker, a place for documentation, releases, forums and feature request ... in short words: I registered a SourceForge^ account and a project called SteganoDotNet^. The next day my project was accepted and I could check in all my files. Well, that's the way my steganography tutorials will go from now on: You have great ideas? Come to SourceForge and code them into the framework! Especially I'd be very happy about anybody who added JPEG support, or a new audio CD ripper/recorder ... welcome to my first open source project, feel free to join and make it the stegano-framework for the .NET world. :-)