#region License /* Copyright 2012-2013 James F. Bellinger Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #endregion using System.Runtime.InteropServices; #pragma warning disable 420 namespace HidSharp { /// /// Communicates with a USB HID class device. /// [ComVisible(true), Guid("0C263D05-0D58-4c6c-AEA7-EB9E0C5338A2")] public abstract class HidStream : Stream { private int _opened, _closed; private volatile int _refCount; internal class CommonOutputReport { public byte[] Bytes; public bool DoneOK, Feature; public volatile bool Done; } internal HidStream() { ReadTimeout = 3000; WriteTimeout = 3000; } internal static int GetTimeout(int startTime, int timeout) { return Math.Min(timeout, Math.Max(0, startTime + timeout - Environment.TickCount)); } internal int CommonRead(byte[] buffer, int offset, int count, Queue queue) { if (count == 0) { return 0; } int readTimeout = ReadTimeout; int startTime = Environment.TickCount; int timeout; HandleAcquireIfOpenOrFail(); try { lock (queue) { while (true) { if (queue.Count > 0) { byte[] packet = queue.Dequeue(); count = Math.Min(count, packet.Length); Array.Copy(packet, 0, buffer, offset, count); return count; } timeout = GetTimeout(startTime, readTimeout); if (!Monitor.Wait(queue, timeout)) { throw new TimeoutException(); } } } } finally { HandleRelease(); } } internal void CommonWrite(byte[] buffer, int offset, int count, Queue queue, bool feature, int maxOutputReportLength) { count = Math.Min(count, maxOutputReportLength); if (count == 0) { return; } int writeTimeout = WriteTimeout; int startTime = Environment.TickCount; int timeout; HandleAcquireIfOpenOrFail(); try { lock (queue) { while (true) { if (queue.Count == 0) { byte[] packet = new byte[count]; Array.Copy(buffer, offset, packet, 0, count); var outputReport = new CommonOutputReport { Bytes = packet, Feature = feature }; queue.Enqueue(outputReport); Monitor.PulseAll(queue); while (true) { if (outputReport.Done) { if (!outputReport.DoneOK) { throw new IOException(); } return; } timeout = GetTimeout(startTime, writeTimeout); if (!Monitor.Wait(queue, timeout)) { throw new TimeoutException(); } } } timeout = GetTimeout(startTime, writeTimeout); if (!Monitor.Wait(queue, timeout)) { throw new TimeoutException(); } } } } finally { HandleRelease(); } } /// public override void Flush() { } /// /// Sends a Get Feature setup request. /// /// The buffer to fill. Place the Report ID in the first byte. public void GetFeature(byte[] buffer) { GetFeature(buffer, 0, buffer.Length); } /// /// Sends a Get Feature setup request. /// /// The buffer to fill. Place the Report ID in the byte at index . /// The index in the buffer to begin filling with data. /// The number of bytes in the feature request. public abstract void GetFeature(byte[] buffer, int offset, int count); public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return AsyncResult.BeginOperation(() => Read(buffer, offset, count), callback, state); } /// /// Reads HID Input Reports. /// /// The data read. public byte[] Read() { byte[] buffer = new byte[Device.MaxInputReportLength]; int bytes = Read(buffer); Array.Resize(ref buffer, bytes); return buffer; } /// /// Reads HID Input Reports. /// /// The buffer to place the reports into. /// The number of bytes read. public int Read(byte[] buffer) { return Read(buffer, 0, buffer.Length); } /// public override int EndRead(IAsyncResult asyncResult) { return ((AsyncResult)asyncResult).EndOperation(); } /// public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } /// /// Sends a Set Feature setup request. /// /// The buffer of data to send. Place the Report ID in the first byte. public void SetFeature(byte[] buffer) { SetFeature(buffer, 0, buffer.Length); } /// /// Sends a Set Feature setup request. /// /// The buffer of data to send. Place the Report ID in the byte at index . /// The index in the buffer to start the write from. /// The number of bytes in the feature request. public abstract void SetFeature(byte[] buffer, int offset, int count); /// public override void SetLength(long value) { throw new NotSupportedException(); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return AsyncResult.BeginOperation(delegate { Write(buffer, offset, count); return 0; }, callback, state); } internal void HandleInitAndOpen() { _opened = 1; _refCount = 1; } internal bool HandleClose() { return 0 == Interlocked.CompareExchange(ref _closed, 1, 0) && _opened != 0; } internal bool HandleAcquire() { while (true) { int refCount = _refCount; if (refCount == 0) { return false; } if (refCount == Interlocked.CompareExchange (ref _refCount, refCount + 1, refCount)) { return true; } } } internal void HandleAcquireIfOpenOrFail() { if (_closed != 0 || !HandleAcquire()) { throw new IOException("Closed."); } } internal void HandleRelease() { if (0 == Interlocked.Decrement(ref _refCount)) { if (_opened != 0) { HandleFree(); } } } internal abstract void HandleFree(); /// /// Writes an HID Output Report to the device. /// /// The buffer containing the report. Place the Report ID in the first byte. public void Write(byte[] buffer) { Write(buffer, 0, buffer.Length); } /// public override void EndWrite(IAsyncResult asyncResult) { ((AsyncResult)asyncResult).EndOperation(); } /// public override bool CanRead { get { return true; } } /// public override bool CanSeek { get { return false; } } /// public override bool CanWrite { get { return true; } } /// public override bool CanTimeout { get { return true; } } /// /// Gets the associated with this stream. /// public abstract HidDevice Device { get; } /// public override long Length { get { throw new NotSupportedException(); } } /// public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } /// /// The maximum amount of time, in milliseconds, to wait for to receive a HID report. /// /// The default is 3000 milliseconds. /// To disable the timeout, set this to . /// public override sealed int ReadTimeout { get; set; } /// /// The maximum amount of time, in milliseconds, to wait for the device to acknowledge a HID report. /// /// The default is 3000 milliseconds. /// To disable the timeout, set this to . /// public override sealed int WriteTimeout { get; set; } } }