Click here to Skip to main content
15,886,258 members
Articles / Programming Languages / C#

SCSI Library in C# - Burn CDs and DVDs, Access Hard Disks, etc.

Rate me:
Please Sign up or sign in to vote.
4.77/5 (48 votes)
19 Jun 2017Ms-PL6 min read 145.3K   8.1K   146  
Ever wonder how programs like Nero work? They make their own SCSI libraries... like this!
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Helper;
using Helper.Algorithms;

namespace FileSystems.Udf
{
	[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
	public abstract class TaggedDescriptor : IMarshalable, ICloneable, ILocatable
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		protected static readonly byte[] EMPTY_BYTES = new byte[0];
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		internal const int TAG_SIZE = 16;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr TAG_IDENTIFIER_BP = (IntPtr)0; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_TagIdentifier");
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr DESCRIPTOR_VERSION_BP = (IntPtr)2; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_DescriptorVersion");
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr TAG_CHECKSUM_BP = (IntPtr)4; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_TagChecksum");
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr RESERVED_BP = (IntPtr)5; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_Reserved");
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr TAG_SERIAL_NUMBER_BP = (IntPtr)6; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_TagSerialNumber");
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr DESCRIPTOR_CRC_BP = (IntPtr)8; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_DescriptorCrc");
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr DESCRIPTOR_CRC_LENGTH_BP = (IntPtr)10; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_DescriptorCrcLength");
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private static readonly IntPtr TAG_LOCATION_BP = (IntPtr)12; //Marshal.OffsetOf(typeof(TaggedDescriptor), "_TagLocation");

		//MAKE SURE that the content of THIS TAG is ENTIRELY unmanaged (that means this class, not derived classes) -- that way, checksumming is a piece of cake
		protected TaggedDescriptor(DescriptorTagIdentifier id) : base() { this.TagIdentifier = id; }

		[DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
		private DescriptorTagIdentifier _TagIdentifier;
		public DescriptorTagIdentifier TagIdentifier { get { return this._TagIdentifier; } private set { this._TagIdentifier = value; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private ushort _DescriptorVersion = 3;
		public ushort DescriptorVersion { get { return this._DescriptorVersion; } set { this._DescriptorVersion = value; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private byte _TagChecksum;
		/// <summary>The previous calculated tag checksum, equal to the sum modulo <c>256</c> of all bytes in the <see cref="TaggedDescriptor"/>, assuming this value is zero. Note that this does not include any fields of the subclasses, but it does include the CRC.</csummary>
		public byte TagChecksum { get { return this._TagChecksum; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private byte _Reserved;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private ushort _TagSerialNumber = 0;
		public ushort TagSerialNumber { get { return this._TagSerialNumber; } set { this._TagSerialNumber = value; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private ushort _DescriptorCrc;
		/// <summary>The previous calculated cyclic redundancy check value. Note that this value is always overwritten before storage to make the descriptor valid; its previous value is not persisted. However, changes to this value can be used to detect changes to the underlying structure.</summary>
		public ushort DescriptorCrc { get { return this._DescriptorCrc; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private ushort _DescriptorCrcLength;
		/// <summary>The length of the descriptor subject to CRC checking. If zero, indicates that the marshaler will calculate the length. (This implies that CRCs must always be calculated.)</summary>
		public ushort DescriptorCrcLength { get { return this._DescriptorCrcLength; } set { this._DescriptorCrcLength = value; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private uint _TagLocation;
		/// <summary>The number of the logical block, within the partition the descriptor is recorded on, containing the first byte of the descriptor.</summary>
		public uint TagLocation { get { return this._TagLocation; } set { this._TagLocation = value; } }

		protected virtual bool AutomaticCyclicRedundancyCheck { get { return true; } }

		public void IncrementSerialNumber() { this.TagSerialNumber = this.GetNextSerialNumber(); }
		public ushort GetNextSerialNumber() { var serial = this.TagSerialNumber; return serial != 0 ? unchecked((ushort)(serial + 1)) : serial; }
		
		public byte CalculateHeaderChecksum()
		{
			int checksum = 0;
			unchecked
			{
				checksum += (byte)((ushort)this._TagIdentifier >> 0);
				checksum += (byte)((ushort)this._TagIdentifier >> 8);
				checksum += (byte)(this._DescriptorVersion >> 0);
				checksum += (byte)(this._DescriptorVersion >> 8);
				checksum += (byte)(this._Reserved >> 0);
				checksum += (byte)(this._TagSerialNumber >> 0);
				checksum += (byte)(this._TagSerialNumber >> 8);
				checksum += (byte)(this._DescriptorCrc >> 0);
				checksum += (byte)(this._DescriptorCrc >> 8);
				checksum += (byte)(this._DescriptorCrcLength >> 0);
				checksum += (byte)(this._DescriptorCrcLength >> 8);
				checksum += (byte)(this._TagLocation >> 0);
				checksum += (byte)(this._TagLocation >> 8);
				checksum += (byte)(this._TagLocation >> 16);
				checksum += (byte)(this._TagLocation >> 24);
			}
			return unchecked((byte)checksum);
		}

		protected virtual void MarshalFromBeforeValidate(BufferWithSize buffer)
		{
			if (this._TagIdentifier != buffer.Read<DescriptorTagIdentifier>(TAG_IDENTIFIER_BP)) { throw new InvalidDataException("The descriptor tag identifier is invalid."); }
			this._DescriptorVersion = buffer.Read<ushort>(DESCRIPTOR_VERSION_BP);
			this._TagChecksum = buffer.Read<byte>(TAG_CHECKSUM_BP);
			this._Reserved = buffer.Read<byte>(RESERVED_BP);
			this._TagSerialNumber = buffer.Read<ushort>(TAG_SERIAL_NUMBER_BP);
			this._DescriptorCrc = buffer.Read<ushort>(DESCRIPTOR_CRC_BP);
			this._DescriptorCrcLength = buffer.Read<ushort>(DESCRIPTOR_CRC_LENGTH_BP);
			this._TagLocation = buffer.Read<uint>(TAG_LOCATION_BP);
		}

		protected virtual void MarshalToBeforeValidate(BufferWithSize buffer)
		{
			buffer.Write(this._TagIdentifier, TAG_IDENTIFIER_BP);
			buffer.Write(this._DescriptorVersion, DESCRIPTOR_VERSION_BP);
			buffer.Write(this._TagChecksum, TAG_CHECKSUM_BP);
			buffer.Write(this._Reserved, RESERVED_BP);
			buffer.Write(this._TagSerialNumber, TAG_SERIAL_NUMBER_BP);
			buffer.Write(this._DescriptorCrc, DESCRIPTOR_CRC_BP);
			buffer.Write(this._DescriptorCrcLength, DESCRIPTOR_CRC_LENGTH_BP);
			buffer.Write(this._TagLocation, TAG_LOCATION_BP);
		}

		private void MarshalFrom(BufferWithSize buffer)
		{
			this.MarshalFromBeforeValidate(buffer);

			if (this.CalculateHeaderChecksum() != this._TagChecksum) { throw new InvalidDataException("Checksum failed."); }
			
			if (this.AutomaticCyclicRedundancyCheck)
			{
				var crc = Checksum.CyclicRedundancyCheck(buffer.ExtractSegment(TAG_SIZE).Address, buffer.LengthU32 - (uint)TAG_SIZE, this.DescriptorCrcLength, 0);
				if (crc != this._DescriptorCrc) { throw new InvalidDataException("Data error (cyclic redundancy check)."); }
			}
		}

		private void MarshalTo(BufferWithSize buffer)
		{
			this.MarshalToBeforeValidate(buffer);

			//HACK: This is a bad way of calculating CRC's, but it ensures that the user can set it to whatever he wants
			ushort prevCrcLen = this._DescriptorCrcLength;
			try
			{
				if (this.AutomaticCyclicRedundancyCheck)
				{
					if (prevCrcLen == 0)
					{
						this._DescriptorCrcLength = (ushort)Math.Min(this.MarshaledSize - TAG_SIZE, 0xFFFFU);
						buffer.Write(this._DescriptorCrcLength, DESCRIPTOR_CRC_LENGTH_BP);
					}
					var crc = Checksum.CyclicRedundancyCheck(buffer.ExtractSegment(TAG_SIZE).Address, buffer.LengthU32 - (uint)TAG_SIZE, this._DescriptorCrcLength, 0);
					this._DescriptorCrc = crc;
					buffer.Write(this._DescriptorCrc, DESCRIPTOR_CRC_BP);
				}

				//It's important that the checksum be calculated AFTER the CRC

				this._TagChecksum = this.CalculateHeaderChecksum();
				buffer.Write(this._TagChecksum, TAG_CHECKSUM_BP);
			}
			finally { if (prevCrcLen != 0) { this._DescriptorCrcLength = prevCrcLen; } }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		protected virtual int MarshaledSize { get { return 16; } }

		[DebuggerHidden]
		void IMarshalable.MarshalFrom(BufferWithSize buffer) { this.MarshalFrom(buffer); }
		[DebuggerHidden]
		void IMarshalable.MarshalTo(BufferWithSize buffer) { this.MarshalTo(buffer); }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		[DebuggerHidden]
		int IMarshalable.MarshaledSize { get { return this.MarshaledSize; } }

		public static uint ReadTagLocation(byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { return new BufferWithSize(pBuffer, buffer.Length - bufferOffset).Read<uint>(TAG_LOCATION_BP); } } }
		public static DescriptorTagIdentifier ReadTagId(byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { return new BufferWithSize(pBuffer, buffer.Length - bufferOffset).Read<DescriptorTagIdentifier>(0); } } }

		public static ushort MarshalToAndCalculateCRC(TaggedDescriptor descriptor, byte[] buffer, int bufferOffset)
		{
			int size = Marshaler.StructureToPtr(descriptor, buffer, bufferOffset);

			return Checksum.CyclicRedundancyCheck(buffer, bufferOffset, size, descriptor.DescriptorCrcLength, 0);
		}

		public virtual object Clone() { return this.MemberwiseClone(); }

		/*
		public override string ToString()
		{
			string tagID;
			switch (this.TagIdentifier)
			{
				case DescriptorTagIdentifier.PrimaryVolumeDescriptor:
					tagID = "PVD";
					break;
				case DescriptorTagIdentifier.AnchorVolumeDescriptorPointer:
					tagID = "AVDP";
					break;
				case DescriptorTagIdentifier.VolumeDescriptorPointer:
					tagID = "VDP";
					break;
				case DescriptorTagIdentifier.ImplementationUseVolumeDescriptor:
					tagID = "IUVD";
					break;
				case DescriptorTagIdentifier.PartitionDescriptor:
					tagID = "PD";
					break;
				case DescriptorTagIdentifier.LogicalVolumeDescriptor:
					tagID = "LVD";
					break;
				case DescriptorTagIdentifier.UnallocatedSpaceDescriptor:
					tagID = "USD";
					break;
				case DescriptorTagIdentifier.TerminatingDescriptor:
					tagID = "TD";
					break;
				case DescriptorTagIdentifier.LogicalVolumeIntegrityDescriptor:
					tagID = "LVID";
					break;
				case DescriptorTagIdentifier.FileSetDescriptor:
					tagID = "FSD";
					break;
				case DescriptorTagIdentifier.FileIdentifierDescriptor:
					tagID = "FID";
					break;
				case DescriptorTagIdentifier.AllocationExtentDescriptor:
					tagID = "AED";
					break;
				case DescriptorTagIdentifier.IndirectEntry:
					tagID = "IE";
					break;
				case DescriptorTagIdentifier.TerminalEntry:
					tagID = "TE";
					break;
				case DescriptorTagIdentifier.FileEntry:
					tagID = "FE";
					break;
				case DescriptorTagIdentifier.ExtendedAttributeHeaderDescriptor:
					tagID = "EAHD";
					break;
				case DescriptorTagIdentifier.UnallocatedSpaceEntry:
					tagID = "USE";
					break;
				case DescriptorTagIdentifier.SpaceBitmapDescriptor:
					tagID = "SBD";
					break;
				case DescriptorTagIdentifier.PartitionIntegrityEntry:
					tagID = "PIE";
					break;
				case DescriptorTagIdentifier.ExtendedFileEntry:
					tagID = "EFE";
					break;
				default:
					throw new InvalidOperationException();
			}

			return string.Format("{0} @ LBA 0x{1:X}", tagID, this.TagLocation);
		}
		//*/

		uint ILocatable.Location { get { return this.TagLocation; } set { this.TagLocation = value; } }
	}

	public enum DescriptorTagIdentifier : short
	{
		None = 0,
		PrimaryVolumeDescriptor = 1,
		AnchorVolumeDescriptorPointer = 2,
		VolumeDescriptorPointer = 3,
		ImplementationUseVolumeDescriptor = 4,
		PartitionDescriptor = 5,
		LogicalVolumeDescriptor = 6,
		UnallocatedSpaceDescriptor = 7,
		TerminatingDescriptor = 8,
		LogicalVolumeIntegrityDescriptor = 9,

		FileSetDescriptor = 256,
		FileIdentifierDescriptor = 257,
		AllocationExtentDescriptor = 258,
		IndirectEntry = 259,
		TerminalEntry = 260,
		FileEntry = 261,
		ExtendedAttributeHeaderDescriptor = 262,
		UnallocatedSpaceEntry = 263,
		SpaceBitmapDescriptor = 264,
		PartitionIntegrityEntry = 265,
		ExtendedFileEntry = 266,
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions