xz.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. ;;; Disarchive
  2. ;;; Copyright © 2021 Timothy Sample <samplet@ngyro.com>
  3. ;;;
  4. ;;; This file is part of Disarchive.
  5. ;;;
  6. ;;; Disarchive is free software: you can redistribute it and/or modify
  7. ;;; it under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation, either version 3 of the License, or
  9. ;;; (at your option) any later version.
  10. ;;;
  11. ;;; Disarchive is distributed in the hope that it will be useful,
  12. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with Disarchive. If not, see <http://www.gnu.org/licenses/>.
  18. (use-modules (disarchive kinds xz)
  19. (disarchive utils)
  20. (quickcheck)
  21. (quickcheck arbitrary)
  22. (quickcheck generator)
  23. (quickcheck property)
  24. (rnrs bytevectors)
  25. (srfi srfi-1)
  26. (srfi srfi-64))
  27. (define-syntax-rule (true-if-exception expr)
  28. (not (false-if-exception (or expr #t))))
  29. (define ($maybe elem)
  30. ($choose
  31. (identity elem)
  32. (not ($const #f))))
  33. (define $nibble
  34. (arbitrary
  35. (gen (choose-integer 0 15))
  36. (xform generator-variant)))
  37. (define $uint32
  38. (arbitrary
  39. (gen (choose-integer 0 (1- (expt 2 32))))
  40. (xform generator-variant)))
  41. (define $crc32 ($maybe $uint32))
  42. (define $crc32-bytevector
  43. (arbitrary
  44. (gen (choose-bytevector 4))
  45. (xform (arbitrary-xform $bytevector))))
  46. (define tests-per-property 1000)
  47. (configure-quickcheck
  48. (stop? (lambda (success-count _)
  49. (>= success-count tests-per-property))))
  50. (define (test-size-zero-to-ten test-number)
  51. (if (zero? test-number)
  52. 0
  53. (1+ (quotient (* test-number 10) tests-per-property))))
  54. (test-begin "kinds--xz")
  55. ;; XZ multibyte integers
  56. (define-syntax-rule (bytevector-u8-update! bv index name exp)
  57. (let ((name (bytevector-u8-ref bv index)))
  58. (bytevector-u8-set! bv index exp)))
  59. (define (bytevector-ensure-xz-integer! bv)
  60. (let loop ((k 0))
  61. (when (< k (bytevector-length bv))
  62. (if (< k (1- (bytevector-length bv)))
  63. (bytevector-u8-update! bv k b (logior b #x80))
  64. (bytevector-u8-update! bv k b (let ((b* (logand b #x7f)))
  65. (if (and (> k 0) (zero? b*))
  66. 1
  67. b*))))
  68. (loop (1+ k)))))
  69. (define $xz-integer $natural)
  70. (define $xz-integer-bytevector
  71. (arbitrary
  72. (gen (generator-let* ((bv (sized-generator choose-bytevector)))
  73. (if (zero? (bytevector-length bv))
  74. (generator-return #vu8(0))
  75. (begin
  76. (bytevector-ensure-xz-integer! bv)
  77. (generator-return bv)))))
  78. (xform (arbitrary-xform $bytevector))))
  79. (test-assert "Refuses to decode an XZ integer with high ending"
  80. (true-if-exception
  81. (let ((bv #vu8(128)))
  82. (decode-xz-integer bv))))
  83. (test-assert "Refuses to decode an XZ integer with low middle"
  84. (true-if-exception
  85. (let ((bv #vu8(127 128)))
  86. (decode-xz-integer bv))))
  87. (test-assert "Refuses to decode an XZ integer ending in zero"
  88. (true-if-exception
  89. (let ((bv #vu8(128 0)))
  90. (decode-xz-integer bv))))
  91. (configure-quickcheck
  92. (size test-size-zero-to-ten))
  93. (test-assert "[prop] Reading XZ integers is reversible"
  94. (quickcheck
  95. (property ((bv $xz-integer-bytevector))
  96. (equal? bv (encode-xz-integer (decode-xz-integer bv))))))
  97. (test-assert "[prop] Writing XZ integers is reversible"
  98. (quickcheck
  99. (property ((n $xz-integer))
  100. (equal? n (decode-xz-integer (encode-xz-integer n))))))
  101. ;; XZ stream headers
  102. (define xz-magic-header #vu8(#xfd #x37 #x7a #x58 #x5a #x00))
  103. (define $xz-stream-header
  104. ($record make-xz-stream-header
  105. (xz-stream-header-check-type $nibble)
  106. (xz-stream-header-reserved ($tuple $byte $nibble))
  107. (xz-stream-header-crc32 $crc32)))
  108. (define (bytevector-set-xz-magic-header! bv)
  109. (bytevector-copy! xz-magic-header 0 bv 0
  110. (min (bytevector-length xz-magic-header)
  111. (bytevector-length bv))))
  112. (test-assert "Refuses to decode a small XZ stream header"
  113. (true-if-exception
  114. (let ((bv (make-bytevector 10)))
  115. (bytevector-set-xz-magic-header! bv)
  116. (decode-xz-stream-header bv))))
  117. (test-assert "Refuses to decode a large XZ stream header"
  118. (true-if-exception
  119. (let ((bv (make-bytevector 14)))
  120. (bytevector-set-xz-magic-header! bv)
  121. (decode-xz-stream-header bv))))
  122. (test-assert "Refuses to decode XZ stream header without magic"
  123. (true-if-exception
  124. (let ((bv (make-bytevector 12)))
  125. (decode-xz-stream-header bv))))
  126. (test-assert "Does not store a trivial XZ stream header CRC"
  127. (let ((bv #vu8(0 0 0 0 0 0 1 2 146 66 204 182)))
  128. (bytevector-set-xz-magic-header! bv)
  129. (let ((header (decode-xz-stream-header bv)))
  130. (not (xz-stream-header-crc32 header)))))
  131. (configure-quickcheck
  132. (size (const 12)))
  133. (test-assert "[prop] Reading XZ stream headers is reversible"
  134. (quickcheck
  135. (property ((bv $bytevector))
  136. (bytevector-set-xz-magic-header! bv)
  137. (equal? bv (encode-xz-stream-header (decode-xz-stream-header bv))))))
  138. (test-assert "[prop] Writing XZ stream headers is reversible"
  139. (quickcheck
  140. (property ((header $xz-stream-header))
  141. (let ((bv (encode-xz-stream-header header)))
  142. ;; If we happen to generate a header with the correct CRC, it
  143. ;; will become #f when we decode it.
  144. (test-when (not (and=> (xz-stream-header-crc32 header)
  145. (lambda (crc32)
  146. (= crc32 (bytevector-xz-crc32 bv 6 8)))))
  147. (equal? header (decode-xz-stream-header bv)))))))
  148. ;; XZ stream footers
  149. (define xz-magic-footer #vu8(#x59 #x5a))
  150. (define $xz-size
  151. (arbitrary
  152. (gen (generator-let* ((n (choose-integer 0 (1- (expt 2 32)))))
  153. (generator-return (* (1+ n) 4))))
  154. (xform generator-variant)))
  155. (define $xz-stream-footer
  156. ($record make-xz-stream-footer
  157. (xz-stream-footer-check-type $nibble)
  158. (xz-stream-footer-reserved ($tuple $byte $nibble))
  159. (xz-stream-footer-backward-size $xz-size)
  160. (xz-stream-footer-crc32 $crc32)))
  161. (define (bytevector-set-xz-magic-footer! bv)
  162. (let* ((size (min (bytevector-length xz-magic-footer)
  163. (bytevector-length bv)))
  164. (start (- (bytevector-length bv) size)))
  165. (bytevector-copy! xz-magic-footer 0 bv start size)))
  166. (test-assert "Refuses to decode a small XZ stream footer"
  167. (true-if-exception
  168. (let ((bv (make-bytevector 14)))
  169. (bytevector-set-xz-magic-footer! bv)
  170. (decode-xz-stream-footer bv))))
  171. (test-assert "Refuses to decode a large XZ stream footer"
  172. (true-if-exception
  173. (let ((bv (make-bytevector 10)))
  174. (bytevector-set-xz-magic-footer! bv)
  175. (decode-xz-stream-footer bv))))
  176. (test-assert "Refuses to decode XZ stream footer without magic"
  177. (true-if-exception
  178. (let ((bv (make-bytevector 12)))
  179. (decode-xz-stream-footer bv))))
  180. (test-assert "Does not store a trivial XZ stream footer CRC"
  181. (let ((bv #vu8(36 119 246 129 1 2 3 4 5 6 0 0)))
  182. (bytevector-set-xz-magic-footer! bv)
  183. (let ((footer (decode-xz-stream-footer bv)))
  184. (not (xz-stream-footer-crc32 footer)))))
  185. (configure-quickcheck
  186. (size (const 12)))
  187. (test-assert "[prop] Reading XZ stream footers is reversible"
  188. (quickcheck
  189. (property ((bv $bytevector))
  190. (bytevector-set-xz-magic-footer! bv)
  191. (equal? bv (encode-xz-stream-footer (decode-xz-stream-footer bv))))))
  192. (test-assert "[prop] Writing XZ stream footers is reversible"
  193. (quickcheck
  194. (property ((footer $xz-stream-footer))
  195. (let ((bv (encode-xz-stream-footer footer)))
  196. ;; If we happen to generate a footer with the correct CRC, it
  197. ;; will become #f when we decode it.
  198. (test-when (not (and=> (xz-stream-footer-crc32 footer)
  199. (lambda (crc32)
  200. (= crc32 (bytevector-xz-crc32 bv 6 8)))))
  201. (equal? footer (decode-xz-stream-footer bv)))))))
  202. ;; XZ filter flags
  203. (define $xz-filter-flags
  204. ($record make-xz-filter-flags
  205. (xz-filter-flags-id $xz-integer)
  206. (xz-filter-flags-properties $bytevector)))
  207. (define $xz-filter-flags-bytevector
  208. (let ((choose-xz-integer-bytevector (arbitrary-gen $xz-integer-bytevector)))
  209. (arbitrary
  210. (gen (generator-let* ((id choose-xz-integer-bytevector)
  211. (props (sized-generator choose-bytevector)))
  212. (let ((size (encode-xz-integer (bytevector-length props))))
  213. (generator-return (bytevector-append id size props)))))
  214. (xform (arbitrary-xform $bytevector)))))
  215. (test-assert "Refuses to decode small XZ filter flags properties"
  216. (true-if-exception
  217. (let ((bv #vu8(0 2 1)))
  218. (decode-xz-filter-flags bv))))
  219. (test-assert "Refuses to decode large XZ filter flags properties"
  220. (true-if-exception
  221. (let ((bv #vu8(0 2 1 2 3)))
  222. (decode-xz-filter-flags bv))))
  223. (configure-quickcheck
  224. (size test-size-zero-to-ten))
  225. (test-assert "[prop] Reading XZ filter flags is reversible"
  226. (quickcheck
  227. (property ((bv $xz-filter-flags-bytevector))
  228. (equal? bv (encode-xz-filter-flags (decode-xz-filter-flags bv))))))
  229. (test-assert "[prop] Writing XZ filter flags is reversible"
  230. (quickcheck
  231. (property ((filter $xz-filter-flags))
  232. (equal? filter (decode-xz-filter-flags
  233. (encode-xz-filter-flags filter))))))
  234. ;; XZ block headers
  235. (define ($one-to-four elem)
  236. (let* ((base ($list elem)))
  237. (arbitrary
  238. (gen (sized-generator
  239. (lambda (size)
  240. (choose-list (arbitrary-gen elem) (1+ (modulo size 4))))))
  241. (xform (arbitrary-xform base)))))
  242. (define $xz-block-header
  243. ;; We do a little dance here to set the padding correctly.
  244. (let* ((base ($record make-xz-block-header
  245. (xz-block-header-reserved $nibble)
  246. (xz-block-header-compressed-size ($maybe $xz-integer))
  247. (xz-block-header-uncompressed-size ($maybe $xz-integer))
  248. (xz-block-header-filters ($one-to-four $xz-filter-flags))
  249. (xz-block-header-padding $nibble)
  250. (xz-block-header-crc32 $crc32)))
  251. (base-gen (arbitrary-gen base)))
  252. (arbitrary
  253. (gen (generator-let* ((bh base-gen))
  254. (let* ((size (- (xz-block-header-size bh)
  255. (xz-block-header-padding bh)))
  256. (remainder (modulo size 4))
  257. (base-padding (if (zero? remainder) 0 (- 4 remainder)))
  258. (padding (+ (* 4 (xz-block-header-padding bh))
  259. base-padding)))
  260. (generator-return
  261. (set-xz-block-header-padding bh padding)))))
  262. (xform (arbitrary-xform base)))))
  263. (configure-quickcheck
  264. (size test-size-zero-to-ten))
  265. (test-assert "[prop] Reading XZ block headers is reversible"
  266. (quickcheck
  267. (property ((reserved $nibble)
  268. (c-size ($maybe $xz-integer-bytevector))
  269. (u-size ($maybe $xz-integer-bytevector))
  270. (filters ($one-to-four $xz-filter-flags-bytevector))
  271. (extra-padding $nibble)
  272. (crc $crc32-bytevector))
  273. (let* ((base-size (+ 1 1 4
  274. (if c-size (bytevector-length c-size) 0)
  275. (if u-size (bytevector-length u-size) 0)
  276. (reduce + 0 (map bytevector-length filters))
  277. (* 4 extra-padding)))
  278. (remainder (modulo base-size 4))
  279. (padding (if (zero? remainder) 0 (- 4 remainder)))
  280. (size (+ base-size padding))
  281. (encoded-size (1- (quotient size 4)))
  282. (flags (logior (1- (length filters))
  283. (ash reserved 2)
  284. (ash (if c-size 1 0) 6)
  285. (ash (if u-size 1 0) 7)))
  286. (filters-bv (apply bytevector-append filters))
  287. (padding-bv (make-bytevector (+ (* 4 extra-padding) padding) 0))
  288. (bv (bytevector-append #vu8(0 0)
  289. (or c-size #vu8())
  290. (or u-size #vu8())
  291. filters-bv
  292. padding-bv
  293. crc)))
  294. (bytevector-u8-set! bv 0 encoded-size)
  295. (bytevector-u8-set! bv 1 flags)
  296. (equal? bv (encode-xz-block-header (decode-xz-block-header bv)))))))
  297. (test-assert "[prop] Writing XZ block headers is reversible"
  298. (quickcheck
  299. (property ((header $xz-block-header))
  300. (equal? header (decode-xz-block-header
  301. (encode-xz-block-header header))))))
  302. ;; XZ index records
  303. (define $xz-index-record
  304. ($record make-xz-index-record
  305. (xz-index-record-unpadded-size $xz-integer)
  306. (xz-index-record-uncompressed-size $xz-integer)))
  307. (define $xz-index-record-bytevector
  308. (let ((choose-xz-integer-bytevector (arbitrary-gen $xz-integer-bytevector)))
  309. (arbitrary
  310. (gen (generator-let* ((up-size choose-xz-integer-bytevector)
  311. (uc-size choose-xz-integer-bytevector))
  312. (generator-return (bytevector-append up-size uc-size))))
  313. (xform (arbitrary-xform $bytevector)))))
  314. (test-assert "Refuses to decode small XZ index record"
  315. (true-if-exception
  316. (let ((bv #vu8(1)))
  317. (decode-xz-index-record bv))))
  318. (test-assert "Refuses to decode large XZ index record"
  319. (true-if-exception
  320. (let ((bv #vu8(1 1 1)))
  321. (decode-xz-index-record bv))))
  322. (configure-quickcheck
  323. (size test-size-zero-to-ten))
  324. (test-assert "[prop] Reading XZ index records is reversible"
  325. (quickcheck
  326. (property ((bv $xz-index-record-bytevector))
  327. (equal? bv (encode-xz-index-record (decode-xz-index-record bv))))))
  328. (test-assert "[prop] Writing XZ index records is reversible"
  329. (quickcheck
  330. (property ((record $xz-index-record))
  331. (equal? record (decode-xz-index-record
  332. (encode-xz-index-record record))))))
  333. ;; XZ indexes
  334. (define $xz-index
  335. ($record make-xz-index
  336. (xz-index-records ($list $xz-index-record))
  337. (xz-index-crc32 $crc32)))
  338. (test-assert "Refuses to decode a small XZ index"
  339. (true-if-exception
  340. (let ((bv #vu8(0 2 1 1 0 0 0 0)))
  341. (decode-xz-index bv))))
  342. (test-assert "Refuses to decode a large XZ index"
  343. (true-if-exception
  344. (let ((bv #vu8(0 3 1 1 2 2 3 3 4 4 0 0 0 0 0 0)))
  345. (decode-xz-index bv))))
  346. (test-assert "Refuses to decode XZ stream index without magic"
  347. (true-if-exception
  348. ;; The initial zero is the "magic".
  349. (let ((bv #vu8(1 1 1 1 0 0 0 0)))
  350. (decode-xz-index bv))))
  351. (test-assert "Does not store a trivial XZ index CRC"
  352. (let ((bv #vu8(0 1 1 1 252 180 154 78)))
  353. (let ((header (decode-xz-index bv)))
  354. (not (xz-index-crc32 header)))))
  355. (test-assert "Refuses to decode XZ index with nonzero padding"
  356. (true-if-exception
  357. (let ((bv #vu8(0 2 1 1 2 2 3 3 0 0 0 0)))
  358. (decode-xz-index bv))))
  359. (configure-quickcheck
  360. (size test-size-zero-to-ten))
  361. (test-assert "[prop] Reading XZ indexes is reversible"
  362. (quickcheck
  363. (property ((rbvs ($list $xz-index-record-bytevector))
  364. (crc $crc32-bytevector))
  365. (let* ((count (encode-xz-integer (length rbvs)))
  366. (count-size (bytevector-length count))
  367. (rbvs-size (reduce + 0 (map bytevector-length rbvs)))
  368. (size (+ 1 count-size rbvs-size))
  369. (remainder (modulo size 4))
  370. (padding-size (if (zero? remainder) 0 (- 4 remainder)))
  371. (padding (make-bytevector padding-size 0))
  372. (rsbv (apply bytevector-append rbvs))
  373. (bv (bytevector-append #vu8(0) count rsbv padding crc)))
  374. (equal? bv (encode-xz-index (decode-xz-index bv)))))))
  375. (test-assert "[prop] Writing XZ indexes is reversible"
  376. (quickcheck
  377. (property ((index $xz-index))
  378. (let* ((bv (encode-xz-index index))
  379. (end (- (bytevector-length bv) 4)))
  380. ;; If we happen to generate an index with the correct CRC, it
  381. ;; will become #f when we decode it.
  382. (test-when (not (and=> (xz-index-crc32 index)
  383. (lambda (crc32)
  384. (= crc32 (bytevector-xz-crc32 bv 0 end)))))
  385. (equal? index (decode-xz-index bv)))))))
  386. (test-end "kinds--xz")