1----------------------------------------------------------------
2--  ZLib for Ada thick binding.                               --
3--                                                            --
4--  Copyright (C) 2002-2004 Dmitriy Anisimkov                 --
5--                                                            --
6--  Open source license information is in the zlib.ads file.  --
7----------------------------------------------------------------
8
9--  $Id: zlib.adb,v 1.31 2004/09/06 06:53:19 vagul Exp $
10
11with Ada.Exceptions;
12with Ada.Unchecked_Conversion;
13with Ada.Unchecked_Deallocation;
14
15with Interfaces.C.Strings;
16
17with ZLib.Thin;
18
19package body ZLib is
20
21   use type Thin.Int;
22
23   type Z_Stream is new Thin.Z_Stream;
24
25   type Return_Code_Enum is
26      (OK,
27       STREAM_END,
28       NEED_DICT,
29       ERRNO,
30       STREAM_ERROR,
31       DATA_ERROR,
32       MEM_ERROR,
33       BUF_ERROR,
34       VERSION_ERROR);
35
36   type Flate_Step_Function is access
37     function (Strm : in Thin.Z_Streamp; Flush : in Thin.Int) return Thin.Int;
38   pragma Convention (C, Flate_Step_Function);
39
40   type Flate_End_Function is access
41      function (Ctrm : in Thin.Z_Streamp) return Thin.Int;
42   pragma Convention (C, Flate_End_Function);
43
44   type Flate_Type is record
45      Step : Flate_Step_Function;
46      Done : Flate_End_Function;
47   end record;
48
49   subtype Footer_Array is Stream_Element_Array (1 .. 8);
50
51   Simple_GZip_Header : constant Stream_Element_Array (1 .. 10)
52     := (16#1f#, 16#8b#,                 --  Magic header
53         16#08#,                         --  Z_DEFLATED
54         16#00#,                         --  Flags
55         16#00#, 16#00#, 16#00#, 16#00#, --  Time
56         16#00#,                         --  XFlags
57         16#03#                          --  OS code
58        );
59   --  The simplest gzip header is not for informational, but just for
60   --  gzip format compatibility.
61   --  Note that some code below is using assumption
62   --  Simple_GZip_Header'Last > Footer_Array'Last, so do not make
63   --  Simple_GZip_Header'Last <= Footer_Array'Last.
64
65   Return_Code : constant array (Thin.Int range <>) of Return_Code_Enum
66     := (0 => OK,
67         1 => STREAM_END,
68         2 => NEED_DICT,
69        -1 => ERRNO,
70        -2 => STREAM_ERROR,
71        -3 => DATA_ERROR,
72        -4 => MEM_ERROR,
73        -5 => BUF_ERROR,
74        -6 => VERSION_ERROR);
75
76   Flate : constant array (Boolean) of Flate_Type
77     := (True  => (Step => Thin.Deflate'Access,
78                   Done => Thin.DeflateEnd'Access),
79         False => (Step => Thin.Inflate'Access,
80                   Done => Thin.InflateEnd'Access));
81
82   Flush_Finish : constant array (Boolean) of Flush_Mode
83     := (True => Finish, False => No_Flush);
84
85   procedure Raise_Error (Stream : in Z_Stream);
86   pragma Inline (Raise_Error);
87
88   procedure Raise_Error (Message : in String);
89   pragma Inline (Raise_Error);
90
91   procedure Check_Error (Stream : in Z_Stream; Code : in Thin.Int);
92
93   procedure Free is new Ada.Unchecked_Deallocation
94      (Z_Stream, Z_Stream_Access);
95
96   function To_Thin_Access is new Ada.Unchecked_Conversion
97     (Z_Stream_Access, Thin.Z_Streamp);
98
99   procedure Translate_GZip
100     (Filter    : in out Filter_Type;
101      In_Data   : in     Ada.Streams.Stream_Element_Array;
102      In_Last   :    out Ada.Streams.Stream_Element_Offset;
103      Out_Data  :    out Ada.Streams.Stream_Element_Array;
104      Out_Last  :    out Ada.Streams.Stream_Element_Offset;
105      Flush     : in     Flush_Mode);
106   --  Separate translate routine for make gzip header.
107
108   procedure Translate_Auto
109     (Filter    : in out Filter_Type;
110      In_Data   : in     Ada.Streams.Stream_Element_Array;
111      In_Last   :    out Ada.Streams.Stream_Element_Offset;
112      Out_Data  :    out Ada.Streams.Stream_Element_Array;
113      Out_Last  :    out Ada.Streams.Stream_Element_Offset;
114      Flush     : in     Flush_Mode);
115   --  translate routine without additional headers.
116
117   -----------------
118   -- Check_Error --
119   -----------------
120
121   procedure Check_Error (Stream : in Z_Stream; Code : in Thin.Int) is
122      use type Thin.Int;
123   begin
124      if Code /= Thin.Z_OK then
125         Raise_Error
126            (Return_Code_Enum'Image (Return_Code (Code))
127              & ": " & Last_Error_Message (Stream));
128      end if;
129   end Check_Error;
130
131   -----------
132   -- Close --
133   -----------
134
135   procedure Close
136     (Filter       : in out Filter_Type;
137      Ignore_Error : in     Boolean := False)
138   is
139      Code : Thin.Int;
140   begin
141      if not Ignore_Error and then not Is_Open (Filter) then
142         raise Status_Error;
143      end if;
144
145      Code := Flate (Filter.Compression).Done (To_Thin_Access (Filter.Strm));
146
147      if Ignore_Error or else Code = Thin.Z_OK then
148         Free (Filter.Strm);
149      else
150         declare
151            Error_Message : constant String
152              := Last_Error_Message (Filter.Strm.all);
153         begin
154            Free (Filter.Strm);
155            Ada.Exceptions.Raise_Exception
156               (ZLib_Error'Identity,
157                Return_Code_Enum'Image (Return_Code (Code))
158                  & ": " & Error_Message);
159         end;
160      end if;
161   end Close;
162
163   -----------
164   -- CRC32 --
165   -----------
166
167   function CRC32
168     (CRC  : in Unsigned_32;
169      Data : in Ada.Streams.Stream_Element_Array)
170      return Unsigned_32
171   is
172      use Thin;
173   begin
174      return Unsigned_32 (crc32 (ULong (CRC),
175                                 Data'Address,
176                                 Data'Length));
177   end CRC32;
178
179   procedure CRC32
180     (CRC  : in out Unsigned_32;
181      Data : in     Ada.Streams.Stream_Element_Array) is
182   begin
183      CRC := CRC32 (CRC, Data);
184   end CRC32;
185
186   ------------------
187   -- Deflate_Init --
188   ------------------
189
190   procedure Deflate_Init
191     (Filter       : in out Filter_Type;
192      Level        : in     Compression_Level  := Default_Compression;
193      Strategy     : in     Strategy_Type      := Default_Strategy;
194      Method       : in     Compression_Method := Deflated;
195      Window_Bits  : in     Window_Bits_Type   := Default_Window_Bits;
196      Memory_Level : in     Memory_Level_Type  := Default_Memory_Level;
197      Header       : in     Header_Type        := Default)
198   is
199      use type Thin.Int;
200      Win_Bits : Thin.Int := Thin.Int (Window_Bits);
201   begin
202      if Is_Open (Filter) then
203         raise Status_Error;
204      end if;
205
206      --  We allow ZLib to make header only in case of default header type.
207      --  Otherwise we would either do header by ourselfs, or do not do
208      --  header at all.
209
210      if Header = None or else Header = GZip then
211         Win_Bits := -Win_Bits;
212      end if;
213
214      --  For the GZip CRC calculation and make headers.
215
216      if Header = GZip then
217         Filter.CRC    := 0;
218         Filter.Offset := Simple_GZip_Header'First;
219      else
220         Filter.Offset := Simple_GZip_Header'Last + 1;
221      end if;
222
223      Filter.Strm        := new Z_Stream;
224      Filter.Compression := True;
225      Filter.Stream_End  := False;
226      Filter.Header      := Header;
227
228      if Thin.Deflate_Init
229           (To_Thin_Access (Filter.Strm),
230            Level      => Thin.Int (Level),
231            method     => Thin.Int (Method),
232            windowBits => Win_Bits,
233            memLevel   => Thin.Int (Memory_Level),
234            strategy   => Thin.Int (Strategy)) /= Thin.Z_OK
235      then
236         Raise_Error (Filter.Strm.all);
237      end if;
238   end Deflate_Init;
239
240   -----------
241   -- Flush --
242   -----------
243
244   procedure Flush
245     (Filter    : in out Filter_Type;
246      Out_Data  :    out Ada.Streams.Stream_Element_Array;
247      Out_Last  :    out Ada.Streams.Stream_Element_Offset;
248      Flush     : in     Flush_Mode)
249   is
250      No_Data : Stream_Element_Array := (1 .. 0 => 0);
251      Last    : Stream_Element_Offset;
252   begin
253      Translate (Filter, No_Data, Last, Out_Data, Out_Last, Flush);
254   end Flush;
255
256   -----------------------
257   -- Generic_Translate --
258   -----------------------
259
260   procedure Generic_Translate
261     (Filter          : in out ZLib.Filter_Type;
262      In_Buffer_Size  : in     Integer := Default_Buffer_Size;
263      Out_Buffer_Size : in     Integer := Default_Buffer_Size)
264   is
265      In_Buffer  : Stream_Element_Array
266                     (1 .. Stream_Element_Offset (In_Buffer_Size));
267      Out_Buffer : Stream_Element_Array
268                     (1 .. Stream_Element_Offset (Out_Buffer_Size));
269      Last       : Stream_Element_Offset;
270      In_Last    : Stream_Element_Offset;
271      In_First   : Stream_Element_Offset;
272      Out_Last   : Stream_Element_Offset;
273   begin
274      Main : loop
275         Data_In (In_Buffer, Last);
276
277         In_First := In_Buffer'First;
278
279         loop
280            Translate
281              (Filter   => Filter,
282               In_Data  => In_Buffer (In_First .. Last),
283               In_Last  => In_Last,
284               Out_Data => Out_Buffer,
285               Out_Last => Out_Last,
286               Flush    => Flush_Finish (Last < In_Buffer'First));
287
288            if Out_Buffer'First <= Out_Last then
289               Data_Out (Out_Buffer (Out_Buffer'First .. Out_Last));
290            end if;
291
292            exit Main when Stream_End (Filter);
293
294            --  The end of in buffer.
295
296            exit when In_Last = Last;
297
298            In_First := In_Last + 1;
299         end loop;
300      end loop Main;
301
302   end Generic_Translate;
303
304   ------------------
305   -- Inflate_Init --
306   ------------------
307
308   procedure Inflate_Init
309     (Filter      : in out Filter_Type;
310      Window_Bits : in     Window_Bits_Type := Default_Window_Bits;
311      Header      : in     Header_Type      := Default)
312   is
313      use type Thin.Int;
314      Win_Bits : Thin.Int := Thin.Int (Window_Bits);
315
316      procedure Check_Version;
317      --  Check the latest header types compatibility.
318
319      procedure Check_Version is
320      begin
321         if Version <= "1.1.4" then
322            Raise_Error
323              ("Inflate header type " & Header_Type'Image (Header)
324               & " incompatible with ZLib version " & Version);
325         end if;
326      end Check_Version;
327
328   begin
329      if Is_Open (Filter) then
330         raise Status_Error;
331      end if;
332
333      case Header is
334         when None =>
335            Check_Version;
336
337            --  Inflate data without headers determined
338            --  by negative Win_Bits.
339
340            Win_Bits := -Win_Bits;
341         when GZip =>
342            Check_Version;
343
344            --  Inflate gzip data defined by flag 16.
345
346            Win_Bits := Win_Bits + 16;
347         when Auto =>
348            Check_Version;
349
350            --  Inflate with automatic detection
351            --  of gzip or native header defined by flag 32.
352
353            Win_Bits := Win_Bits + 32;
354         when Default => null;
355      end case;
356
357      Filter.Strm        := new Z_Stream;
358      Filter.Compression := False;
359      Filter.Stream_End  := False;
360      Filter.Header      := Header;
361
362      if Thin.Inflate_Init
363         (To_Thin_Access (Filter.Strm), Win_Bits) /= Thin.Z_OK
364      then
365         Raise_Error (Filter.Strm.all);
366      end if;
367   end Inflate_Init;
368
369   -------------
370   -- Is_Open --
371   -------------
372
373   function Is_Open (Filter : in Filter_Type) return Boolean is
374   begin
375      return Filter.Strm /= null;
376   end Is_Open;
377
378   -----------------
379   -- Raise_Error --
380   -----------------
381
382   procedure Raise_Error (Message : in String) is
383   begin
384      Ada.Exceptions.Raise_Exception (ZLib_Error'Identity, Message);
385   end Raise_Error;
386
387   procedure Raise_Error (Stream : in Z_Stream) is
388   begin
389      Raise_Error (Last_Error_Message (Stream));
390   end Raise_Error;
391
392   ----------
393   -- Read --
394   ----------
395
396   procedure Read
397     (Filter : in out Filter_Type;
398      Item   :    out Ada.Streams.Stream_Element_Array;
399      Last   :    out Ada.Streams.Stream_Element_Offset;
400      Flush  : in     Flush_Mode := No_Flush)
401   is
402      In_Last    : Stream_Element_Offset;
403      Item_First : Ada.Streams.Stream_Element_Offset := Item'First;
404      V_Flush    : Flush_Mode := Flush;
405
406   begin
407      pragma Assert (Rest_First in Buffer'First .. Buffer'Last + 1);
408      pragma Assert (Rest_Last in Buffer'First - 1 .. Buffer'Last);
409
410      loop
411         if Rest_Last = Buffer'First - 1 then
412            V_Flush := Finish;
413
414         elsif Rest_First > Rest_Last then
415            Read (Buffer, Rest_Last);
416            Rest_First := Buffer'First;
417
418            if Rest_Last < Buffer'First then
419               V_Flush := Finish;
420            end if;
421         end if;
422
423         Translate
424           (Filter   => Filter,
425            In_Data  => Buffer (Rest_First .. Rest_Last),
426            In_Last  => In_Last,
427            Out_Data => Item (Item_First .. Item'Last),
428            Out_Last => Last,
429            Flush    => V_Flush);
430
431         Rest_First := In_Last + 1;
432
433         exit when Stream_End (Filter)
434           or else Last = Item'Last
435           or else (Last >= Item'First and then Allow_Read_Some);
436
437         Item_First := Last + 1;
438      end loop;
439   end Read;
440
441   ----------------
442   -- Stream_End --
443   ----------------
444
445   function Stream_End (Filter : in Filter_Type) return Boolean is
446   begin
447      if Filter.Header = GZip and Filter.Compression then
448         return Filter.Stream_End
449            and then Filter.Offset = Footer_Array'Last + 1;
450      else
451         return Filter.Stream_End;
452      end if;
453   end Stream_End;
454
455   --------------
456   -- Total_In --
457   --------------
458
459   function Total_In (Filter : in Filter_Type) return Count is
460   begin
461      return Count (Thin.Total_In (To_Thin_Access (Filter.Strm).all));
462   end Total_In;
463
464   ---------------
465   -- Total_Out --
466   ---------------
467
468   function Total_Out (Filter : in Filter_Type) return Count is
469   begin
470      return Count (Thin.Total_Out (To_Thin_Access (Filter.Strm).all));
471   end Total_Out;
472
473   ---------------
474   -- Translate --
475   ---------------
476
477   procedure Translate
478     (Filter    : in out Filter_Type;
479      In_Data   : in     Ada.Streams.Stream_Element_Array;
480      In_Last   :    out Ada.Streams.Stream_Element_Offset;
481      Out_Data  :    out Ada.Streams.Stream_Element_Array;
482      Out_Last  :    out Ada.Streams.Stream_Element_Offset;
483      Flush     : in     Flush_Mode) is
484   begin
485      if Filter.Header = GZip and then Filter.Compression then
486         Translate_GZip
487           (Filter   => Filter,
488            In_Data  => In_Data,
489            In_Last  => In_Last,
490            Out_Data => Out_Data,
491            Out_Last => Out_Last,
492            Flush    => Flush);
493      else
494         Translate_Auto
495           (Filter   => Filter,
496            In_Data  => In_Data,
497            In_Last  => In_Last,
498            Out_Data => Out_Data,
499            Out_Last => Out_Last,
500            Flush    => Flush);
501      end if;
502   end Translate;
503
504   --------------------
505   -- Translate_Auto --
506   --------------------
507
508   procedure Translate_Auto
509     (Filter    : in out Filter_Type;
510      In_Data   : in     Ada.Streams.Stream_Element_Array;
511      In_Last   :    out Ada.Streams.Stream_Element_Offset;
512      Out_Data  :    out Ada.Streams.Stream_Element_Array;
513      Out_Last  :    out Ada.Streams.Stream_Element_Offset;
514      Flush     : in     Flush_Mode)
515   is
516      use type Thin.Int;
517      Code : Thin.Int;
518
519   begin
520      if not Is_Open (Filter) then
521         raise Status_Error;
522      end if;
523
524      if Out_Data'Length = 0 and then In_Data'Length = 0 then
525         raise Constraint_Error;
526      end if;
527
528      Set_Out (Filter.Strm.all, Out_Data'Address, Out_Data'Length);
529      Set_In  (Filter.Strm.all, In_Data'Address, In_Data'Length);
530
531      Code := Flate (Filter.Compression).Step
532        (To_Thin_Access (Filter.Strm),
533         Thin.Int (Flush));
534
535      if Code = Thin.Z_STREAM_END then
536         Filter.Stream_End := True;
537      else
538         Check_Error (Filter.Strm.all, Code);
539      end if;
540
541      In_Last  := In_Data'Last
542         - Stream_Element_Offset (Avail_In (Filter.Strm.all));
543      Out_Last := Out_Data'Last
544         - Stream_Element_Offset (Avail_Out (Filter.Strm.all));
545   end Translate_Auto;
546
547   --------------------
548   -- Translate_GZip --
549   --------------------
550
551   procedure Translate_GZip
552     (Filter    : in out Filter_Type;
553      In_Data   : in     Ada.Streams.Stream_Element_Array;
554      In_Last   :    out Ada.Streams.Stream_Element_Offset;
555      Out_Data  :    out Ada.Streams.Stream_Element_Array;
556      Out_Last  :    out Ada.Streams.Stream_Element_Offset;
557      Flush     : in     Flush_Mode)
558   is
559      Out_First : Stream_Element_Offset;
560
561      procedure Add_Data (Data : in Stream_Element_Array);
562      --  Add data to stream from the Filter.Offset till necessary,
563      --  used for add gzip headr/footer.
564
565      procedure Put_32
566        (Item : in out Stream_Element_Array;
567         Data : in     Unsigned_32);
568      pragma Inline (Put_32);
569
570      --------------
571      -- Add_Data --
572      --------------
573
574      procedure Add_Data (Data : in Stream_Element_Array) is
575         Data_First : Stream_Element_Offset renames Filter.Offset;
576         Data_Last  : Stream_Element_Offset;
577         Data_Len   : Stream_Element_Offset; --  -1
578         Out_Len    : Stream_Element_Offset; --  -1
579      begin
580         Out_First := Out_Last + 1;
581
582         if Data_First > Data'Last then
583            return;
584         end if;
585
586         Data_Len  := Data'Last     - Data_First;
587         Out_Len   := Out_Data'Last - Out_First;
588
589         if Data_Len <= Out_Len then
590            Out_Last  := Out_First  + Data_Len;
591            Data_Last := Data'Last;
592         else
593            Out_Last  := Out_Data'Last;
594            Data_Last := Data_First + Out_Len;
595         end if;
596
597         Out_Data (Out_First .. Out_Last) := Data (Data_First .. Data_Last);
598
599         Data_First := Data_Last + 1;
600         Out_First  := Out_Last + 1;
601      end Add_Data;
602
603      ------------
604      -- Put_32 --
605      ------------
606
607      procedure Put_32
608        (Item : in out Stream_Element_Array;
609         Data : in     Unsigned_32)
610      is
611         D : Unsigned_32 := Data;
612      begin
613         for J in Item'First .. Item'First + 3 loop
614            Item (J) := Stream_Element (D and 16#FF#);
615            D := Shift_Right (D, 8);
616         end loop;
617      end Put_32;
618
619   begin
620      Out_Last := Out_Data'First - 1;
621
622      if not Filter.Stream_End then
623         Add_Data (Simple_GZip_Header);
624
625         Translate_Auto
626           (Filter   => Filter,
627            In_Data  => In_Data,
628            In_Last  => In_Last,
629            Out_Data => Out_Data (Out_First .. Out_Data'Last),
630            Out_Last => Out_Last,
631            Flush    => Flush);
632
633         CRC32 (Filter.CRC, In_Data (In_Data'First .. In_Last));
634      end if;
635
636      if Filter.Stream_End and then Out_Last <= Out_Data'Last then
637         --  This detection method would work only when
638         --  Simple_GZip_Header'Last > Footer_Array'Last
639
640         if Filter.Offset = Simple_GZip_Header'Last + 1 then
641            Filter.Offset := Footer_Array'First;
642         end if;
643
644         declare
645            Footer : Footer_Array;
646         begin
647            Put_32 (Footer, Filter.CRC);
648            Put_32 (Footer (Footer'First + 4 .. Footer'Last),
649                    Unsigned_32 (Total_In (Filter)));
650            Add_Data (Footer);
651         end;
652      end if;
653   end Translate_GZip;
654
655   -------------
656   -- Version --
657   -------------
658
659   function Version return String is
660   begin
661      return Interfaces.C.Strings.Value (Thin.zlibVersion);
662   end Version;
663
664   -----------
665   -- Write --
666   -----------
667
668   procedure Write
669     (Filter : in out Filter_Type;
670      Item   : in     Ada.Streams.Stream_Element_Array;
671      Flush  : in     Flush_Mode := No_Flush)
672   is
673      Buffer   : Stream_Element_Array (1 .. Buffer_Size);
674      In_Last  : Stream_Element_Offset;
675      Out_Last : Stream_Element_Offset;
676      In_First : Stream_Element_Offset := Item'First;
677   begin
678      if Item'Length = 0 and Flush = No_Flush then
679         return;
680      end if;
681
682      loop
683         Translate
684           (Filter   => Filter,
685            In_Data  => Item (In_First .. Item'Last),
686            In_Last  => In_Last,
687            Out_Data => Buffer,
688            Out_Last => Out_Last,
689            Flush    => Flush);
690
691         if Out_Last >= Buffer'First then
692            Write (Buffer (1 .. Out_Last));
693         end if;
694
695         exit when In_Last = Item'Last or Stream_End (Filter);
696
697         In_First := In_Last + 1;
698      end loop;
699   end Write;
700
701end ZLib;
702