Removed DeflateStream.UnmanagedRead Read loop. Fixes #19313.
[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
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 compressedStream, CompressionMode mode) :
56                         this (compressedStream, mode, false, false)
57                 {
58                 }
59
60                 public DeflateStream (Stream compressedStream, CompressionMode mode, bool leaveOpen) :
61                         this (compressedStream, 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 #if NET_4_5
84                 [MonoTODO]
85                 public DeflateStream (Stream stream, CompressionLevel compressionLevel)
86                         : this (stream, CompressionMode.Compress)
87                 {
88                         throw new NotImplementedException ();
89                 }
90                 
91                 [MonoTODO]
92                 public DeflateStream (Stream stream, CompressionLevel compressionLevel, bool leaveOpen)
93                         : this (stream, CompressionMode.Compress, leaveOpen)
94                 {
95                         throw new NotImplementedException ();
96                 }
97 #endif
98
99                 protected override void Dispose (bool disposing)
100                 {
101                         native.Dispose (disposing);
102
103                         if (disposing && !disposed) {
104                                 disposed = true;
105
106                                 if (!leaveOpen) {
107                                         Stream st = base_stream;
108                                         if (st != null)
109                                                 st.Close ();
110                                         base_stream = null;
111                                 }
112                         }
113
114                         base.Dispose (disposing);
115                 }
116
117                 unsafe int ReadInternal (byte[] array, int offset, int count)
118                 {
119                         if (count == 0)
120                                 return 0;
121
122                         fixed (byte *b = array) {
123                                 IntPtr ptr = new IntPtr (b + offset);
124                                 return native.ReadZStream (ptr, count);
125                         }
126                 }
127
128                 public override int Read (byte[] dest, int dest_offset, int count)
129                 {
130                         if (disposed)
131                                 throw new ObjectDisposedException (GetType ().FullName);
132                         if (dest == null)
133                                 throw new ArgumentNullException ("Destination array is null.");
134                         if (!CanRead)
135                                 throw new InvalidOperationException ("Stream does not support reading.");
136                         int len = dest.Length;
137                         if (dest_offset < 0 || count < 0)
138                                 throw new ArgumentException ("Dest or count is negative.");
139                         if (dest_offset > len)
140                                 throw new ArgumentException ("destination offset is beyond array size");
141                         if ((dest_offset + count) > len)
142                                 throw new ArgumentException ("Reading would overrun buffer");
143
144                         return ReadInternal (dest, dest_offset, count);
145                 }
146
147                 unsafe void WriteInternal (byte[] array, int offset, int count)
148                 {
149                         if (count == 0)
150                                 return;
151
152                         fixed (byte *b = array) {
153                                 IntPtr ptr = new IntPtr (b + offset);
154                                 native.WriteZStream (ptr, count);
155                         }
156                 }
157
158                 public override void Write (byte[] src, int src_offset, int count)
159                 {
160                         if (disposed)
161                                 throw new ObjectDisposedException (GetType ().FullName);
162
163                         if (src == null)
164                                 throw new ArgumentNullException ("src");
165
166                         if (src_offset < 0)
167                                 throw new ArgumentOutOfRangeException ("src_offset");
168
169                         if (count < 0)
170                                 throw new ArgumentOutOfRangeException ("count");
171
172                         if (!CanWrite)
173                                 throw new NotSupportedException ("Stream does not support writing");
174
175                         WriteInternal (src, src_offset, count);
176                 }
177
178                 public override void Flush ()
179                 {
180                         if (disposed)
181                                 throw new ObjectDisposedException (GetType ().FullName);
182
183                         if (CanWrite) {
184                                 native.Flush ();
185                         }
186                 }
187
188                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
189                                                         AsyncCallback cback, object state)
190                 {
191                         if (disposed)
192                                 throw new ObjectDisposedException (GetType ().FullName);
193
194                         if (!CanRead)
195                                 throw new NotSupportedException ("This stream does not support reading");
196
197                         if (buffer == null)
198                                 throw new ArgumentNullException ("buffer");
199
200                         if (count < 0)
201                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
202
203                         if (offset < 0)
204                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
205
206                         if (count + offset > buffer.Length)
207                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
208
209                         ReadMethod r = new ReadMethod (ReadInternal);
210                         return r.BeginInvoke (buffer, offset, count, cback, state);
211                 }
212
213                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
214                                                         AsyncCallback cback, object state)
215                 {
216                         if (disposed)
217                                 throw new ObjectDisposedException (GetType ().FullName);
218
219                         if (!CanWrite)
220                                 throw new InvalidOperationException ("This stream does not support writing");
221
222                         if (buffer == null)
223                                 throw new ArgumentNullException ("buffer");
224
225                         if (count < 0)
226                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
227
228                         if (offset < 0)
229                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
230
231                         if (count + offset > buffer.Length)
232                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
233
234                         WriteMethod w = new WriteMethod (WriteInternal);
235                         return w.BeginInvoke (buffer, offset, count, cback, state);                     
236                 }
237
238                 public override int EndRead(IAsyncResult async_result)
239                 {
240                         if (async_result == null)
241                                 throw new ArgumentNullException ("async_result");
242
243                         AsyncResult ares = async_result as AsyncResult;
244                         if (ares == null)
245                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
246
247                         ReadMethod r = ares.AsyncDelegate as ReadMethod;
248                         if (r == null)
249                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
250
251                         return r.EndInvoke (async_result);
252                 }
253
254                 public override void EndWrite (IAsyncResult async_result)
255                 {
256                         if (async_result == null)
257                                 throw new ArgumentNullException ("async_result");
258
259                         AsyncResult ares = async_result as AsyncResult;
260                         if (ares == null)
261                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
262
263                         WriteMethod w = ares.AsyncDelegate as WriteMethod;
264                         if (w == null)
265                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
266
267                         w.EndInvoke (async_result);
268                         return;
269                 }
270
271                 public override long Seek (long offset, SeekOrigin origin)
272                 {
273                         throw new NotSupportedException();
274                 }
275
276                 public override void SetLength (long value)
277                 {
278                         throw new NotSupportedException();
279                 }
280
281                 public Stream BaseStream {
282                         get { return base_stream; }
283                 }
284
285                 public override bool CanRead {
286                         get { return !disposed && mode == CompressionMode.Decompress && base_stream.CanRead; }
287                 }
288
289                 public override bool CanSeek {
290                         get { return false; }
291                 }
292
293                 public override bool CanWrite {
294                         get { return !disposed && mode == CompressionMode.Compress && base_stream.CanWrite; }
295                 }
296
297                 public override long Length {
298                         get { throw new NotSupportedException(); }
299                 }
300
301                 public override long Position {
302                         get { throw new NotSupportedException(); }
303                         set { throw new NotSupportedException(); }
304                 }
305         }
306
307         class DeflateStreamNative
308         {
309                 const int BufferSize = 4096;
310
311                 [UnmanagedFunctionPointer (CallingConvention.Cdecl)]
312                 delegate int UnmanagedReadOrWrite (IntPtr buffer, int length, IntPtr data);
313
314                 UnmanagedReadOrWrite feeder; // This will be passed to unmanaged code and used there
315
316                 Stream base_stream;
317                 IntPtr z_stream;
318                 GCHandle data;
319                 bool disposed;
320                 byte [] io_buffer;
321
322                 private DeflateStreamNative ()
323                 {
324                 }
325
326                 public static DeflateStreamNative Create (Stream compressedStream, CompressionMode mode, bool gzip)
327                 {
328                         var dsn = new DeflateStreamNative ();
329                         dsn.data = GCHandle.Alloc (dsn);
330                         dsn.feeder = mode == CompressionMode.Compress ? new UnmanagedReadOrWrite (UnmanagedWrite) : new UnmanagedReadOrWrite (UnmanagedRead);
331                         dsn.z_stream = CreateZStream (mode, gzip, dsn.feeder, GCHandle.ToIntPtr (dsn.data));
332                         if (dsn.z_stream == IntPtr.Zero) {
333                                 dsn.Dispose (true);
334                                 return null;
335                         }
336
337                         dsn.base_stream = compressedStream;
338                         return dsn;
339                 }
340
341                 ~DeflateStreamNative ()
342                 {
343                         Dispose (false);
344                 }
345
346                 public void Dispose (bool disposing)
347                 {
348                         if (disposing && !disposed) {
349                                 disposed = true;
350                                 GC.SuppressFinalize (this);
351                         
352                                 io_buffer = null;
353                         
354                                 IntPtr zz = z_stream;
355                                 z_stream = IntPtr.Zero;
356                                 if (zz != IntPtr.Zero)
357                                         CloseZStream (zz); // This will Flush() the remaining output if any
358                         }
359
360                         if (data.IsAllocated) {
361                                 data.Free ();
362                         }
363                 }
364
365                 public void Flush ()
366                 {
367                         var res = Flush (z_stream);
368                         CheckResult (res, "Flush");
369                 }
370
371                 public int ReadZStream (IntPtr buffer, int length)
372                 {
373                         var res = ReadZStream (z_stream, buffer, length);
374                         CheckResult (res, "ReadInternal");
375                         return res;
376                 }
377
378                 public void WriteZStream (IntPtr buffer, int length)
379                 {
380                         var res = WriteZStream (z_stream, buffer, length);
381                         CheckResult (res, "WriteInternal");
382                 }
383
384 #if MONOTOUCH
385                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
386 #endif
387                 static int UnmanagedRead (IntPtr buffer, int length, IntPtr data)
388                 {
389                         GCHandle s = GCHandle.FromIntPtr (data);
390                         var self = s.Target as DeflateStreamNative;
391                         if (self == null)
392                                 return -1;
393                         return self.UnmanagedRead (buffer, length);
394                 }
395
396                 int UnmanagedRead (IntPtr buffer, int length)
397                 {
398                         if (io_buffer == null)
399                                 io_buffer = new byte [BufferSize];
400
401                         int count = Math.Min (length, io_buffer.Length);
402                         int n = base_stream.Read (io_buffer, 0, count);
403                         if (n > 0)
404                                 Marshal.Copy (io_buffer, 0, buffer, n);
405
406                         return n;
407                 }
408
409 #if MONOTOUCH
410                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
411 #endif
412                 static int UnmanagedWrite (IntPtr buffer, int length, IntPtr data)
413                 {
414                         GCHandle s = GCHandle.FromIntPtr (data);
415                         var self = s.Target as DeflateStreamNative;
416                         if (self == null)
417                                 return -1;
418                         return self.UnmanagedWrite (buffer, length);
419                 }
420
421                 int UnmanagedWrite (IntPtr buffer, int length)
422                 {
423                         int total = 0;
424                         while (length > 0) {
425                                 if (io_buffer == null)
426                                         io_buffer = new byte [BufferSize];
427
428                                 int count = Math.Min (length, io_buffer.Length);
429                                 Marshal.Copy (buffer, io_buffer, 0, count);
430                                 base_stream.Write (io_buffer, 0, count);
431                                 unsafe {
432                                         buffer = new IntPtr ((byte *) buffer.ToPointer () + count);
433                                 }
434                                 length -= count;
435                                 total += count;
436                         }
437                         return total;
438                 }
439
440                 static void CheckResult (int result, string where)
441                 {
442                         if (result >= 0)
443                                 return;
444
445                         string error;
446                         switch (result) {
447                         case -1: // Z_ERRNO
448                                 error = "Unknown error"; // Marshal.GetLastWin32() ?
449                                 break;
450                         case -2: // Z_STREAM_ERROR
451                                 error = "Internal error";
452                                 break;
453                         case -3: // Z_DATA_ERROR
454                                 error = "Corrupted data";
455                                 break;
456                         case -4: // Z_MEM_ERROR
457                                 error = "Not enough memory";
458                                 break;
459                         case -5: // Z_BUF_ERROR
460                                 error = "Internal error (no progress possible)";
461                                 break;
462                         case -6: // Z_VERSION_ERROR
463                                 error = "Invalid version";
464                                 break;
465                         case -10:
466                                 error = "Invalid argument(s)";
467                                 break;
468                         case -11:
469                                 error = "IO error";
470                                 break;
471                         default:
472                                 error = "Unknown error";
473                                 break;
474                         }
475
476                         throw new IOException (error + " " + where);
477                 }
478
479 #if MONOTOUCH || MONODROID
480                 const string LIBNAME = "__Internal";
481 #else
482                 const string LIBNAME = "MonoPosixHelper";
483 #endif
484
485                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
486                 static extern IntPtr CreateZStream (CompressionMode compress, bool gzip, UnmanagedReadOrWrite feeder, IntPtr data);
487
488                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
489                 static extern int CloseZStream (IntPtr stream);
490
491                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
492                 static extern int Flush (IntPtr stream);
493
494                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
495                 static extern int ReadZStream (IntPtr stream, IntPtr buffer, int length);
496
497                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
498                 static extern int WriteZStream (IntPtr stream, IntPtr buffer, int length);
499         }
500 }
501