Home | History | Annotate | Download | only in ada
      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 
     11 with Ada.Exceptions;
     12 with Ada.Unchecked_Conversion;
     13 with Ada.Unchecked_Deallocation;
     14 
     15 with Interfaces.C.Strings;
     16 
     17 with ZLib.Thin;
     18 
     19 package 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 
    701 end ZLib;
    702