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