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