BindingFlags.Public needed here as Exception.HResult is now public in .NET 4.5. This...
[mono.git] / mcs / class / System / System.IO.Compression / DeflateStream.cs
1 /* -*- Mode: csharp; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 // 
3 // DeflateStream.cs
4 //
5 // Authors:
6 //      Christopher James Lahey <clahey@ximian.com>
7 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
8 //
9 // (c) Copyright 2004,2009 Novell, Inc. <http://www.novell.com>
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 #if NET_2_0
32 using System;
33 using System.IO;
34 using System.Runtime.InteropServices;
35 using System.Runtime.Remoting.Messaging;
36
37 #if MONOTOUCH
38 using MonoTouch;
39 #endif
40
41 namespace System.IO.Compression {
42         public class DeflateStream : Stream
43         {
44                 const int BufferSize = 4096;
45                 [UnmanagedFunctionPointer (CallingConvention.Cdecl)]
46                 delegate int UnmanagedReadOrWrite (IntPtr buffer, int length, IntPtr data);
47                 delegate int ReadMethod (byte[] array, int offset, int count);
48                 delegate void WriteMethod (byte[] array, int offset, int count);
49
50                 Stream base_stream;
51                 CompressionMode mode;
52                 bool leaveOpen;
53                 bool disposed;
54                 UnmanagedReadOrWrite feeder; // This will be passed to unmanaged code and used there
55                 IntPtr z_stream;
56                 byte [] io_buffer;
57
58                 GCHandle data;
59
60                 public DeflateStream (Stream compressedStream, CompressionMode mode) :
61                         this (compressedStream, mode, false, false)
62                 {
63                 }
64
65                 public DeflateStream (Stream compressedStream, CompressionMode mode, bool leaveOpen) :
66                         this (compressedStream, mode, leaveOpen, false)
67                 {
68                 }
69
70                 internal DeflateStream (Stream compressedStream, CompressionMode mode, bool leaveOpen, bool gzip)
71                 {
72                         if (compressedStream == null)
73                                 throw new ArgumentNullException ("compressedStream");
74
75                         if (mode != CompressionMode.Compress && mode != CompressionMode.Decompress)
76                                 throw new ArgumentException ("mode");
77
78                         this.data = GCHandle.Alloc (this);
79                         this.base_stream = compressedStream;
80                         this.feeder = (mode == CompressionMode.Compress) ? new UnmanagedReadOrWrite (UnmanagedWrite) :
81                                                                            new UnmanagedReadOrWrite (UnmanagedRead);
82                         this.z_stream = CreateZStream (mode, gzip, feeder, GCHandle.ToIntPtr (data));
83                         if (z_stream == IntPtr.Zero) {
84                                 this.base_stream = null;
85                                 this.feeder = null;
86                                 throw new NotImplementedException ("Failed to initialize zlib. You probably have an old zlib installed. Version 1.2.0.4 or later is required.");
87                         }
88                         this.mode = mode;
89                         this.leaveOpen = leaveOpen;
90                 }
91
92                 protected override void Dispose (bool disposing)
93                 {
94                         if (disposing && !disposed) {
95                                 disposed = true;
96                                 IntPtr zz = z_stream;
97                                 z_stream = IntPtr.Zero;
98                                 int res = 0;
99                                 if (zz != IntPtr.Zero)
100                                         res = CloseZStream (zz); // This will Flush() the remaining output if any
101
102                                 io_buffer = null;
103                                 if (!leaveOpen) {
104                                         Stream st = base_stream;
105                                         if (st != null)
106                                                 st.Close ();
107                                         base_stream = null;
108                                 }
109                                 CheckResult (res, "Dispose");
110                         }
111
112                         if (data.IsAllocated) {
113                                 data.Free ();
114                                 data = new GCHandle ();
115                         }
116
117                         base.Dispose (disposing);
118                 }
119
120 #if MONOTOUCH
121                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
122 #endif
123                 static int UnmanagedRead (IntPtr buffer, int length, IntPtr data)
124                 {
125                         GCHandle s = GCHandle.FromIntPtr (data);
126                         var self = s.Target as DeflateStream;
127                         if (self == null)
128                                 return -1;
129                         return self.UnmanagedRead (buffer, length);
130                 }
131
132                 int UnmanagedRead (IntPtr buffer, int length)
133                 {
134                         int total = 0;
135                         int n = 1;
136                         while (length > 0 && n > 0) {
137                                 if (io_buffer == null)
138                                         io_buffer = new byte [BufferSize];
139
140                                 int count = Math.Min (length, io_buffer.Length);
141                                 n = base_stream.Read (io_buffer, 0, count);
142                                 if (n > 0) {
143                                         Marshal.Copy (io_buffer, 0, buffer, n);
144                                         unsafe {
145                                                 buffer = new IntPtr ((byte *) buffer.ToPointer () + n);
146                                         }
147                                         length -= n;
148                                         total += n;
149                                 }
150                         }
151                         return total;
152                 }
153
154 #if MONOTOUCH
155                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
156 #endif
157                 static int UnmanagedWrite (IntPtr buffer, int length, IntPtr data)
158                 {
159                         GCHandle s = GCHandle.FromIntPtr (data);
160                         var self = s.Target as DeflateStream;
161                         if (self == null)
162                                 return -1;
163                         return self.UnmanagedWrite (buffer, length);
164                 }
165
166                 int UnmanagedWrite (IntPtr buffer, int length)
167                 {
168                         int total = 0;
169                         while (length > 0) {
170                                 if (io_buffer == null)
171                                         io_buffer = new byte [BufferSize];
172
173                                 int count = Math.Min (length, io_buffer.Length);
174                                 Marshal.Copy (buffer, io_buffer, 0, count);
175                                 base_stream.Write (io_buffer, 0, count);
176                                 unsafe {
177                                         buffer = new IntPtr ((byte *) buffer.ToPointer () + count);
178                                 }
179                                 length -= count;
180                                 total += count;
181                         }
182                         return total;
183                 }
184
185                 unsafe int ReadInternal (byte[] array, int offset, int count)
186                 {
187                         if (count == 0)
188                                 return 0;
189
190                         int result = 0;
191                         fixed (byte *b = array) {
192                                 IntPtr ptr = new IntPtr (b + offset);
193                                 result = ReadZStream (z_stream, ptr, count);
194                         }
195                         CheckResult (result, "ReadInternal");
196                         return result;
197                 }
198
199                 public override int Read (byte[] dest, int dest_offset, int count)
200                 {
201                         if (disposed)
202                                 throw new ObjectDisposedException (GetType ().FullName);
203                         if (dest == null)
204                                 throw new ArgumentNullException ("Destination array is null.");
205                         if (!CanRead)
206                                 throw new InvalidOperationException ("Stream does not support reading.");
207                         int len = dest.Length;
208                         if (dest_offset < 0 || count < 0)
209                                 throw new ArgumentException ("Dest or count is negative.");
210                         if (dest_offset > len)
211                                 throw new ArgumentException ("destination offset is beyond array size");
212                         if ((dest_offset + count) > len)
213                                 throw new ArgumentException ("Reading would overrun buffer");
214
215                         return ReadInternal (dest, dest_offset, count);
216                 }
217
218                 unsafe void WriteInternal (byte[] array, int offset, int count)
219                 {
220                         if (count == 0)
221                                 return;
222
223                         int result = 0;
224                         fixed (byte *b = array) {
225                                 IntPtr ptr = new IntPtr (b + offset);
226                                 result = WriteZStream (z_stream, ptr, count);
227                         }
228                         CheckResult (result, "WriteInternal");
229                 }
230
231                 public override void Write (byte[] src, int src_offset, int count)
232                 {
233                         if (disposed)
234                                 throw new ObjectDisposedException (GetType ().FullName);
235
236                         if (src == null)
237                                 throw new ArgumentNullException ("src");
238
239                         if (src_offset < 0)
240                                 throw new ArgumentOutOfRangeException ("src_offset");
241
242                         if (count < 0)
243                                 throw new ArgumentOutOfRangeException ("count");
244
245                         if (!CanWrite)
246                                 throw new NotSupportedException ("Stream does not support writing");
247
248                         WriteInternal (src, src_offset, count);
249                 }
250
251                 static void CheckResult (int result, string where)
252                 {
253                         if (result >= 0)
254                                 return;
255
256                         string error;
257                         switch (result) {
258                         case -1: // Z_ERRNO
259                                 error = "Unknown error"; // Marshal.GetLastWin32() ?
260                                 break;
261                         case -2: // Z_STREAM_ERROR
262                                 error = "Internal error";
263                                 break;
264                         case -3: // Z_DATA_ERROR
265                                 error = "Corrupted data";
266                                 break;
267                         case -4: // Z_MEM_ERROR
268                                 error = "Not enough memory";
269                                 break;
270                         case -5: // Z_BUF_ERROR
271                                 error = "Internal error (no progress possible)";
272                                 break;
273                         case -6: // Z_VERSION_ERROR
274                                 error = "Invalid version";
275                                 break;
276                         case -10:
277                                 error = "Invalid argument(s)";
278                                 break;
279                         case -11:
280                                 error = "IO error";
281                                 break;
282                         default:
283                                 error = "Unknown error";
284                                 break;
285                         }
286
287                         throw new IOException (error + " " + where);
288                 }
289
290                 public override void Flush ()
291                 {
292                         if (disposed)
293                                 throw new ObjectDisposedException (GetType ().FullName);
294
295                         if (CanWrite) {
296                                 int result = Flush (z_stream);
297                                 CheckResult (result, "Flush");
298                         }
299                 }
300
301                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
302                                                         AsyncCallback cback, object state)
303                 {
304                         if (disposed)
305                                 throw new ObjectDisposedException (GetType ().FullName);
306
307                         if (!CanRead)
308                                 throw new NotSupportedException ("This stream does not support reading");
309
310                         if (buffer == null)
311                                 throw new ArgumentNullException ("buffer");
312
313                         if (count < 0)
314                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
315
316                         if (offset < 0)
317                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
318
319                         if (count + offset > buffer.Length)
320                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
321
322                         ReadMethod r = new ReadMethod (ReadInternal);
323                         return r.BeginInvoke (buffer, offset, count, cback, state);
324                 }
325
326                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
327                                                         AsyncCallback cback, object state)
328                 {
329                         if (disposed)
330                                 throw new ObjectDisposedException (GetType ().FullName);
331
332                         if (!CanWrite)
333                                 throw new InvalidOperationException ("This stream does not support writing");
334
335                         if (buffer == null)
336                                 throw new ArgumentNullException ("buffer");
337
338                         if (count < 0)
339                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
340
341                         if (offset < 0)
342                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
343
344                         if (count + offset > buffer.Length)
345                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
346
347                         WriteMethod w = new WriteMethod (WriteInternal);
348                         return w.BeginInvoke (buffer, offset, count, cback, state);                     
349                 }
350
351                 public override int EndRead(IAsyncResult async_result)
352                 {
353                         if (async_result == null)
354                                 throw new ArgumentNullException ("async_result");
355
356                         AsyncResult ares = async_result as AsyncResult;
357                         if (ares == null)
358                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
359
360                         ReadMethod r = ares.AsyncDelegate as ReadMethod;
361                         if (r == null)
362                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
363
364                         return r.EndInvoke (async_result);
365                 }
366
367                 public override void EndWrite (IAsyncResult async_result)
368                 {
369                         if (async_result == null)
370                                 throw new ArgumentNullException ("async_result");
371
372                         AsyncResult ares = async_result as AsyncResult;
373                         if (ares == null)
374                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
375
376                         WriteMethod w = ares.AsyncDelegate as WriteMethod;
377                         if (w == null)
378                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
379
380                         w.EndInvoke (async_result);
381                         return;
382                 }
383
384                 public override long Seek (long offset, SeekOrigin origin)
385                 {
386                         throw new NotSupportedException();
387                 }
388
389                 public override void SetLength (long value)
390                 {
391                         throw new NotSupportedException();
392                 }
393
394                 public Stream BaseStream {
395                         get { return base_stream; }
396                 }
397
398                 public override bool CanRead {
399                         get { return !disposed && mode == CompressionMode.Decompress && base_stream.CanRead; }
400                 }
401
402                 public override bool CanSeek {
403                         get { return false; }
404                 }
405
406                 public override bool CanWrite {
407                         get { return !disposed && mode == CompressionMode.Compress && base_stream.CanWrite; }
408                 }
409
410                 public override long Length {
411                         get { throw new NotSupportedException(); }
412                 }
413
414                 public override long Position {
415                         get { throw new NotSupportedException(); }
416                         set { throw new NotSupportedException(); }
417                 }
418
419 #if MONOTOUCH || MONODROID
420                 const string LIBNAME = "__Internal";
421 #else
422                 const string LIBNAME = "MonoPosixHelper";
423 #endif
424
425                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
426                 static extern IntPtr CreateZStream (CompressionMode compress, bool gzip, UnmanagedReadOrWrite feeder, IntPtr data);
427
428                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
429                 static extern int CloseZStream (IntPtr stream);
430
431                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
432                 static extern int Flush (IntPtr stream);
433
434                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
435                 static extern int ReadZStream (IntPtr stream, IntPtr buffer, int length);
436
437                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
438                 static extern int WriteZStream (IntPtr stream, IntPtr buffer, int length);
439         }
440 }
441 #endif
442