Πρότυπα αρχείων - ένα header GIF#

Στο τέλος αυτού του κεφαλαίου θα μπορείτε να αναγνωρίζετε ένα δυαδικό αρχείο από τον μαγικό του αριθμό, να αναλύετε το header σταθερής διάταξής του, και να αποκωδικοποιείτε bytes σημαιών συσκευασμένα σε bit. Το παράδειγμα είναι η μορφή εικόνας GIF, αλλά η προσέγγιση μεταφέρεται απευθείας σε PNG, WAV, BMP, ELF, ZIP, και κάθε άλλη μορφή χτισμένη γύρω από σταθερό header.

Το header GIF και ο logical screen descriptor#

Ένα αρχείο GIF ξεκινάει με δύο μπλοκ σταθερού μεγέθους, δεκατρία bytes συνολικά. Από τις προδιαγραφές GIF89a:

Header (6 bytes)#

Offset

Bytes

Πεδίο

0

3

Υπογραφή - "GIF"

3

3

Έκδοση - "87a" ή "89a"

Logical Screen Descriptor (7 bytes)#

Offset

Bytes

Πεδίο

Σειρά bytes

6

2

Λογικό πλάτος οθόνης

little-endian

8

2

Λογικό ύψος οθόνης

little-endian

10

1

Συσκευασμένες σημαίες

-

11

1

Δείκτης χρώματος φόντου

-

12

1

Αναλογία διαστάσεων pixel

-

Το byte συσκευασμένων σημαιών έχει πέντε υπο-πεδία:

Bit:  7  6 5 4  3  2 1 0
     [GCT][CR  ][Sort][GCT size]
  • bit 7 - σημαία καθολικού πίνακα χρωμάτων

  • bits 6-4 - ανάλυση χρώματος (μείον 1)

  • bit 3 - σημαία ταξινόμησης

  • bits 2-0 - μέγεθος καθολικού πίνακα χρωμάτων (πραγματικό μέγεθος = 2^(value+1))

Κάθε προδιαγραφή μορφής μοιάζει περίπου με αυτό: πεδία σταθερού πλάτους σε γνωστή σειρά, με κάποιο περιστασιακό byte από συσκευασμένα bits.

Διαβάζοντάς το#

Το πρότυπο γράφεται από μόνο του πεδίο προς πεδίο:

sub parse_gif_header {
    my ($fh) = @_;
    binmode $fh;

    my $header;
    read $fh, $header, 13
        or die "short read: $!";

    my ($sig, $ver, $w, $h, $flags, $bg, $aspect) =
        unpack "A3 A3 v v C C C", $header;

    die "not a GIF" unless $sig eq "GIF";
    die "unknown version: $ver" unless $ver eq "87a" or $ver eq "89a";

    return {
        version => $ver,
        width   => $w,
        height  => $h,
        flags   => $flags,
        bg      => $bg,
        aspect  => $aspect,
    };
}

Οδηγία προς οδηγία:

  • A3 A3 - έξι bytes ASCII, χωρισμένα σε υπογραφή και έκδοση. A (όχι a ή Z) επειδή οι προδιαγραφές εγγυώνται ακριβώς τρεις χαρακτήρες χωρίς συμπλήρωση - το A τους επιστρέφει αμετάβλητους και έχει την ασφαλέστερη συμπεριφορά αποκοπής κατά το unpack.

  • v v - δύο little-endian 16-bit μη προσημασμένοι ακέραιοι. Η μορφή GIF είναι little-endian από άκρη σε άκρη· το v ταιριάζει ακριβώς.

  • C C C - τρία μη προσημασμένα bytes.

Συνολικό πλάτος: 3 + 3 + 2 + 2 + 1 + 1 + 1 = 13 bytes. Διαβάζουμε ακριβώς τόσα πριν αποπακετάρουμε· οτιδήποτε λιγότερο είναι σφάλμα.

Ο έλεγχος του μαγικού αριθμού#

Δύο γραμμές παραπάνω παρουσιάζουν ένα μοτίβο που θα επαναλάβετε για κάθε μορφή αρχείου:

die "not a GIF" unless $sig eq "GIF";
die "unknown version: $ver" unless $ver eq "87a" or $ver eq "89a";

Ελέγξτε πρώτα τον μαγικό αριθμό, αποπακετάρετε τα υπόλοιπα μετά. Η αντιστροφή της σειράς επιτρέπει σε ασυναρτησίες να περάσουν σε μεταγενέστερα στάδια όπου τα μηνύματα σφάλματος είναι λιγότερο σαφή. Για μορφές με μεγαλύτερο μαγικό αριθμό (το PNG έχει 8 bytes, το ELF 4, το «RIFF…WAVE» σάντουιτς του WAV έχει 12), ισχύει η ίδια ιδέα: αποπακετάρετε αρκετά για να αναγνωρίσετε το αρχείο, ματαιώστε σε αναντιστοιχία, μετά αποπακετάρετε τα υπόλοιπα.

Αποκωδικοποίηση του byte συσκευασμένων σημαιών#

Οι pack και unpack δεν μπορούν να αποκωδικοποιήσουν πεδία bit κάτω από byte απευθείας (με την εξαίρεση των οδηγιών b / B σε σχήμα συμβολοσειράς, δείτε το κεφάλαιο strings). Η πρακτική προσέγγιση είναι shifts και masks σε σκέτη Perl πάνω στο byte που επέστρεψε η unpack:

sub decode_gif_flags {
    my ($flags) = @_;
    return {
        gct_present => ($flags >> 7) & 0x01,
        color_res   => (($flags >> 4) & 0x07) + 1,
        sort_flag   => ($flags >> 3) & 0x01,
        gct_size    => 2 ** ((($flags) & 0x07) + 1),
    };
}

The pattern - «unpack the byte, pick the bits apart with shifts»

  • is so common it deserves to be named. Anything smaller than a byte belongs outside the template.

Επεξεργασμένη εκτέλεση#

open my $fh, "<:raw", "sample.gif" or die $!;
my $hdr = parse_gif_header($fh);
my $gct = decode_gif_flags($hdr->{flags});

printf "GIF%s %dx%d\n", $hdr->{version}, $hdr->{width}, $hdr->{height};
printf "global colour table: %s (%d entries)\n",
       $gct->{gct_present} ? "yes" : "no",
       $gct->{gct_size};

Σε ένα πραγματικό αρχείο GIF89a διαστάσεων 640×480, αυτό εκτυπώνει:

GIF89a 640x480
global colour table: yes (256 entries)

Εγγραφή ενός header#

Το αντίστροφο είναι ακόμα συντομότερο - κάθε οδηγία που αποπακετάραμε, μπορούμε να πακετάρουμε. Μία λεπτομέρεια: το byte σημαιών πρέπει να επανασυναρμολογηθεί από τα υπο-πεδία του πρώτα.

sub encode_gif_flags {
    my (%f) = @_;
    return (($f{gct_present} & 0x01) << 7)
         | (((log2($f{color_res})) & 0x07) << 4)
         | (($f{sort_flag}   & 0x01) << 3)
         | (log2($f{gct_size}) - 1);
}

sub write_gif_header {
    my ($fh, $hdr, $gct) = @_;
    my $flags = encode_gif_flags(%$gct);
    my $bytes = pack "A3 A3 v v C C C",
        "GIF", $hdr->{version},
        $hdr->{width}, $hdr->{height},
        $flags, $hdr->{bg}, $hdr->{aspect};
    print {$fh} $bytes;
}

Ίδιο πρότυπο στην πλευρά της pack, ίδια 13 bytes στην έξοδο, ταιριάζει με τον αναγνώστη GIF στο άλλο άκρο. Ο πλήρης κύκλος με ένα διάνυσμα δοκιμής είναι ο γρηγορότερος τρόπος να αποκτήσετε σιγουριά ότι έχετε το πρότυπο σωστό: αποπακετάρετε ένα υπάρχον αρχείο, ξανα-πακετάρετε το αποτέλεσμα, συγκρίνετε byte-προς-byte.

Κλιμάκωση σε μεγαλύτερες μορφές#

Η προσέγγιση είναι συνθετική. Μια μορφή όπως το WAV έχει πολλαπλά chunks, καθένα με το δικό του header (id + size + data). Γράφετε:

  • Έναν βοηθό χαμηλού επιπέδου «ανάγνωση ενός header chunk» - δύο κλήσεις unpack σε 8 bytes: 4-byte FourCC και 4-byte little-endian μέγεθος.

  • Έναν διανομέα που κοιτάει τον FourCC και καλεί έναν αναλυτή ανά chunk.

  • Αναλυτές ανά chunk που χρησιμοποιούν unpack στο σώμα του chunk σύμφωνα με τη δική του διάταξη bytes.

Κάθε στρώμα είναι μια μικρή unpack πάνω σε ένα μικροσκοπικό πρότυπο. Τη στιγμή που το πλάτος ενός πεδίου εξαρτάται από μια τιμή που μόλις διαβάσατε, σταματάτε, συνθέτετε το νέο πρότυπο από αυτή την τιμή, και καλείτε ξανά την unpack - το ίδιο σταδιακό μοτίβο που χρησιμοποιήσαμε για τα ονόματα DNS.

Τι μάθατε#

  • Μαγικός αριθμός πρώτα, σώμα δεύτερο. Αποτυχία γρήγορα σε λάθος μορφή.

  • Οι little-endian μορφές αρχείων χρησιμοποιούν v / V, οι big-endian χρησιμοποιούν n / N· τα < / > χειρίζονται τις προσημασμένες ή 64-bit περιπτώσεις.

  • Τα πεδία υπο-byte είναι shifts σε σκέτη Perl, όχι οδηγίες προτύπου.

  • Οι μεγαλύτερες μορφές αποσυντίθενται σε πολλά μικρά πρότυπα. Η δουλειά του προγραμματιστή είναι να κρατά κάθε πρότυπο μικρό και προφανές.

Με τις σελίδες αναφοράς ως πηγή αναζήτησης και αυτά τα επτά κεφάλαια στο ενεργητικό σας, η επόμενη δυαδική μορφή που θα διασχίσει το γραφείο σας θα πρέπει να διαβάζεται ως λυμένο πρόβλημα. Οι σελίδες pack και unpack περιέχουν τον πλήρη πίνακα οδηγιών όταν χρειάζεται να επιβεβαιώσετε ένα πλάτος ή έναν τροποποιητή.