674560b9a713459e47464efb43c2a7da3ad4f006
[mono.git] / mcs / class / referencesource / System / sys / system / IO / ports / SerialPort.cs
1 // ==++==
2 //
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 //
5 // ==--==
6 /*============================================================
7 **
8 ** Class:  SerialPort
9 **
10 ** Purpose: SerialPort wraps an internal SerialStream class,
11 **        : providing a high but complete level of Serial Port I/O functionality
12 **        : over the handle/Win32 object level of the SerialStream.
13 **
14 **
15 ** Date:  August 2002
16 **
17 ===========================================================*/
18
19
20 using System;
21 using System.ComponentModel;
22 using System.Collections;
23 using System.Diagnostics;
24 using System.IO;
25 using System.Text;
26 using System.Security;
27 using System.Security.Permissions;
28 using Microsoft.Win32;
29 using System.Runtime.InteropServices;
30 using System.Runtime.Versioning;
31
32
33 namespace System.IO.Ports
34 {
35
36     [MonitoringDescription(SR.SerialPortDesc)]
37     public class SerialPort : System.ComponentModel.Component
38     {
39         public const int InfiniteTimeout = -1;
40
41         // ---------- default values -------------*
42
43         private const int defaultDataBits = 8;
44         private const Parity defaultParity = Parity.None;
45         private const StopBits defaultStopBits = StopBits.One;
46         private const Handshake defaultHandshake = Handshake.None;
47         private const int defaultBufferSize = 1024;
48         private const string defaultPortName = "COM1";
49         private const int defaultBaudRate = 9600;
50         private const bool defaultDtrEnable = false;
51         private const bool defaultRtsEnable = false;
52         private const bool defaultDiscardNull = false;
53         private const byte defaultParityReplace = (byte) '?';
54         private const int defaultReceivedBytesThreshold = 1;
55         private const int defaultReadTimeout = SerialPort.InfiniteTimeout;
56         private const int defaultWriteTimeout = SerialPort.InfiniteTimeout;
57         private const int defaultReadBufferSize = 4096;
58         private const int defaultWriteBufferSize = 2048;
59         private const int maxDataBits = 8;
60         private const int minDataBits = 5;
61         private const string defaultNewLine = "\n";
62
63         private const string SERIAL_NAME = @"\Device\Serial";
64
65         // --------- members supporting exposed properties ------------*
66         private int baudRate = defaultBaudRate;
67         private int dataBits = defaultDataBits;
68         private Parity parity = defaultParity;
69         private StopBits stopBits = defaultStopBits;
70         private string portName = defaultPortName;
71         private Encoding encoding = System.Text.Encoding.ASCII; // ASCII is default encoding for modem communication, etc.
72         private Decoder decoder = System.Text.Encoding.ASCII.GetDecoder();
73         private int maxByteCountForSingleChar = System.Text.Encoding.ASCII.GetMaxByteCount(1);
74         private Handshake handshake = defaultHandshake;
75         private int readTimeout = defaultReadTimeout;
76         private int writeTimeout = defaultWriteTimeout;
77         private int receivedBytesThreshold = defaultReceivedBytesThreshold;
78         private bool discardNull = defaultDiscardNull;
79         private bool dtrEnable = defaultDtrEnable;
80         private bool rtsEnable = defaultRtsEnable;
81         private byte parityReplace = defaultParityReplace;
82         private string newLine = defaultNewLine;
83         private int readBufferSize = defaultReadBufferSize;
84         private int writeBufferSize = defaultWriteBufferSize;
85
86         // ---------- members for internal support ---------*
87         private SerialStream internalSerialStream = null;
88         private byte[] inBuffer = new byte[defaultBufferSize];
89         private int readPos = 0;    // position of next byte to read in the read buffer.  readPos <= readLen
90         private int readLen = 0;    // position of first unreadable byte => CachedBytesToRead is the number of readable bytes left.
91         private char[] oneChar = new char[1];
92         private char[] singleCharBuffer = null;
93
94         // ------ event members ------------------*
95         //public event EventHandler Disposed;
96         [MonitoringDescription(SR.SerialErrorReceived)]
97         public event SerialErrorReceivedEventHandler ErrorReceived;
98         
99         [MonitoringDescription(SR.SerialPinChanged)]
100         public event SerialPinChangedEventHandler PinChanged;
101         
102         [MonitoringDescription(SR.SerialDataReceived)]
103         public event SerialDataReceivedEventHandler DataReceived;
104
105         //--- component properties---------------*
106
107         // ---- SECTION: public properties --------------*
108         // Note: information about port properties passes in ONE direction: from SerialPort to
109         // its underlying Stream.  No changes are able to be made in the important properties of
110         // the stream and its behavior, so no reflection back to SerialPort is necessary.
111
112         // Gets the internal SerialStream object.  Used to pass essence of SerialPort to another Stream wrapper.
113         [Browsable(false)]
114         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
115         public Stream BaseStream
116         {
117             get { 
118                 if (!IsOpen)
119                     throw new InvalidOperationException(SR.GetString(SR.BaseStream_Invalid_Not_Open));
120                     
121                 return internalSerialStream; 
122             }
123         }
124
125         [Browsable(true),
126         DefaultValue(defaultBaudRate),
127         MonitoringDescription(SR.BaudRate)]
128         public int BaudRate
129         {
130             get { return baudRate;  }
131             set {
132                 if (value <= 0)
133                     throw new ArgumentOutOfRangeException("BaudRate", SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
134                 
135                 if (IsOpen)
136                     internalSerialStream.BaudRate = value;
137                 baudRate = value;
138             }
139         }
140
141         [Browsable(false)]
142         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
143         public bool BreakState
144         {
145             get
146             {
147                 if (!IsOpen)
148                     throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
149
150                 return internalSerialStream.BreakState;
151             }
152
153             set {
154                 if (!IsOpen)
155                     throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
156
157                 internalSerialStream.BreakState = value;
158             }
159         }
160
161         // includes all bytes available on serial driver's output buffer.  Note that we do not internally buffer output bytes in SerialPort.
162         [Browsable(false)]
163         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
164         public int BytesToWrite
165         {
166             get
167             {
168                 if (!IsOpen)
169                     throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
170                 return internalSerialStream.BytesToWrite;
171             }
172         }
173
174         // includes all bytes available on serial driver's input buffer as well as bytes internally buffered int the SerialPort class.
175         [Browsable(false)]
176         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
177         public int BytesToRead
178         {
179             get
180             {
181                 if (!IsOpen)
182                     throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
183                 return internalSerialStream.BytesToRead + CachedBytesToRead; // count the number of bytes we have in the internal buffer too.
184             }
185         }
186
187         private int CachedBytesToRead {
188             get {
189                 return readLen - readPos;
190             }
191         }
192
193         [Browsable(false)]
194         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
195         public bool CDHolding
196         {
197             get
198             {
199                 if (!IsOpen)
200                     throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
201                 return internalSerialStream.CDHolding;
202             }
203         }
204
205         [Browsable(false)]
206         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
207         public bool CtsHolding
208         {
209             get
210             {
211                 if (!IsOpen)
212                     throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
213                 return internalSerialStream.CtsHolding;
214             }
215         }
216
217
218         [Browsable(true),
219         DefaultValue(defaultDataBits),
220         MonitoringDescription(SR.DataBits)]
221         public int DataBits
222         {
223             get
224             { return dataBits;  }
225             set
226             {
227                 if (value < minDataBits || value > maxDataBits)
228                     throw new ArgumentOutOfRangeException("DataBits", SR.GetString(SR.ArgumentOutOfRange_Bounds_Lower_Upper, minDataBits, maxDataBits));
229                 
230                 if (IsOpen)
231                     internalSerialStream.DataBits = value;
232                 dataBits = value;
233             }
234         }
235
236         [Browsable(true),
237         DefaultValue(defaultDiscardNull),
238         MonitoringDescription(SR.DiscardNull)]
239         public bool DiscardNull
240         {
241             get
242             {
243                 return discardNull;
244             }
245             set
246             {
247                 if (IsOpen)
248                     internalSerialStream.DiscardNull = value;
249                 discardNull = value;
250             }
251         }
252
253         [Browsable(false)]
254         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
255         public bool DsrHolding
256         {
257             get
258             {
259                 if (!IsOpen)
260                     throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
261                 return internalSerialStream.DsrHolding;
262             }
263         }
264
265         [Browsable(true),
266         DefaultValue(defaultDtrEnable),
267         MonitoringDescription(SR.DtrEnable)]
268         public bool DtrEnable
269         {
270             get { 
271                 if (IsOpen)
272                     dtrEnable = internalSerialStream.DtrEnable;
273
274                 return dtrEnable; 
275             }
276             set
277             {
278                 if (IsOpen)
279                     internalSerialStream.DtrEnable = value;
280                 dtrEnable = value;
281             }
282         }
283
284         // Allows specification of an arbitrary encoding for the reading and writing functions of the port
285         // which deal with chars and strings.  Set by default in the code to System.Text.ASCIIEncoding(), which
286         // is the standard text encoding for modem commands and most of serial communication.
287         // Clearly not designable.
288         [Browsable(false),
289         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
290         MonitoringDescription(SR.Encoding)]
291         public Encoding Encoding
292         {
293             get { 
294                 return encoding; 
295             }
296             set { 
297                 if (value == null)
298                     throw new ArgumentNullException("Encoding");
299
300                 // Limit the encodings we support to some known ones.  The code pages < 50000 represent all of the single-byte
301                 // and double-byte code pages.  Code page 54936 is GB18030.  Finally we check that the encoding's assembly
302                 // is mscorlib, so we don't get any weird user encodings that happen to set a code page less than 50000. 
303                 if (!(value is ASCIIEncoding || value is UTF8Encoding || value is UnicodeEncoding || value is UTF32Encoding || 
304                       ((value.CodePage < 50000 || value.CodePage == 54936)&& value.GetType().Assembly == typeof(String).Assembly))) {
305                       
306                     throw new ArgumentException(SR.GetString(SR.NotSupportedEncoding, value.WebName), "value");
307                 }
308                 
309                 encoding = value; 
310                 decoder = encoding.GetDecoder();
311                 
312                 // This is somewhat of an approximate guesstimate to get the max char[] size needed to encode a single character
313                 maxByteCountForSingleChar = encoding.GetMaxByteCount(1); 
314                 singleCharBuffer = null;
315             }
316         }
317
318         [Browsable(true),
319         DefaultValue(defaultHandshake),
320         MonitoringDescription(SR.Handshake)]
321         public Handshake Handshake
322         {
323             get
324             {
325                 return handshake;
326             }
327             set
328             {
329                 if (value < Handshake.None || value > Handshake.RequestToSendXOnXOff)
330                     throw new ArgumentOutOfRangeException("Handshake", SR.GetString(SR.ArgumentOutOfRange_Enum));
331                 
332                 if (IsOpen)
333                     internalSerialStream.Handshake = value;
334                 handshake = value;
335             }
336         }
337
338         // true only if the Open() method successfully called on this SerialPort object, without Close() being called more recently.
339         [Browsable(false)]
340         public bool IsOpen
341         {
342             get { return (internalSerialStream != null && internalSerialStream.IsOpen); }
343         }
344
345         [
346             Browsable(false),
347             DefaultValue(defaultNewLine),
348             MonitoringDescription(SR.NewLine)
349         ]
350         public string NewLine {
351             get { return newLine; }
352             set { 
353                 if (value == null)
354                     throw new ArgumentNullException();
355                 if (value.Length == 0)
356                     throw new ArgumentException(SR.GetString(SR.InvalidNullEmptyArgument, "NewLine"));
357
358                 newLine = value;
359             }
360         }
361
362         [Browsable(true),
363         DefaultValue(defaultParity),
364         MonitoringDescription(SR.Parity)]
365         public Parity Parity
366         {
367             get
368             {
369
370                 return parity;
371             }
372             set
373             {
374                 if (value < Parity.None || value > Parity.Space)
375                     throw new ArgumentOutOfRangeException("Parity", SR.GetString(SR.ArgumentOutOfRange_Enum));
376                 
377                 if (IsOpen)
378                     internalSerialStream.Parity = value;
379                 parity = value;
380             }
381         }
382
383         [Browsable(true),
384         DefaultValue(defaultParityReplace),
385         MonitoringDescription(SR.ParityReplace)]
386         public byte ParityReplace
387         {
388             get {   return parityReplace;   }
389             set
390             {
391                 if (IsOpen)
392                     internalSerialStream.ParityReplace = value;
393                 parityReplace = value;
394             }
395         }
396
397
398
399         // Note that the communications port cannot be meaningfully re-set when the port is open,
400         // and so once set by the constructor becomes read-only.
401         [Browsable(true),
402         DefaultValue(defaultPortName),
403         MonitoringDescription(SR.PortName)]
404         public string PortName
405         {
406             get { 
407                 return portName; 
408             }
409             [ResourceExposure(ResourceScope.Machine)]
410             set
411             {
412                 if (value == null)
413                     throw new ArgumentNullException("PortName");
414                 if (value.Length ==0)
415                     throw new ArgumentException(SR.GetString(SR.PortNameEmpty_String), "PortName");
416                 
417                 // disallow access to device resources beginning with @"\\", instead requiring "COM2:", etc.
418                 // Note that this still allows freedom in mapping names to ports, etc., but blocks security leaks.
419                 if (value.StartsWith("\\\\", StringComparison.Ordinal))
420                     throw new ArgumentException(SR.GetString(SR.Arg_SecurityException), "PortName");
421                 
422                 if (IsOpen)
423                     throw new InvalidOperationException(SR.GetString(SR.Cant_be_set_when_open, "PortName"));
424                 portName = value;
425             }
426         }
427
428         [Browsable(true),
429         DefaultValue(defaultReadBufferSize),
430         MonitoringDescription(SR.ReadBufferSize)]
431         public int ReadBufferSize {
432             get {
433                 return readBufferSize;
434             }
435             set {
436                 if (value <= 0)
437                     throw new ArgumentOutOfRangeException("value");
438
439                 if (IsOpen)
440                     throw new InvalidOperationException(SR.GetString(SR.Cant_be_set_when_open, "value"));
441
442                 readBufferSize = value;
443             }
444         }
445
446         // timeout for all read operations.  May be set to SerialPort.InfiniteTimeout, 0, or any positive value
447         [Browsable(true),
448         DefaultValue(SerialPort.InfiniteTimeout),
449         MonitoringDescription(SR.ReadTimeout)]
450         public int ReadTimeout
451         {
452             get
453             {
454                 return readTimeout;
455             }
456
457             set
458             {
459                 if (value < 0 && value != SerialPort.InfiniteTimeout)
460                     throw new ArgumentOutOfRangeException("ReadTimeout", SR.GetString(SR.ArgumentOutOfRange_Timeout));
461                 
462                 if (IsOpen)
463                     internalSerialStream.ReadTimeout = value;
464                 readTimeout = value;
465             }
466         }
467
468         [Browsable(true),
469         DefaultValue(defaultReceivedBytesThreshold),
470         MonitoringDescription(SR.ReceivedBytesThreshold)]
471         // If we have the SerialData.Chars event set, this property indicates the number of bytes necessary
472         // to exist in our buffers before the event is thrown.  This is useful if we expect to receive n-byte
473         // packets and can only act when we have this many, etc.
474         public int ReceivedBytesThreshold
475         {
476             get
477             {
478                 return receivedBytesThreshold;
479             }
480
481             set
482             {
483                 if (value <= 0)
484                     throw new ArgumentOutOfRangeException("ReceivedBytesThreshold",
485                         SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
486                 receivedBytesThreshold = value;
487
488                 if (IsOpen) {
489                     // fake the call to our event handler in case the threshold has been set lower
490                     // than how many bytes we currently have.
491                     SerialDataReceivedEventArgs args = new SerialDataReceivedEventArgs(SerialData.Chars);
492                     CatchReceivedEvents(this, args);
493                 }
494             }
495         }
496
497         [Browsable(true),
498         DefaultValue(defaultRtsEnable),
499         MonitoringDescription(SR.RtsEnable)]
500         public bool RtsEnable
501         {
502             get
503             {
504                 if (IsOpen)
505                     rtsEnable = internalSerialStream.RtsEnable;
506                 
507                 return rtsEnable;
508             }
509             set
510             {
511                 if (IsOpen)
512                     internalSerialStream.RtsEnable = value;
513                 rtsEnable = value;
514             }
515         }
516
517         // StopBits represented in C# as StopBits enum type and in Win32 as an integer 1, 2, or 3.
518         [Browsable(true),
519         DefaultValue(defaultStopBits),
520         MonitoringDescription(SR.StopBits)
521         ]
522         public StopBits StopBits
523         {
524             get
525             {
526                 return stopBits;
527             }
528             set
529             {
530                 // this range check looks wrong, but it really is correct.  One = 1, Two = 2, and OnePointFive = 3
531                 if (value < StopBits.One || value > StopBits.OnePointFive)
532                     throw new ArgumentOutOfRangeException("StopBits", SR.GetString(SR.ArgumentOutOfRange_Enum));
533                 
534                 if (IsOpen)
535                     internalSerialStream.StopBits = value;
536                 stopBits = value;
537             }
538         }
539
540         [Browsable(true),
541         DefaultValue(defaultWriteBufferSize),
542         MonitoringDescription(SR.WriteBufferSize)]
543         public int WriteBufferSize {
544             get {
545                 return writeBufferSize;
546             }
547             set {
548                 if (value <= 0)
549                     throw new ArgumentOutOfRangeException("value");
550
551                 if (IsOpen)
552                     throw new InvalidOperationException(SR.GetString(SR.Cant_be_set_when_open, "value"));
553
554                 writeBufferSize = value;
555             }
556         }
557
558         // timeout for all write operations.  May be set to SerialPort.InfiniteTimeout or any positive value
559         [Browsable(true),
560         DefaultValue(defaultWriteTimeout),
561         MonitoringDescription(SR.WriteTimeout)]
562         public int WriteTimeout
563         {
564             get
565             {
566                 return writeTimeout;
567             }
568             set
569             {
570                 if (value <= 0 && value != SerialPort.InfiniteTimeout)
571                     throw new ArgumentOutOfRangeException("WriteTimeout", SR.GetString(SR.ArgumentOutOfRange_WriteTimeout));
572                 
573                 if (IsOpen)
574                     internalSerialStream.WriteTimeout = value;
575                 writeTimeout = value;
576             }
577         }
578
579
580
581         // -------- SECTION: constructors -----------------*
582         public SerialPort(System.ComponentModel.IContainer container)
583         {
584             ///
585             /// Required for Windows.Forms Class Composition Designer support
586             ///
587             container.Add(this);
588         }
589
590         public SerialPort()
591         {
592         }
593
594         // Non-design SerialPort constructors here chain, using default values for members left unspecified by parameters
595         // Note: Calling SerialPort() does not open a port connection but merely instantiates an object.
596         //     : A connection must be made using SerialPort's Open() method.
597         [ResourceExposure(ResourceScope.Machine)]
598         [ResourceConsumption(ResourceScope.Machine)]
599         public SerialPort(string portName) : this (portName, defaultBaudRate, defaultParity, defaultDataBits, defaultStopBits)
600         {
601         }
602
603         [ResourceExposure(ResourceScope.Machine)]
604         [ResourceConsumption(ResourceScope.Machine)]
605         public SerialPort(string portName, int baudRate) : this (portName, baudRate, defaultParity, defaultDataBits, defaultStopBits)
606         {
607         }
608
609         [ResourceExposure(ResourceScope.Machine)]
610         [ResourceConsumption(ResourceScope.Machine)]
611         public SerialPort(string portName, int baudRate, Parity parity) : this (portName, baudRate, parity, defaultDataBits, defaultStopBits)
612         {
613         }
614
615         [ResourceExposure(ResourceScope.Machine)]
616         [ResourceConsumption(ResourceScope.Machine)]
617         public SerialPort(string portName, int baudRate, Parity parity, int dataBits) : this (portName, baudRate, parity, dataBits, defaultStopBits)
618         {
619         }
620
621         // all the magic happens in the call to the instance's .Open() method.
622         // Internally, the SerialStream constructor opens the file handle, sets the device
623         // control block and associated Win32 structures, and begins the event-watching cycle.
624         [ResourceExposure(ResourceScope.Machine)]
625         [ResourceConsumption(ResourceScope.Machine)]
626         public SerialPort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
627         {
628             this.PortName = portName;
629             this.BaudRate = baudRate;
630             this.Parity = parity;
631             this.DataBits = dataBits;
632             this.StopBits = stopBits;
633         }
634
635         // Calls internal Serial Stream's Close() method on the internal Serial Stream.
636         public void Close()
637         {
638             Dispose();
639         }
640
641         protected override void Dispose( bool disposing )
642         {
643             if( disposing ) {
644                 if (IsOpen) {
645                     internalSerialStream.Flush();
646                     internalSerialStream.Close();
647                     internalSerialStream = null;
648                 }
649             }
650             base.Dispose( disposing );
651         }
652
653         public void DiscardInBuffer()
654         {
655             if (!IsOpen)
656                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
657             internalSerialStream.DiscardInBuffer();
658             readPos = readLen = 0;
659         }
660
661         public void DiscardOutBuffer()
662         {
663             if (!IsOpen)
664                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
665             internalSerialStream.DiscardOutBuffer();
666         }
667
668         [ResourceExposure(ResourceScope.Machine)]
669         [ResourceConsumption(ResourceScope.Machine)]
670         public static string[] GetPortNames() {
671             RegistryKey baseKey = null;
672             RegistryKey serialKey = null;
673             
674             String[] portNames = null;
675
676             RegistryPermission registryPermission = new RegistryPermission(RegistryPermissionAccess.Read, 
677                                     @"HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM");                                    
678             registryPermission.Assert();
679
680             try {
681                 baseKey = Registry.LocalMachine;
682                 serialKey = baseKey.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", false);
683
684                 if (serialKey != null) {
685
686                     string[] deviceNames = serialKey.GetValueNames();
687                     portNames = new String[deviceNames.Length];
688
689                     for (int i=0; i<deviceNames.Length; i++)
690                         portNames[i] = (string)serialKey.GetValue(deviceNames[i]);    
691                 }
692             }
693             finally {
694                 if (baseKey != null) 
695                     baseKey.Close();
696                 
697                 if (serialKey != null) 
698                     serialKey.Close();
699                 
700                 RegistryPermission.RevertAssert();
701             }
702
703             // If serialKey didn't exist for some reason
704             if (portNames == null) 
705                 portNames = new String[0];
706
707             return portNames;
708         }
709
710 #if NYT
711         public static string[] GetPortNames() {
712             if (Environment.OSVersion.Platform == PlatformID.Win32Windows)
713                 throw new PlatformNotSupportedException(SR.GetString(SR.NotSupportedOS));
714             
715             // Get all the registered serial device names
716             RegistryPermission registryPermission = new RegistryPermission(PermissionState.Unrestricted);
717             registryPermission.Assert();
718
719             RegistryKey baseKey = null;
720             RegistryKey serialKey = null;
721             
722             Hashtable portNames = new Hashtable(10);
723
724             try {
725                 baseKey = Registry.LocalMachine;
726                 serialKey = baseKey.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", true);
727
728                 if (serialKey != null) {
729
730                     string[] devices = serialKey.GetValueNames();
731                     for (int j=0; j<devices.Length; j++) {
732                         portNames.Add(devices[j], null);   
733                     }
734                 }
735             }
736             finally {
737                 if (baseKey != null) 
738                     baseKey.Close();
739                 
740                 if (serialKey != null) 
741                     serialKey.Close();
742                 
743                 RegistryPermission.RevertAssert();
744             }
745             
746             // Get all the MS-DOS names on the local machine 
747             //(sending null for lpctstrName gets all the names)
748             int dataSize;
749             char[] buffer = CallQueryDosDevice(null, out dataSize); 
750
751             // From QueryDosDevice, we get back a long string where the names are delimited by \0 and the end
752             // of the string is indicated by two \0s
753             ArrayList names = new ArrayList();
754             ArrayList deviceNames = new ArrayList();
755
756             int i=0;
757             while (i < dataSize) {
758                 // Walk through the buffer building a name until we hit the delimiter \0
759                 int start = i;
760                 while (buffer[i] != '\0') {
761                     i++;
762                 }
763
764                 if (i != start) {
765                     // We now have an MS-DOS name (the common name). We call QueryDosDevice again with
766                     // this name to get the underlying system name mapped to the MS-DOS name. 
767                     string currentName = (new String(buffer, start, i-start)).Trim();
768                     int nameSize;
769                     char[] nameBuffer = CallQueryDosDevice(currentName, out nameSize);
770
771                     // If we got a system name, see if it's a serial port name. If it is, add the common name
772                     // to our list
773                     if (nameSize > 0) {
774                         // internalName will include the trailing null chars as well as any additional
775                         // names that may get returned.  This is ok, since we are only interested in the
776                         // first name and we can use StartsWith. 
777                         string internalName = new string(nameBuffer, 0, nameSize-2).Trim();
778                         
779                         if (internalName.StartsWith(SERIAL_NAME) || portNames.ContainsKey(internalName)) {
780                             names.Add(currentName);
781                             deviceNames.Add(internalName);
782                         }
783                     }
784                 }
785                 i++;
786             }
787             
788             string[] namesArray = new String[names.Count];
789             names.CopyTo(namesArray);
790
791             string[] deviceNamesArray = new String[deviceNames.Count];
792             deviceNames.CopyTo(deviceNamesArray);
793
794             // sort the common names according to their actual device ordering
795             Array.Sort(deviceNamesArray, namesArray, Comparer.DefaultInvariant);
796             
797             return namesArray;
798         }
799         
800         private static unsafe char[] CallQueryDosDevice(string name, out int dataSize) {
801             char[] buffer = new char[1024];
802
803             fixed (char *bufferPtr = buffer) {
804                 dataSize =  UnsafeNativeMethods.QueryDosDevice(name, buffer, buffer.Length);
805                 while (dataSize <= 0) {
806                     int lastError = Marshal.GetLastWin32Error();
807                     if (lastError == NativeMethods.ERROR_INSUFFICIENT_BUFFER || lastError == NativeMethods.ERROR_MORE_DATA) {
808                         buffer = new char[buffer.Length * 2];
809                         dataSize = UnsafeNativeMethods.QueryDosDevice(null, buffer, buffer.Length);
810                     }
811                     else {
812                         throw new Win32Exception();
813                     }
814                 }
815             }
816             return buffer;
817         }
818 #endif
819         
820         // SerialPort is open <=> SerialPort has an associated SerialStream.
821         // The two statements are functionally equivalent here, so this method basically calls underlying Stream's
822         // constructor from the main properties specified in SerialPort: baud, stopBits, parity, dataBits,
823         // comm portName, handshaking, and timeouts.
824         [ResourceExposure(ResourceScope.None)]  // Look at Name property
825         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
826         public void Open()
827         {
828             if (IsOpen)
829                 throw new InvalidOperationException(SR.GetString(SR.Port_already_open));
830
831             // Demand unmanaged code permission
832             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
833
834             internalSerialStream = new SerialStream(portName, baudRate, parity, dataBits, stopBits, readTimeout,
835                 writeTimeout, handshake, dtrEnable, rtsEnable, discardNull, parityReplace);
836
837             internalSerialStream.SetBufferSizes(readBufferSize, writeBufferSize);
838
839             internalSerialStream.ErrorReceived += new SerialErrorReceivedEventHandler(CatchErrorEvents);
840             internalSerialStream.PinChanged += new SerialPinChangedEventHandler(CatchPinChangedEvents);
841             internalSerialStream.DataReceived += new SerialDataReceivedEventHandler(CatchReceivedEvents);
842         }
843
844         // Read Design pattern:
845         //  : ReadChar() returns the first available full char if found before, throws TimeoutExc if timeout.
846         //  : Read(byte[] buffer..., int count) returns all data available before read timeout expires up to *count* bytes
847         //  : Read(char[] buffer..., int count) returns all data available before read timeout expires up to *count* chars.
848         //  :                                   Note, this does not return "half-characters".
849         //  : ReadByte() is the binary analogue of the first one.
850         //  : ReadLine(): returns null string on timeout, saves received data in buffer
851         //  : ReadAvailable(): returns all full characters which are IMMEDIATELY available.
852
853         public int Read(byte[] buffer, int offset, int count)
854         {
855             if (!IsOpen)
856                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
857             if (buffer==null)
858                 throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
859             if (offset < 0)
860                 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
861             if (count < 0)
862                 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
863             if (buffer.Length - offset < count)
864                 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
865             int bytesReadToBuffer=0;
866
867             // if any bytes available in internal buffer, return those without calling any read ops.
868             if (CachedBytesToRead >= 1)
869             {
870                 bytesReadToBuffer = Math.Min(CachedBytesToRead, count);
871                 Buffer.BlockCopy(inBuffer, readPos, buffer, offset, bytesReadToBuffer);
872                 readPos += bytesReadToBuffer;
873                 if (bytesReadToBuffer == count) {
874                     if (readPos == readLen) readPos = readLen = 0;  // just a check to see if we can reset buffer
875                     return count;
876                 }
877
878                 // if we have read some bytes but there's none immediately available, return.
879                 if (BytesToRead == 0) 
880                     return bytesReadToBuffer;
881             }
882
883             Debug.Assert(CachedBytesToRead == 0, "there should be nothing left in our internal buffer");
884             readLen = readPos = 0;
885
886             int bytesLeftToRead = count - bytesReadToBuffer;
887
888             // request to read the requested number of bytes to fulfill the contract,
889             // doesn't matter if we time out.  We still return all the data we have available.
890             bytesReadToBuffer += internalSerialStream.Read(buffer, offset + bytesReadToBuffer, bytesLeftToRead);
891
892             decoder.Reset();
893             return bytesReadToBuffer;
894         }
895
896         // publicly exposed "ReadOneChar"-type: Read()
897         // reads one full character from the stream
898         public int ReadChar()
899         {
900             if (!IsOpen)
901                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
902
903             return ReadOneChar(readTimeout);
904         }
905
906         // gets next available full character, which may be from the buffer, the stream, or both.
907         // this takes size^2 time at most, where *size* is the maximum size of any one character in an encoding.
908         // The user can call Read(1) to mimic this functionality.
909
910         // We can replace ReadOneChar with Read at some point
911         private int ReadOneChar(int timeout)
912         {
913             int nextByte;
914             int timeUsed = 0;
915             Debug.Assert(IsOpen, "ReadOneChar - port not open");
916
917             // case 1: we have >= 1 character in the internal buffer.
918             if (decoder.GetCharCount(inBuffer, readPos, CachedBytesToRead) != 0)
919             {
920                 int beginReadPos = readPos;
921                 // get characters from buffer.
922                 do
923                 {
924                     readPos++;
925                 } while (decoder.GetCharCount(inBuffer, beginReadPos, readPos - beginReadPos) < 1);
926
927                 try {
928                     decoder.GetChars(inBuffer, beginReadPos, readPos - beginReadPos, oneChar, 0);
929                 }
930                 catch {
931
932                     // Handle surrogate chars correctly, restore readPos
933                     readPos = beginReadPos;
934                     throw;
935                 }
936                 return oneChar[0];
937             }
938             else
939             {
940
941                 // need to return immediately.
942                 if (timeout == 0) {
943                     // read all bytes in the serial driver in here.  Make sure we ask for at least 1 byte
944                     // so that we get the proper timeout behavior
945                     int bytesInStream = internalSerialStream.BytesToRead; 
946                     if (bytesInStream == 0)
947                         bytesInStream = 1;
948                     MaybeResizeBuffer(bytesInStream);
949                     readLen += internalSerialStream.Read(inBuffer, readLen, bytesInStream); // read all immediately avail.
950
951                     // If what we have in the buffer is not enough, throw TimeoutExc
952                     // if we are reading surrogate char then ReadBufferIntoChars 
953                     // will throw argexc and that is okay as readPos is not altered
954                     if (ReadBufferIntoChars(oneChar, 0, 1, false) == 0) 
955                         throw new TimeoutException();
956                     else 
957                         return oneChar[0];
958                 }
959
960                 // case 2: we need to read from outside to find this.
961                 // timeout is either infinite or positive.
962                 int startTicks = Environment.TickCount;
963                 do
964                 {
965                     if (timeout == SerialPort.InfiniteTimeout)
966                         nextByte = internalSerialStream.ReadByte(InfiniteTimeout);
967                     else if (timeout - timeUsed >= 0) {
968                         nextByte = internalSerialStream.ReadByte(timeout - timeUsed);
969                         timeUsed = Environment.TickCount - startTicks;
970                     }
971                     else
972                         throw new TimeoutException();
973
974                     MaybeResizeBuffer(1);
975                     inBuffer[readLen++] = (byte) nextByte;  // we must add to the end of the buffer
976                 } while (decoder.GetCharCount(inBuffer, readPos, readLen - readPos) < 1);
977             }
978
979             // If we are reading surrogate char then this will throw argexc 
980             // we need not deal with that exc because we have not altered readPos yet.
981             decoder.GetChars(inBuffer, readPos, readLen - readPos, oneChar, 0);
982
983             // Everything should be out of inBuffer now.  We'll just reset the pointers. 
984             readLen = readPos = 0;
985             return oneChar[0];
986         }
987
988         // Will return 'n' (1 < n < count) characters (or) TimeoutExc
989         public int Read(char[] buffer, int offset, int count)
990         {
991             if (!IsOpen)
992                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
993             if (buffer==null)
994                 throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
995             if (offset < 0)
996                 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
997             if (count < 0)
998                 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
999             if (buffer.Length - offset < count)
1000                 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
1001
1002             return InternalRead(buffer, offset, count, readTimeout, false);
1003         }
1004
1005         private int InternalRead(char[] buffer, int offset, int count, int timeout, bool countMultiByteCharsAsOne)
1006         {
1007             Debug.Assert(IsOpen, "port not open!");
1008             Debug.Assert(buffer!=null, "invalid buffer!");
1009             Debug.Assert(offset >= 0, "invalid offset!");
1010             Debug.Assert(count >= 0, "invalid count!");
1011             Debug.Assert(buffer.Length - offset >= count, "invalid offset/count!");
1012
1013             if (count == 0) return 0;   // immediately return on zero chars desired.  This simplifies things later.
1014
1015             // Get the startticks before we read the underlying stream
1016             int startTicks = Environment.TickCount;
1017             
1018             // read everything else into internal buffer, which we know we can do instantly, and see if we NOW have enough.
1019             int bytesInStream = internalSerialStream.BytesToRead;
1020             MaybeResizeBuffer(bytesInStream);
1021             readLen += internalSerialStream.Read(inBuffer, readLen, bytesInStream);    // should execute instantaneously.
1022
1023             int charsWeAlreadyHave = decoder.GetCharCount(inBuffer, readPos, CachedBytesToRead); // full chars already in our buffer
1024             if (charsWeAlreadyHave > 0)
1025             {
1026                 // we found some chars after reading everything the SerialStream had to offer.  We'll return what we have
1027                 // rather than wait for more. 
1028                 return ReadBufferIntoChars(buffer, offset, count, countMultiByteCharsAsOne);
1029             }
1030
1031             if (timeout == 0) 
1032                 throw new TimeoutException();
1033
1034             // else: we need to do incremental reads from the stream.
1035             // -----
1036             // our internal algorithm for finding exactly n characters is a bit complicated, but must overcome the
1037             // hurdle of NEVER READING TOO MANY BYTES from the Stream, since we can time out.  A variable-length encoding
1038             // allows anywhere between minimum and maximum bytes per char times number of chars to be the exactly correct
1039             // target, and we have to take care not to overuse GetCharCount().  The problem is that GetCharCount() will never tell
1040             // us if we've read "half" a character in our current set of collected bytes; it underestimates.
1041             // size = maximum bytes per character in the encoding.  n = number of characters requested.
1042             // Solution I: Use ReadOneChar() to read successive characters until we get to n.
1043             // Read calls: size * n; GetCharCount calls: size * n; each byte "counted": size times.
1044             // Solution II: Use a binary reduction and backtracking to reduce the number of calls.
1045             // Read calls: size * log n; GetCharCount calls: size * log n; each byte "counted": size * (log n) / n times.
1046             // We use the second, more complicated solution here.  Note log is actually log_(size/size - 1)...
1047
1048
1049             // we need to read some from the stream
1050             // read *up to* the maximum number of bytes from the stream
1051             // we can read more since we receive everything instantaneously, and we don't have enough,
1052             // so when we do receive any data, it will be necessary and sufficient.
1053
1054             int justRead;
1055             int maxReadSize = Encoding.GetMaxByteCount(count); 
1056             do {
1057                 MaybeResizeBuffer(maxReadSize);
1058                 
1059                 readLen += internalSerialStream.Read(inBuffer, readLen, maxReadSize);
1060                 justRead = ReadBufferIntoChars(buffer, offset, count, countMultiByteCharsAsOne);
1061                 if (justRead > 0) {
1062                     return justRead;
1063                 }
1064             } while (timeout == SerialPort.InfiniteTimeout || (timeout - GetElapsedTime(Environment.TickCount, startTicks) > 0));
1065
1066             // must've timed out w/o getting a character.
1067             throw new TimeoutException();
1068         }
1069
1070         // ReadBufferIntoChars reads from Serial Port's inBuffer up to *count* chars and
1071         // places them in *buffer* starting at *offset*.
1072         // This does not call any stream Reads, and so takes "no time".
1073         // If the buffer specified is insufficient to accommodate surrogate characters
1074         // the call to underlying Decoder.GetChars will throw argexc. 
1075         private int ReadBufferIntoChars(char[] buffer, int offset, int count, bool countMultiByteCharsAsOne)
1076         {
1077             Debug.Assert(count != 0, "Count should never be zero.  We will probably see bugs further down if count is 0.");
1078
1079             int bytesToRead = Math.Min(count, CachedBytesToRead);
1080
1081             // There are lots of checks to determine if this really is a single byte encoding with no
1082             // funky fallbacks that would make it not single byte
1083             DecoderReplacementFallback fallback = encoding.DecoderFallback as DecoderReplacementFallback;
1084             if (encoding.IsSingleByte && encoding.GetMaxCharCount(bytesToRead) == bytesToRead && 
1085                 fallback != null && fallback.MaxCharCount == 1)
1086             {   
1087                 // kill ASCII/ANSI encoding easily.
1088                 // read at least one and at most *count* characters
1089                 decoder.GetChars(inBuffer, readPos, bytesToRead, buffer, offset);
1090
1091                 readPos += bytesToRead;
1092                 if (readPos == readLen) readPos = readLen = 0;
1093                 return bytesToRead;
1094             }
1095             else
1096             {
1097                 //
1098                 // We want to turn inBuffer into at most count chars.  This algorithm basically works like this:
1099                 // 1) Take the largest step possible that won't give us too many chars
1100                 // 2) If we find some chars, walk backwards until we find exactly how many bytes
1101                 //    they occupy.  lastFullCharPos points to the end of the full chars.
1102                 // 3) if we don't have enough chars for the buffer, goto #1
1103
1104                 int totalBytesExamined = 0; // total number of Bytes in inBuffer we've looked at
1105                 int totalCharsFound = 0;     // total number of chars we've found in inBuffer, totalCharsFound <= totalBytesExamined
1106                 int currentBytesToExamine; // the number of additional bytes to examine for characters
1107                 int currentCharsFound; // the number of additional chars found after examining currentBytesToExamine extra bytes
1108                 int lastFullCharPos = readPos; // first index AFTER last full char read, capped at ReadLen.
1109                 do
1110                 {
1111                     currentBytesToExamine = Math.Min(count - totalCharsFound, readLen - readPos - totalBytesExamined);
1112                     if (currentBytesToExamine <= 0)
1113                         break;
1114
1115                     totalBytesExamined += currentBytesToExamine;
1116                     // recalculate currentBytesToExamine so that it includes leftover bytes from the last iteration. 
1117                     currentBytesToExamine = readPos + totalBytesExamined - lastFullCharPos; 
1118
1119                     // make sure we don't go beyond the end of the valid data that we have. 
1120                     Debug.Assert((lastFullCharPos + currentBytesToExamine) <= readLen, "We should never be attempting to read more bytes than we have");
1121                     
1122                     currentCharsFound = decoder.GetCharCount(inBuffer, lastFullCharPos, currentBytesToExamine);
1123
1124                     if (currentCharsFound > 0)
1125                     {
1126                         if ((totalCharsFound + currentCharsFound) > count) {
1127
1128                             // Multibyte unicode sequence (possibly surrogate chars) 
1129                             // at the end of the buffer. We should not split the sequence, 
1130                             // instead return with less chars now and defer reading them 
1131                             // until next time
1132                             if (!countMultiByteCharsAsOne) 
1133                                 break;
1134
1135                             // If we are here it is from ReadTo which attempts to read one logical character 
1136                             // at a time. The supplied singleCharBuffer should be large enough to accommodate 
1137                             // this multi-byte char
1138                             Debug.Assert((buffer.Length - offset - totalCharsFound) >= currentCharsFound, "internal buffer to read one full unicode char sequence is not sufficient!");
1139                         }
1140
1141                         // go backwards until we know we have a full set of currentCharsFound bytes with no extra lead-bytes.
1142                         int foundCharsByteLength = currentBytesToExamine;
1143                         do
1144                         {
1145                             foundCharsByteLength--;
1146                         } while (decoder.GetCharCount(inBuffer, lastFullCharPos, foundCharsByteLength) == currentCharsFound);
1147
1148                         // Fill into destination buffer all the COMPLETE characters we've read.
1149                         // If the buffer specified is insufficient to accommodate surrogate character
1150                         // the call to underlying Decoder.GetChars will throw argexc. We need not 
1151                         // deal with this exc because we have not altered readPos yet.
1152                         decoder.GetChars(inBuffer, lastFullCharPos, foundCharsByteLength + 1, buffer, offset + totalCharsFound);
1153                         lastFullCharPos = lastFullCharPos + foundCharsByteLength + 1; // update the end position of last known char.
1154                     }
1155
1156                     totalCharsFound += currentCharsFound;
1157                 } while ((totalCharsFound < count) && (totalBytesExamined < CachedBytesToRead));
1158
1159                 readPos = lastFullCharPos;
1160
1161                 if (readPos == readLen) readPos = readLen = 0;
1162                 return totalCharsFound;
1163             }
1164         }
1165
1166         public int ReadByte()
1167         {
1168             if (!IsOpen)
1169                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1170             if (readLen != readPos)         // stuff left in buffer, so we can read from it
1171                 return inBuffer[readPos++];
1172
1173             decoder.Reset();
1174             return internalSerialStream.ReadByte(); // otherwise, ask the stream.
1175         }
1176
1177         public string ReadExisting()
1178         {
1179             if (!IsOpen)
1180                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1181
1182             byte [] bytesReceived = new byte[BytesToRead];
1183
1184             if (readPos < readLen)
1185             {           // stuff in internal buffer
1186                 Buffer.BlockCopy(inBuffer, readPos, bytesReceived, 0, CachedBytesToRead);
1187             }
1188             internalSerialStream.Read(bytesReceived, CachedBytesToRead, bytesReceived.Length - (CachedBytesToRead));    // get everything
1189             // Read full characters and leave partial input in the buffer. Encoding.GetCharCount doesn't work because
1190             // it returns fallback characters on partial input, meaning that it overcounts. Instead, we use 
1191             // GetCharCount from the decoder and tell it to preserve state, so that it returns the count of full 
1192             // characters. Note that we don't actually want it to preserve state, so we call the decoder as if it's 
1193             // preserving state and then call Reset in between calls. This uses a local decoder instead of the class 
1194             // member decoder because that one may preserve state across SerialPort method calls.
1195             Decoder localDecoder = Encoding.GetDecoder();
1196             int numCharsReceived = localDecoder.GetCharCount(bytesReceived, 0, bytesReceived.Length);
1197             int lastFullCharIndex = bytesReceived.Length;
1198             
1199             if (numCharsReceived == 0)
1200             {
1201                 Buffer.BlockCopy(bytesReceived, 0, inBuffer, 0, bytesReceived.Length); // put it all back!
1202                 // don't change readPos. --> readPos == 0?
1203                 readPos = 0;
1204                 readLen = bytesReceived.Length;
1205                 return "";
1206             }
1207
1208             do 
1209             {
1210                 localDecoder.Reset();
1211                 lastFullCharIndex--;
1212             } while (localDecoder.GetCharCount(bytesReceived, 0, lastFullCharIndex) == numCharsReceived);
1213
1214             readPos = 0;
1215             readLen = bytesReceived.Length - (lastFullCharIndex + 1);
1216
1217             Buffer.BlockCopy(bytesReceived, lastFullCharIndex + 1, inBuffer, 0, bytesReceived.Length - (lastFullCharIndex + 1));
1218             return Encoding.GetString(bytesReceived, 0, lastFullCharIndex + 1);
1219         }
1220
1221         public string ReadLine() {
1222             return ReadTo(NewLine);
1223         }
1224
1225         public string ReadTo(string value)
1226         {
1227             if (!IsOpen)
1228                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1229             if (value == null)
1230                 throw new ArgumentNullException("value");
1231             if (value.Length == 0)
1232                 throw new ArgumentException(SR.GetString(SR.InvalidNullEmptyArgument, "value"));
1233                 
1234             int startTicks = Environment.TickCount;
1235             int numCharsRead;
1236             int timeUsed = 0;
1237             int timeNow;
1238             StringBuilder currentLine = new StringBuilder();
1239             char lastValueChar = value[value.Length-1];
1240
1241             // for timeout issues, best to read everything already on the stream into our buffers.
1242             // first make sure inBuffer is big enough
1243             int bytesInStream = internalSerialStream.BytesToRead;
1244             MaybeResizeBuffer(bytesInStream);
1245
1246             readLen += internalSerialStream.Read(inBuffer, readLen, bytesInStream);
1247             int beginReadPos = readPos;
1248
1249             if (singleCharBuffer == null) {
1250                 // This is somewhat of an approximate guesstimate to get the max char[] size needed to encode a single character
1251                 singleCharBuffer = new char[maxByteCountForSingleChar];
1252             }
1253
1254             try {
1255                 while (true)
1256                 {
1257                     if(readTimeout == InfiniteTimeout) {
1258                         numCharsRead = InternalRead(singleCharBuffer, 0, 1, readTimeout, true);
1259                     }
1260                     else if (readTimeout - timeUsed >= 0) {
1261                         timeNow = Environment.TickCount;
1262                         numCharsRead = InternalRead(singleCharBuffer, 0, 1, readTimeout - timeUsed, true);
1263                         timeUsed += Environment.TickCount - timeNow;
1264                     }
1265                     else 
1266                         throw new TimeoutException();
1267                     
1268 #if _DEBUG
1269                     if (numCharsRead > 1) {
1270                         for (int i=0; i<numCharsRead; i++)
1271                             Debug.Assert((Char.IsSurrogate(singleCharBuffer[i])), "number of chars read should be more than one only for surrogate characters!");
1272                     }
1273 #endif
1274                     Debug.Assert((numCharsRead > 0), "possible bug in ReadBufferIntoChars, reading surrogate char?");
1275                     currentLine.Append(singleCharBuffer, 0, numCharsRead);
1276                     
1277                     if (lastValueChar == (char) singleCharBuffer[numCharsRead-1] && (currentLine.Length >= value.Length)) {
1278                         // we found the last char in the value string.  See if the rest is there.  No need to
1279                         // recompare the last char of the value string.
1280                         bool found = true;
1281                         for (int i=2; i<=value.Length; i++) {
1282                             if (value[value.Length-i] != currentLine[currentLine.Length-i]) {
1283                                 found = false;
1284                                 break;
1285                             }
1286                         }
1287
1288                         if (found) {
1289                             // we found the search string.  Exclude it from the return string.
1290                             string ret = currentLine.ToString(0, currentLine.Length - value.Length);
1291                             if (readPos == readLen) readPos = readLen = 0;
1292                             return ret;
1293                         }
1294                     }
1295                 }
1296             }
1297             catch {
1298                 // We probably got here due to timeout. 
1299                 // We will try our best to restore the internal states, it's tricky!
1300                 
1301                 // 0) Save any existing data
1302                 // 1) Restore readPos to the original position upon entering ReadTo 
1303                 // 2) Set readLen to the number of bytes read since entering ReadTo
1304                 // 3) Restore inBuffer so that it contains the bytes from currentLine, resizing if necessary.
1305                 // 4) Append the buffer with any saved data from 0) 
1306                 
1307                 byte[] readBuffer = encoding.GetBytes(currentLine.ToString());
1308                 
1309                 // We will compact the data by default
1310                 if (readBuffer.Length > 0) {
1311                     int bytesToSave = CachedBytesToRead;
1312                     byte[] savBuffer = new byte[bytesToSave];
1313                         
1314                     if (bytesToSave > 0) 
1315                         Buffer.BlockCopy(inBuffer, readPos, savBuffer, 0, bytesToSave);
1316
1317                     readPos = 0;
1318                     readLen = 0;
1319
1320                     MaybeResizeBuffer(readBuffer.Length + bytesToSave);
1321
1322                     Buffer.BlockCopy(readBuffer, 0, inBuffer, readLen, readBuffer.Length);
1323                     readLen += readBuffer.Length;
1324
1325                     if (bytesToSave > 0) {
1326                         Buffer.BlockCopy(savBuffer, 0, inBuffer, readLen, bytesToSave);
1327                         readLen += bytesToSave; 
1328                     }
1329                 }
1330                 
1331                 throw;
1332             }
1333         }
1334         
1335         // Writes string to output, no matter string's length.
1336         public void Write(string text)
1337         {
1338             if (!IsOpen)
1339                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1340             if (text == null)
1341                 throw new ArgumentNullException("text");
1342             if (text.Length == 0) return;
1343             byte [] bytesToWrite;
1344
1345             bytesToWrite = encoding.GetBytes(text);
1346
1347             internalSerialStream.Write(bytesToWrite, 0, bytesToWrite.Length, writeTimeout);
1348         }
1349
1350         // encoding-dependent Write-chars method.
1351         // Probably as performant as direct conversion from ASCII to bytes, since we have to cast anyway (we can just call GetBytes)
1352         public void Write(char[] buffer, int offset, int count) {
1353             if (!IsOpen)
1354                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1355             if (buffer == null)
1356                 throw new ArgumentNullException("buffer");
1357             if (offset < 0)
1358                 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1359             if (count < 0)
1360                 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1361             if (buffer.Length - offset < count)
1362                 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
1363
1364             if (buffer.Length == 0) return;
1365
1366             byte [] byteArray = Encoding.GetBytes(buffer,offset, count);
1367             Write(byteArray, 0, byteArray.Length);
1368
1369         }
1370
1371         // Writes a specified section of a byte buffer to output.
1372         public void Write(byte[] buffer, int offset, int count)
1373         {
1374             if (!IsOpen)
1375                 throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
1376             if (buffer==null)
1377                 throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
1378             if (offset < 0)
1379                 throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1380             if (count < 0)
1381                 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
1382             if (buffer.Length - offset < count)
1383                 throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
1384             if (buffer.Length == 0) return;
1385
1386             internalSerialStream.Write(buffer, offset, count, writeTimeout);
1387         }
1388
1389         public void WriteLine(string text) {
1390             Write(text + NewLine);
1391         }
1392
1393
1394         // ----- SECTION: internal utility methods ----------------*
1395
1396         // included here just to use the event filter to block unwanted invocations of the Serial Port's events.
1397         // Plus, this enforces the requirement on the received event that the number of buffered bytes >= receivedBytesThreshold
1398         private void CatchErrorEvents(object src, SerialErrorReceivedEventArgs e)
1399         {
1400             SerialErrorReceivedEventHandler eventHandler = ErrorReceived;
1401             SerialStream stream = internalSerialStream;
1402
1403             if ((eventHandler != null) && (stream != null)){
1404                 lock (stream) {
1405                     if (stream.IsOpen)
1406                         eventHandler(this, e);
1407                 }
1408             }
1409         }
1410
1411         private void CatchPinChangedEvents(object src, SerialPinChangedEventArgs e)
1412         {
1413             SerialPinChangedEventHandler eventHandler = PinChanged;
1414             SerialStream stream = internalSerialStream;
1415
1416             if ((eventHandler != null) && (stream != null)){
1417                 lock (stream) {
1418                     if (stream.IsOpen)
1419                         eventHandler(this, e);
1420                 }
1421             }
1422         }
1423
1424         private void CatchReceivedEvents(object src, SerialDataReceivedEventArgs e)
1425         {
1426             SerialDataReceivedEventHandler eventHandler = DataReceived;
1427             SerialStream stream = internalSerialStream;
1428
1429             if ((eventHandler != null) && (stream != null)){
1430                 lock (stream) {
1431                     // SerialStream might be closed between the time the event runner
1432                     // pumped this event and the time the threadpool thread end up 
1433                     // invoking this event handler. The above lock and IsOpen check 
1434                     // ensures that we raise the event only when the port is open
1435
1436                     bool raiseEvent = false;
1437                     try {
1438                         raiseEvent = stream.IsOpen && (SerialData.Eof == e.EventType || BytesToRead >= receivedBytesThreshold);    
1439                     }
1440                     catch {
1441                         // Ignore and continue. SerialPort might have been closed already! 
1442                     }
1443                     finally {
1444                         if (raiseEvent)
1445                             eventHandler(this, e);  // here, do your reading, etc. 
1446                     }
1447                 }
1448             }
1449         }
1450
1451         private void CompactBuffer()
1452         {
1453             Buffer.BlockCopy(inBuffer, readPos, inBuffer, 0, CachedBytesToRead);
1454             readLen = CachedBytesToRead;
1455             readPos = 0;
1456         }
1457
1458         // This method guarantees that our inBuffer is big enough.  The parameter passed in is
1459         // the number of bytes that our code is going to add to inBuffer.  MaybeResizeBuffer will 
1460         // do one of three things depending on how much data is already in the buffer and how 
1461         // much will be added:
1462         // 1) Nothing.  The current buffer is big enough to hold it all
1463         // 2) Compact the existing data and keep the current buffer. 
1464         // 3) Create a new, larger buffer and compact the existing data into it.
1465         private void MaybeResizeBuffer(int additionalByteLength)
1466         {
1467             // Case 1.  No action needed
1468             if (additionalByteLength + readLen <= inBuffer.Length)
1469                 return;
1470
1471             // Case 2.  Compact                
1472             if (CachedBytesToRead + additionalByteLength <= inBuffer.Length / 2)
1473                 CompactBuffer();
1474             else {
1475                 // Case 3.  Create a new buffer
1476                 int newLength = Math.Max(CachedBytesToRead + additionalByteLength, inBuffer.Length * 2);
1477
1478                 Debug.Assert(inBuffer.Length >= readLen, "ResizeBuffer - readLen > inBuffer.Length");
1479                 byte[] newBuffer = new byte[newLength];
1480                 // only copy the valid data from inBuffer, and put it at the beginning of newBuffer.
1481                 Buffer.BlockCopy(inBuffer, readPos, newBuffer, 0, CachedBytesToRead);
1482                 readLen = CachedBytesToRead;
1483                 readPos = 0;
1484                 inBuffer = newBuffer;
1485             }
1486         }
1487
1488         private static int GetElapsedTime(int currentTickCount, int startTickCount)
1489         {
1490             int elapsedTime = unchecked(currentTickCount - startTickCount);
1491             return (elapsedTime >= 0) ? (int)elapsedTime : Int32.MaxValue;
1492         }
1493     }
1494 }