Detecting Tag Padding
Now all that remains is to implement read-frame
. This is a bit tricky since the code that actually reads bytes from the stream is several layers down from read-frame
.
What you’d really like to do in read-frame
is read one byte and return **NIL**
if it’s a null and otherwise read a frame with read-value
. Unfortunately, if you read the byte in read-frame
, then it won’t be available to be read by read-value
.6
It turns out this is a perfect opportunity to use the condition system—you can check for null bytes in the low-level code that reads from the stream and signal a condition when you read a null; read-frame
can then handle the condition by unwinding the stack before more bytes are read. In addition to turning out to be a tidy solution to the problem of detecting the start of the tag’s padding, this is also an example of how you can use conditions for purposes other than handling errors.
You can start by defining a condition type to be signaled by the low-level code and handled by the high-level code. This condition doesn’t need any slots—you just need a distinct class of condition so you know no other code will be signaling or handling it.
(define-condition in-padding () ())
Next you need to define a binary type whose :reader
reads a given number of bytes, first reading a single byte and signaling an in-padding
condition if the byte is null and otherwise reading the remaining bytes as an iso-8859-1-string
and combining it with the first byte read.
(define-binary-type frame-id (length)
(:reader (in)
(let ((first-byte (read-byte in)))
(when (= first-byte 0) (signal 'in-padding))
(let ((rest (read-value 'iso-8859-1-string in :length (1- length))))
(concatenate
'string (string (code-char first-byte)) rest))))
(:writer (out id)
(write-value 'iso-8859-1-string out id :length length)))
If you redefine id3-frame
to make the type of its id
slot frame-id
instead of iso-8859-1-string
, the condition will be signaled whenever id3-frame
‘s read-value
method reads a null byte instead of the beginning of a frame.
(define-tagged-binary-class id3-frame ()
((id (frame-id :length 3))
(size u3))
(:dispatch (find-frame-class id)))
Now all read-frame
has to do is wrap a call to read-value
in a **HANDLER-CASE**
that handles the in-padding
condition by returning **NIL**
.
(defun read-frame (in)
(handler-case (read-value 'id3-frame in)
(in-padding () nil)))
With read-frame
defined, you can now read a complete version 2.2 ID3 tag, representing frames with instances of generic-frame
. In the “What Frames Do You Actually Need?” section, you’ll do some experiments at the REPL to determine what frame classes you need to implement. But first let’s add support for version 2.3 ID3 tags.