Add lib/litonlylzma.FileFormatXz
diff --git a/lib/litonlylzma/example_test.go b/lib/litonlylzma/example_test.go
index d2a3b59..1825030 100644
--- a/lib/litonlylzma/example_test.go
+++ b/lib/litonlylzma/example_test.go
@@ -21,7 +21,7 @@
 	"github.com/google/wuffs/lib/litonlylzma"
 )
 
-func ExampleRoundTrip() {
+func ExampleRoundTripLZMA() {
 	original := []byte("Hello world.\n")
 	compressed, err := litonlylzma.FileFormatLZMA.Encode(nil, original)
 	if err != nil {
@@ -31,15 +31,51 @@
 	if err != nil {
 		log.Fatalf("Decode: %v", err)
 	}
+
 	for i, c := range compressed {
-		if i > 0 {
+		if (i & 15) > 0 {
 			fmt.Printf(" ")
 		}
 		fmt.Printf("%02X", c)
+		if ((i & 15) == 15) || ((i + 1) == len(compressed)) {
+			fmt.Println()
+		}
 	}
-	fmt.Printf("\n%s\n", recovered)
+	fmt.Printf("%s", recovered)
 
 	// Output:
-	// 5D 00 10 00 00 0D 00 00 00 00 00 00 00 00 24 19 49 86 E7 D5 E5 E2 56 ED 6A E6 0E 81 FE B8 00 00
+	// 5D 00 10 00 00 0D 00 00 00 00 00 00 00 00 24 19
+	// 49 86 E7 D5 E5 E2 56 ED 6A E6 0E 81 FE B8 00 00
+	// Hello world.
+}
+
+func ExampleRoundTripXz() {
+	original := []byte("Hello world.\n")
+	compressed, err := litonlylzma.FileFormatXz.Encode(nil, original)
+	if err != nil {
+		log.Fatalf("Encode: %v", err)
+	}
+	recovered, _, err := litonlylzma.FileFormatXz.Decode(nil, compressed)
+	if err != nil {
+		log.Fatalf("Decode: %v", err)
+	}
+
+	for i, c := range compressed {
+		if (i & 15) > 0 {
+			fmt.Printf(" ")
+		}
+		fmt.Printf("%02X", c)
+		if ((i & 15) == 15) || ((i + 1) == len(compressed)) {
+			fmt.Println()
+		}
+	}
+	fmt.Printf("%s", recovered)
+
+	// Output:
+	// FD 37 7A 58 5A 00 00 01 69 22 DE 36 02 00 21 01
+	// 00 00 00 00 37 27 97 D6 01 00 0C 48 65 6C 6C 6F
+	// 20 77 6F 72 6C 64 2E 0A 00 00 00 00 8E F8 31 35
+	// 00 01 21 0D 75 DC A8 D2 90 42 99 0D 01 00 00 00
+	// 00 01 59 5A
 	// Hello world.
 }
diff --git a/lib/litonlylzma/litonlylzma.go b/lib/litonlylzma/litonlylzma.go
index 53ce400..70286a2 100644
--- a/lib/litonlylzma/litonlylzma.go
+++ b/lib/litonlylzma/litonlylzma.go
@@ -14,23 +14,28 @@
 
 // ----------------
 
-// Package litonlylzma provides a decoder and encoder for Literal Only LZMA, a
-// subset of the LZMA compressed file format.
+// Package litonlylzma provides a decoder and encoder for Literal Only LZMA (or
+// Literal Only Xz), a subset of the LZMA (or Xz) compressed file formats.
 //
-// Subset means that the Encode method's output is a valid LZMA file and can be
-// decompressed by the tools available at https://www.7-zip.org/sdk.html (and
-// available as /usr/bin/lzma on a Debian system).
+// Subset means that:
+//   - the Encode methods' output is a valid LZMA/Xz file and can be
+//     decompressed by the tools available at https://www.7-zip.org/sdk.html or
+//     https://tukaani.org/xz/ (and available as /usr/bin/lzma or /usr/bin/xz
+//     on a Debian system).
+//   - the Decode methods supports the Encode methods' output but they do not
+//     support the full set of valid LZMA/Xz files.
+//   - this codec's compression ratios are not as good as full LZMA/Xz
+//     (although compression times are much faster). Moderate compression is
+//     still achieved, through range coding, but there are no Lempel Ziv
+//     back-references.
 //
-// Subset also means that this codec's compression ratios are not as good as
-// full-LZMA (although compression times are much faster). Moderate compression
-// is still achieved, through range coding, but there are no Lempel Ziv
-// back-references. The main benefit, compared to full-LZMA, is that this
-// implementation is much simpler and hence easier to study. It is only a few
-// hundred lines of code.
+// The main benefit, compared to full LZMA/Xz, is that this implementation is
+// much simpler and hence easier to study. It is around 800 lines of code.
 //
 // Example compression numbers on a small English text file (at
 // https://github.com/google/wuffs/blob/main/test/data/romeo.txt):
 //   - romeo.txt             is 942 bytes (100%).
+//   - romeo.txt.litonlyxz   is 708 bytes  (75%).
 //   - romeo.txt.litonlylzma is 659 bytes  (70%).
 //   - romeo.txt.xz          is 644 bytes  (68%).
 //   - romeo.txt.lzma        is 598 bytes  (63%).
@@ -41,6 +46,7 @@
 // Example compression numbers on a large archive of source code (at
 // https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz):
 //   - linux-5.0.1.tar             is 863313920 bytes (100%).
+//   - linux-5.0.1.tar.litonlyxz   is 454480932 bytes  (53%).
 //   - linux-5.0.1.tar.litonlylzma is 449726070 bytes  (52%).
 //   - linux-5.0.1.tar.gz          is 164575959 bytes  (19%).
 //   - linux-5.0.1.tar.zst         is 156959897 bytes  (18%).
@@ -55,13 +61,16 @@
 
 import (
 	"errors"
+	"hash/crc32"
 )
 
 var (
-	// ErrUnsupportedLZMAData is potentially returned by Decode.
+	// ErrUnsupportedXxxData is potentially returned by Decode.
 	ErrUnsupportedLZMAData = errors.New("litonlylzma: unsupported LZMA data")
+	ErrUnsupportedXzData   = errors.New("litonlylzma: unsupported Xz data")
 
 	errInvalidLZMAData       = errors.New("litonlylzma: invalid LZMA data")
+	errInvalidXzData         = errors.New("litonlylzma: invalid Xz data")
 	errUnexpectedEOF         = errors.New("litonlylzma: unexpected EOF")
 	errUnsupportedFileFormat = errors.New("litonlylzma: unsupported FileFormat")
 )
@@ -79,13 +88,30 @@
 	pbMask = (1 << pb) - 1
 )
 
-// lzmaHeader5 is the first 5 bytes of the LZMA header using the hard-coded
-// configuration for the subset-of-LZMA that this package supports. 0x5D
-// encodes the (lc, lp, pb) triple and the next four bytes hold the u32le
-// dictionary size. 0x1000 is LZMA's minimum dictionary size, although our
+// lzmaHeader5 is the first 5 bytes of an LZMA file using the hard-coded
+// configuration for the subset-of-LZMA that this package supports.
+//
+// 0x5D encodes the (lc, lp, pb) triple and the next four bytes hold the u32le
+// dictionary size. 0x00001000 is LZMA's minimum dictionary size, although our
 // subset-of-LZMA does not use a dictionary (also called a "sliding window").
 const lzmaHeader5 = "\x5D\x00\x10\x00\x00"
 
+// xzHeader24 is the first 24 bytes of an Xz file using the hard-coded
+// configuration for the subset-of-Xz that this package supports.
+const xzHeader24 = "" +
+	"\xFD\x37\x7A\x58\x5A\x00" + // Stream Header Magic Bytes.
+	// ----
+	"\x00\x01" + // Stream Flags (0x01 means CRC-32).
+	"\x69\x22\xDE\x36" + // CRC-32 of the previous 2 bytes.
+	// ----
+	"\x02" + // Block Header Size (0x02 * 4 = 8 bytes).
+	"\x00" + // Block Flags.
+	"\x21" + // Filter ID (0x21 means LZMA2).
+	"\x01" + // Size of Filter Properties (size of the next line).
+	"\x00" + // Filter Properties (0x00 means an 0x1000 dictionary size).
+	"\x00\x00\x00" + // Padding to 4-byte alignment.
+	"\x37\x27\x97\xD6" // CRC-32 of the previous 8 bytes.
+
 // rangeDecoder and rangeEncoder hold the state for range coding (also known as
 // arithmetic coding, roughly speaking). "range" is a keyword in Go, so in this
 // implementation, what's traditionally called the "range" fields are called
@@ -120,13 +146,13 @@
 // digit is stashed as the pendingHead field. low is set to 8872, width is set
 // to (46055 - 38872) = 7183 and we progress until the width drops below 1000,
 // causing a 'zoom in' event. There are three cases:
-//  - if low is less than 9000 (but starts with an '8'), we can emit the '3'
-//    with confidence (and then stash '8' in the pendingHead field).
-//  - if low overflows 9999, we can emit the '4' with confidence (and then stash
-//    low's 'thousands digit' in the pendingHead field).
-//  - if low is in 9000 ..= 9999 then we could still be unsure. Keep the '3'
-//    pending but extend it so that there are two pending digits: '39'. If this
-//    occurs again: '399', and so on.
+//   - if low is less than 9000 (but starts with an '8'), we can emit the '3'
+//     with confidence (and then stash '8' in the pendingHead field).
+//   - if low overflows 9999, we can emit the '4' with confidence (and then
+//     stash low's 'thousands digit' in the pendingHead field).
+//   - if low is in 9000 ..= 9999 then we could still be unsure. Keep the '3'
+//     pending but extend it so that there are two pending digits: '39'. If
+//     this occurs again: '399', and so on.
 //
 // In all three cases, 'zooming in' causes us to shift out the most significant
 // digit of the low field. This method is therefore named shiftLow here, the
@@ -320,7 +346,7 @@
 }
 
 // FileFormat is a compressed file format, either the original LZMA format
-// itself or something that builds on or varies that.
+// itself or something like Xz that builds on or varies that.
 //
 // Each valid FileFormatXxx value does not distinguish between the full file
 // format (as spoken by numerous Xxx software tools) and the subset-of-Xxx
@@ -328,26 +354,27 @@
 // produce compressed data that is valid full-LZMA (and is also valid
 // subset-of-LZMA). FileFormatLZMA.Decode can decode subset-of-LZMA (but not
 // full-LZMA).
-//
-// This package currently defines only one valid FileFormatXxx value:
-// FileFormatLZMA. Future versions of this package could define e.g.
-// FileFormatLZMA2 or FileFormatXz such that FileFormatXz.Encode would produce
-// valid full-XZ data: a valid XZ file (that could be decoded by official XZ
-// tools) that used the LZMA compression filter, but elected not to use the
-// full capabilities of that filter (using LZ literals only, not matches).
 type FileFormat uint32
 
 const (
 	FileFormatInvalid = FileFormat(0)
 	FileFormatLZMA    = FileFormat(1)
+	FileFormatXz      = FileFormat(2)
 )
 
-// Decode converts src from the Literal Only LZMA compressed file format,
+func (f FileFormat) String() string {
+	switch f {
+	case FileFormatLZMA:
+		return "FileFormatLZMA"
+	case FileFormatXz:
+		return "FileFormatXz"
+	}
+	return "FileFormatInvalid"
+}
+
+// Decode converts src from the Literal Only LZMA/Xz compressed file format,
 // appending the decoding to dst.
 //
-// f should be FileFormatLZMA but future versions of this package may support
-// other file formats.
-//
 // It is valid to pass a nil dst, like the way it's valid to pass nil to the
 // built-in append function.
 //
@@ -356,14 +383,19 @@
 // It can also return partial results. The appendedDst and remainingSrc return
 // values may be non-zero even if retErr is non-zero.
 //
-// It returns ErrUnsupportedLZMAData if the source bytes look like LZMA
-// formatted data that is outside the subset-of-LZMA that this package
-// implements.
+// It returns ErrUnsupportedXxxData if the source bytes look like Xxx formatted
+// data that is outside the subset-of-Xxx that this package implements.
 func (f FileFormat) Decode(dst []byte, src []byte) (appendedDst []byte, remainingSrc []byte, retErr error) {
-	if f != FileFormatLZMA {
-		return dst, nil, errUnsupportedFileFormat
+	switch f {
+	case FileFormatLZMA:
+		return decodeLZMA(dst, src)
+	case FileFormatXz:
+		return decodeXz(dst, src)
 	}
+	return nil, nil, errUnsupportedFileFormat
+}
 
+func decodeLZMA(dst []byte, src []byte) (appendedDst []byte, remainingSrc []byte, retErr error) {
 	// LZMA consists of a 13 byte header and then at least a 5 byte payload
 	// (range coded data). The 13 byte header is:
 	//  - 1 byte for the (lc, lp, pb) triple with max values (8, 4, 4),
@@ -371,12 +403,7 @@
 	//  - 8 bytes i64le uncompressed size.
 	if (len(src) < 18) || (src[0] >= (9 * 5 * 5)) {
 		return dst, nil, errInvalidLZMAData
-	} else if (src[0] != lzmaHeader5[0]) ||
-		(src[1] != lzmaHeader5[1]) ||
-		(src[2] != lzmaHeader5[2]) ||
-		(src[3] != lzmaHeader5[3]) ||
-		(src[4] != lzmaHeader5[4]) ||
-		(src[13] != 0x00) {
+	} else if string(src[:5]) != lzmaHeader5 {
 		return dst, nil, ErrUnsupportedLZMAData
 	}
 
@@ -390,12 +417,151 @@
 		return dst, nil, ErrUnsupportedLZMAData
 	}
 
+	return decodeRaw(dst, src[13:], size, ErrUnsupportedLZMAData)
+}
+
+func decodeXz(dst []byte, src []byte) (appendedDst []byte, remainingSrc []byte, retErr error) {
+	originalDstLen := len(dst)
+	originalSrcLen := len(src)
+
+	// Decode the headers.
+	if (len(src) < 24) || (string(src[:6]) != xzHeader24[:6]) {
+		return dst, src, errInvalidXzData
+	} else if string(src[6:24]) != xzHeader24[6:24] {
+		return dst, src, ErrUnsupportedXzData
+	}
+	src = src[24:]
+
+	// Decode the payload as multiple chunks.
+	for {
+		if len(src) == 0 {
+			return dst, src, errInvalidXzData
+
+		} else if src[0] == 0x00 { // No more chunks.
+			src = src[1:]
+			break
+
+		} else if src[0] == 0x01 { // Independent uncompressed chunk.
+			if len(src) < 3 {
+				return dst, src, errInvalidXzData
+			}
+			uncompressedSize := (uint32(src[1]) << 8) + (uint32(src[2]) << 0) + 1
+			src = src[3:]
+			if uint64(uncompressedSize) > uint64(len(src)) {
+				return dst, src, errInvalidXzData
+			}
+			dst = append(dst, src[:uncompressedSize]...)
+			src = src[uncompressedSize:]
+
+		} else if src[0] == 0xE0 { // Independent LZMA chunk.
+			if len(src) < 6 {
+				return dst, src, errInvalidXzData
+			} else if src[5] != 0x5D { // Like lzmaHeader5[0], this encodes lc=3, lp=0, pb=2.
+				return dst, src, ErrUnsupportedXzData
+			}
+			uncompressedSize := (uint32(src[1]) << 8) + (uint32(src[2]) << 0) + 1
+			compressedSize := (uint32(src[3]) << 8) + (uint32(src[4]) << 0) + 1
+			src = src[6:]
+			if uint64(compressedSize) > uint64(len(src)) {
+				return dst, src, errInvalidXzData
+			}
+			lenSrc := len(src)
+			dst, src, retErr = decodeRaw(dst, src, uint64(uncompressedSize), ErrUnsupportedXzData)
+			if retErr != nil {
+				return dst, src, retErr
+			} else if lenSrc != (len(src) + int(compressedSize)) {
+				return dst, src, errInvalidXzData
+			}
+
+		} else {
+			return dst, src, ErrUnsupportedXzData
+		}
+	}
+	unpaddedSizeWant := uint64((originalSrcLen - 12) - len(src) + 4)
+	for i := unpaddedSizeWant & 3; (i & 3) != 0; i++ {
+		if (len(src) == 0) || (src[0] != 0x00) {
+			return dst, src, errInvalidXzData
+		}
+		src = src[1:]
+	}
+	if len(src) < 4 {
+		return dst, src, errInvalidXzData
+	}
+	checksum := crc32.ChecksumIEEE(dst[originalDstLen:])
+	if (src[0] != uint8(checksum>>0)) ||
+		(src[1] != uint8(checksum>>8)) ||
+		(src[2] != uint8(checksum>>16)) ||
+		(src[3] != uint8(checksum>>24)) {
+		return dst, src, errInvalidXzData
+	}
+	src = src[4:]
+
+	// Decode the index.
+	srcCheckpoint1 := src
+	if (len(src) < 2) || (src[0] != 0x00) || (src[1] != 0x01) {
+		return dst, src, errInvalidXzData
+	}
+	src = src[2:]
+	src, unpaddedSizeHave, ok := decodeUvarint(src)
+	if !ok || (unpaddedSizeHave != unpaddedSizeWant) {
+		return dst, src, errInvalidXzData
+	}
+	src, uncompressedSize, ok := decodeUvarint(src)
+	if !ok || (uncompressedSize != uint64(len(dst)-originalDstLen)) {
+		return dst, src, errInvalidXzData
+	}
+	for i := (len(srcCheckpoint1) - len(src)) & 3; (i & 3) != 0; i++ {
+		if (len(src) == 0) || (src[0] != 0x00) {
+			return dst, src, errInvalidXzData
+		}
+		src = src[1:]
+	}
+	backwardSize := (len(srcCheckpoint1) - len(src)) >> 2
+	if len(src) < 4 {
+		return dst, src, errInvalidXzData
+	}
+	checksum = crc32.ChecksumIEEE(srcCheckpoint1[:len(srcCheckpoint1)-len(src)])
+	if (src[0] != uint8(checksum>>0)) ||
+		(src[1] != uint8(checksum>>8)) ||
+		(src[2] != uint8(checksum>>16)) ||
+		(src[3] != uint8(checksum>>24)) {
+		return dst, src, errInvalidXzData
+	}
+	src = src[4:]
+
+	// Decode the footer.
+	if len(src) < 12 {
+		return dst, src, errInvalidXzData
+	}
+	checksum = crc32.ChecksumIEEE(src[4:10])
+	if (src[0] != uint8(checksum>>0)) || // Checksum.
+		(src[1] != uint8(checksum>>8)) ||
+		(src[2] != uint8(checksum>>16)) ||
+		(src[3] != uint8(checksum>>24)) ||
+		(src[4] != uint8(backwardSize>>0)) || // Backward Size.
+		(src[5] != uint8(backwardSize>>8)) ||
+		(src[6] != uint8(backwardSize>>16)) ||
+		(src[7] != uint8(backwardSize>>24)) ||
+		(src[8] != xzHeader24[6]) || // Stream Flags again.
+		(src[9] != xzHeader24[7]) ||
+		(src[10] != 0x59) || // Stream Footer Magic Bytes.
+		(src[11] != 0x5A) {
+		return dst, src, errInvalidXzData
+	}
+	return dst, src[12:], nil
+}
+
+func decodeRaw(dst []byte, src []byte, size uint64, errUnsupported error) (appendedDst []byte, remainingSrc []byte, retErr error) {
+	if (len(src) < 5) || (src[0] != 0x00) {
+		return dst, src, errUnsupported
+	}
+
 	rDec := rangeDecoder{
-		src: src[18:],
-		bits: (uint32(src[14]) << 24) |
-			(uint32(src[15]) << 16) |
-			(uint32(src[16]) << 8) |
-			(uint32(src[17]) << 0),
+		src: src[5:],
+		bits: (uint32(src[1]) << 24) |
+			(uint32(src[2]) << 16) |
+			(uint32(src[3]) << 8) |
+			(uint32(src[4]) << 0),
 		width: 0xFFFF_FFFF,
 	}
 
@@ -412,12 +578,12 @@
 	curr := byte(0)
 	for ; size > 0; size-- {
 		if bitValue, err := posProbs[pos&pbMask].decodeBit(&rDec); err != nil {
-			return dst, nil, err
+			return dst, rDec.src, err
 		} else if bitValue != 0 {
 			// A full-LZMA decoder would implement Lempel Ziv matches (or the
 			// End of Stream marker) here. Our simpler subset-of-LZMA decoder
 			// only uses literals.
-			return dst, rDec.src, ErrUnsupportedLZMAData
+			return dst, rDec.src, errUnsupported
 		}
 
 		i := (pos & lpMask) << lc
@@ -433,26 +599,141 @@
 	return dst, rDec.src, nil
 }
 
+func decodeUvarint(src []byte) (remainingSrc []byte, x uint64, ok bool) {
+	for i := uint32(0); (i < 63) && (len(src) > 0); i += 7 {
+		s := src[0]
+		src = src[1:]
+		x |= uint64(s&0x7F) << i
+		if (s & 0x80) == 0 {
+			return src, x, true
+		}
+	}
+	return src, 0, false
+}
+
 // Encode converts src to the Literal Only LZMA compressed file format,
 // appending the encoding to dst.
 //
-// f should be FileFormatLZMA but future versions of this package may support
-// other file formats.
-//
 // It is valid to pass a nil dst, like the way it's valid to pass nil to the
 // built-in append function.
 func (f FileFormat) Encode(dst []byte, src []byte) (appendedDst []byte, retErr error) {
-	if f != FileFormatLZMA {
-		return nil, errUnsupportedFileFormat
+	switch f {
+	case FileFormatLZMA:
+		return encodeLZMA(dst, src)
+	case FileFormatXz:
+		return encodeXz(dst, src)
 	}
+	return nil, errUnsupportedFileFormat
+}
 
+func encodeLZMA(dst []byte, src []byte) (appendedDst []byte, retErr error) {
 	dst = append(dst, lzmaHeader5...)
 	size := len(src)
 	for i := 0; i < 8; i++ {
 		dst = append(dst, byte(size))
 		size >>= 8
 	}
+	return encodeRaw(dst, src), nil
+}
 
+func encodeXz(dst []byte, src []byte) (appendedDst []byte, retErr error) {
+	// Encode the headers (12 byte stream header then 12 byte block header).
+	// The block's unpaddedSize calculation ignores the stream header.
+	dstLen0 := len(dst) + 12
+	dst = append(dst, xzHeader24...)
+
+	// Encode the payload as multiple chunks.
+	//
+	// Later XZ chunks can depend on earlier ones (and doing so can help
+	// compression ratios) but for simplicity, this encoder emits independent
+	// chunks. Each chunk's maximum compressed and uncompressed size are both
+	// equal to 0x10000 = 65536 bytes, roughly speaking. The encoder partitions
+	// the src into 65536 byte sub-slices. If a sub-slice's LZMA-compressed
+	// form would be longer, the encoder emits an uncompressed chunk instead.
+	rawLZMA := []byte(nil)
+	for remaining := src; len(remaining) > 0; {
+		srcChunk := []byte(nil)
+		if len(remaining) > 0x10000 {
+			srcChunk, remaining = remaining[:0x10000], remaining[0x10000:]
+		} else {
+			srcChunk, remaining = remaining, nil
+		}
+
+		rawLZMA = encodeRaw(rawLZMA[:0], srcChunk)
+		if (len(srcChunk) + 3) <= (len(rawLZMA) + 6) {
+			dst = append(dst,
+				0x01, // Independent uncompressed chunk.
+				uint8((len(srcChunk)-1)>>8),
+				uint8((len(srcChunk)-1)>>0),
+			)
+			dst = append(dst, srcChunk...)
+		} else {
+			dst = append(dst,
+				0xE0, // Independent LZMA chunk.
+				uint8((len(srcChunk)-1)>>8),
+				uint8((len(srcChunk)-1)>>0),
+				uint8((len(rawLZMA)-1)>>8),
+				uint8((len(rawLZMA)-1)>>0),
+				0x5D, // Like lzmaHeader5[0], this encodes lc=3, lp=0, pb=2.
+			)
+			dst = append(dst, rawLZMA...)
+		}
+	}
+	dst = append(dst, 0x00)                      // No more chunks.
+	unpaddedSize := uint64(len(dst)-dstLen0) + 4 // +4 for the upcoming checksum.
+	for 0 != 3&(len(dst)-dstLen0) {
+		dst = append(dst, 0x00) // Padding
+	}
+	checksum := crc32.ChecksumIEEE(src)
+	dst = append(dst,
+		uint8(checksum>>0),
+		uint8(checksum>>8),
+		uint8(checksum>>16),
+		uint8(checksum>>24),
+	)
+
+	// Encode the index.
+	dstLen1 := len(dst)
+	dst = append(dst,
+		0x00, // Index Indicator.
+		0x01, // Number of Records.
+	)
+	dst = encodeUvarint(dst, unpaddedSize)
+	dst = encodeUvarint(dst, uint64(len(src)))
+	for 0 != 3&(len(dst)-dstLen1) {
+		dst = append(dst, 0x00) // Padding
+	}
+	backwardSize := (len(dst) - dstLen1) >> 2
+	checksum = crc32.ChecksumIEEE(dst[dstLen1:])
+	dst = append(dst,
+		uint8(checksum>>0),
+		uint8(checksum>>8),
+		uint8(checksum>>16),
+		uint8(checksum>>24),
+	)
+
+	// Encode the footer.
+	dstLen2 := len(dst)
+	dst = append(dst,
+		0, 0, 0, 0, // Space for the checksum.
+		uint8(backwardSize>>0), // Backward Size.
+		uint8(backwardSize>>8),
+		uint8(backwardSize>>16),
+		uint8(backwardSize>>24),
+		xzHeader24[6], // Stream Flags again.
+		xzHeader24[7],
+		0x59, // Stream Footer Magic Bytes.
+		0x5A,
+	)
+	checksum = crc32.ChecksumIEEE(dst[dstLen2+4 : dstLen2+10])
+	dst[dstLen2+0] = uint8(checksum >> 0)
+	dst[dstLen2+1] = uint8(checksum >> 8)
+	dst[dstLen2+2] = uint8(checksum >> 16)
+	dst[dstLen2+3] = uint8(checksum >> 24)
+	return dst, nil
+}
+
+func encodeRaw(dst []byte, src []byte) (appendedDst []byte) {
 	rEnc := rangeEncoder{
 		dst:   dst,
 		width: 0xFFFF_FFFF,
@@ -487,5 +768,12 @@
 		rEnc.shiftLow()
 	}
 
-	return rEnc.dst, nil
+	return rEnc.dst
+}
+
+func encodeUvarint(dst []byte, x uint64) []byte {
+	for ; x >= 0x80; x >>= 7 {
+		dst = append(dst, byte(x)|0x80)
+	}
+	return append(dst, byte(x))
 }
diff --git a/lib/litonlylzma/litonlylzma_test.go b/lib/litonlylzma/litonlylzma_test.go
index 3c68191..2935a6c 100644
--- a/lib/litonlylzma/litonlylzma_test.go
+++ b/lib/litonlylzma/litonlylzma_test.go
@@ -43,16 +43,23 @@
 		original = o
 	}
 
-	compressed, err := FileFormatLZMA.Encode(nil, original)
-	if err != nil {
-		tt.Fatalf("Encode: %v", err)
+	fileFormats := [...]FileFormat{
+		FileFormatLZMA,
+		FileFormatXz,
 	}
-	recovered, _, err := FileFormatLZMA.Decode(nil, compressed)
-	if err != nil {
-		tt.Fatalf("Decode: %v", err)
-	}
-	if !bytes.Equal(original, recovered) {
-		tt.Fatalf("round trip produced different bytes")
+
+	for _, f := range fileFormats {
+		compressed, err := f.Encode(nil, original)
+		if err != nil {
+			tt.Fatalf("%v.Encode: %v", f, err)
+		}
+		recovered, _, err := f.Decode(nil, compressed)
+		if err != nil {
+			tt.Fatalf("%v.Decode: %v", f, err)
+		}
+		if !bytes.Equal(original, recovered) {
+			tt.Fatalf("%v: round trip produced different bytes", f)
+		}
 	}
 }