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