diff --git a/Cargo.lock b/Cargo.lock index 20ff672be..492fef141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,7 +159,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -213,13 +213,28 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + [[package]] name = "alloc-stdlib" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ - "alloc-no-stdlib", + "alloc-no-stdlib 2.0.4", +] + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib 3.0.0", ] [[package]] @@ -283,14 +298,14 @@ dependencies = [ "clipboard-win", "image", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.59.0", "wl-clipboard-rs", "x11rb", ] @@ -580,21 +595,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec 0.8.0", -] - [[package]] name = "bit-set" version = "0.10.0" @@ -605,10 +617,13 @@ dependencies = [ ] [[package]] -name = "bit-vec" -version = "0.8.0" +name = "bit-set" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" +dependencies = [ + "bit-vec 0.10.1", +] [[package]] name = "bit-vec" @@ -616,6 +631,16 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +[[package]] +name = "bit-vec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -667,7 +692,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -684,14 +709,38 @@ dependencies = [ ] [[package]] -name = "brotli" -version = "8.0.4" +name = "borsh" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "brotli" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" +dependencies = [ + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", + "brotli-decompressor 6.0.1", ] [[package]] @@ -700,8 +749,18 @@ version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.4", +] + +[[package]] +name = "brotli-decompressor" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" +dependencies = [ + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", ] [[package]] @@ -829,7 +888,7 @@ dependencies = [ "safetensors 0.7.0", "thiserror 2.0.21", "yoke 0.8.3", - "zip", + "zip 7.2.0", ] [[package]] @@ -839,7 +898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fdfe9d06de16ce49961e49084e5b79a75a9bdf157246e7c7b6328e87a7aa25d" dependencies = [ "half", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-foundation 0.3.2", "objc2-metal 0.3.2", "once_cell", @@ -916,7 +975,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", ] [[package]] @@ -926,7 +996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -1000,7 +1070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1038,6 +1108,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1051,7 +1131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -1064,10 +1144,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1077,6 +1163,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "craft-docpath" version = "0.1.0" @@ -1091,22 +1186,47 @@ dependencies = [ "serde", ] +[[package]] +name = "craft-update" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" +dependencies = [ + "chrono", + "dunce", + "egui", + "flate2", + "getrandom 0.3.4", + "log", + "plist", + "semver", + "serde", + "serde_json", + "sha2", + "tar", + "tempfile", + "ureq", + "verifysign", + "winreg", + "winsafe", + "zip 9.1.0", +] + [[package]] name = "craft-window" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=a0916808f7d78bed8c7b90cdf937176015454a7f#a0916808f7d78bed8c7b90cdf937176015454a7f" +source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" dependencies = [ "egui", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "wayland-client", ] [[package]] name = "crc32fast" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" dependencies = [ "cfg-if", ] @@ -1195,7 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -1226,6 +1346,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" + [[package]] name = "dhat" version = "0.3.3" @@ -1282,7 +1408,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.6.2", "libc", - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -1315,6 +1441,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-stack" version = "0.10.0" @@ -1406,7 +1538,7 @@ dependencies = [ "image", "js-sys", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "parking_lot", @@ -1474,7 +1606,7 @@ dependencies = [ "egui", "image", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "objc2-ui-kit 0.3.2", @@ -1605,7 +1737,7 @@ dependencies = [ "emath", "epaint_default_fonts", "font-types", - "harfrust", + "harfrust 0.12.0", "log", "nohash-hasher", "parking_lot", @@ -1678,7 +1810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1795,6 +1927,16 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.14" @@ -2353,8 +2495,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -2436,7 +2581,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -2545,7 +2690,20 @@ checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" dependencies = [ "bitflags 2.13.2", "bytemuck", - "read-fonts", + "read-fonts 0.41.0", + "smallvec", +] + +[[package]] +name = "harfrust" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948d0741125ba89cd3e1c23e5642415b6ade7e1d29d67ba25fb925b533e989d6" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "once_cell", + "read-fonts 0.43.3", "smallvec", ] @@ -2625,6 +2783,22 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "image" version = "0.25.10" @@ -2911,7 +3085,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e35d032bcec660647828527ff42c6f5776d2fd44b8357f9f6d9ac6dc07218e46" dependencies = [ - "brotli-decompressor", + "brotli-decompressor 5.0.3", "jxl-bitstream", "jxl-frame", "jxl-grid", @@ -2943,7 +3117,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d36c662923f47586880211f3bc7c0d83fb3a9b410d278c7bde93450748abeef3" dependencies = [ - "brotli-decompressor", + "brotli-decompressor 5.0.3", "jxl-bitstream", "jxl-color", "jxl-frame", @@ -3069,9 +3243,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.189" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libfuzzer-sys" @@ -3116,6 +3290,7 @@ name = "lightcraft" version = "0.14.0" dependencies = [ "chrono", + "craft-update", "eframe", "egui", "lightcraft-codecs", @@ -3126,7 +3301,7 @@ dependencies = [ "lightcraft-ui-egui", "log", "muda", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "pollster 1.0.1", @@ -3218,7 +3393,7 @@ dependencies = [ "faer-core", "lightcraft-denoise-core", "lightcraft-raster", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "rayon", "serde", @@ -3232,8 +3407,9 @@ dependencies = [ name = "lightcraft-denoise-core" version = "0.14.0" dependencies = [ + "dhat", "lightcraft-raster", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "rayon", "serde", @@ -3249,7 +3425,7 @@ version = "0.14.0" dependencies = [ "lightcraft-color", "lightcraft-geom", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "serde", "serde_json", @@ -3262,7 +3438,7 @@ dependencies = [ "ab_glyph", "chrono", "craft-docpath", - "harfrust", + "harfrust 0.13.3", "jxl-oxide", "libc", "lightcraft-catalog", @@ -3290,7 +3466,7 @@ dependencies = [ "lightcraft-testkit", "lightcraft-tiff", "log", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "rayon", "serde", "serde_json", @@ -3329,7 +3505,7 @@ dependencies = [ name = "lightcraft-geocode" version = "0.14.0" dependencies = [ - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "serde", ] @@ -3382,7 +3558,7 @@ version = "0.14.0" dependencies = [ "lightcraft-develop", "lightcraft-geom", - "quick-xml", + "quick-xml 0.41.0", ] [[package]] @@ -3420,7 +3596,7 @@ dependencies = [ "lightcraft-geom", "lightcraft-tiff", "proptest", - "quick-xml", + "quick-xml 0.41.0", "serde", "serde_json", ] @@ -3481,7 +3657,7 @@ dependencies = [ "lightcraft-raster", "lightcraft-testkit", "lightcraft-tiff", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "rayon", "serde", @@ -3548,6 +3724,7 @@ name = "lightcraft-ui-egui" version = "0.14.0" dependencies = [ "craft-ui", + "craft-update", "craft-window", "egui", "egui_kittest", @@ -3765,21 +3942,20 @@ dependencies = [ [[package]] name = "muda" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6744ab645ebe79f9c428455c7cb9dfa4275fa3b50a25408e08f927da8dc618c" +checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03" dependencies = [ "crossbeam-channel", "dpi", "keyboard-types", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", - "once_cell", "png", "thiserror 2.0.21", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3871,6 +4047,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "no_std_io2" version = "0.9.4" @@ -3951,6 +4139,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-derive" version = "0.4.2" @@ -4061,9 +4255,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" dependencies = [ "objc2-encode", ] @@ -4092,7 +4286,7 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.2", "block2 0.6.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -4142,7 +4336,7 @@ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -4153,7 +4347,7 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-io-surface", ] @@ -4210,7 +4404,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.6.2", "libc", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", ] @@ -4221,7 +4415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", ] @@ -4258,7 +4452,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.6.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -4283,7 +4477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -4328,7 +4522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -4388,6 +4582,12 @@ dependencies = [ "libc", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "orbclient" version = "0.3.55" @@ -4664,6 +4864,19 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plist" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327a4cb2ad49dc48adfdc9da89e22c64d488b508c67a044d71ba729e4521a285" +dependencies = [ + "base64", + "indexmap", + "quick-xml 0.42.0", + "serde", + "time", +] + [[package]] name = "png" version = "0.18.1" @@ -4709,7 +4922,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4730,7 +4943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4750,6 +4963,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "powerfmt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4813,16 +5032,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", + "bit-set 0.11.1", + "bit-vec 0.10.1", "bitflags 2.13.2", + "chacha20 0.10.2", + "core_detect", "num-traits", - "rand 0.9.5", - "rand_chacha 0.9.0", + "rand 0.10.3", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -4906,6 +5126,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.47" @@ -4947,6 +5176,16 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -4985,6 +5224,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_distr" version = "0.5.1" @@ -4997,11 +5242,11 @@ dependencies = [ [[package]] name = "rand_xorshift" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" dependencies = [ - "rand_core 0.9.5", + "rand_core 0.10.1", ] [[package]] @@ -5090,7 +5335,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" dependencies = [ - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", "objc2-quartz-core 0.3.2", @@ -5133,6 +5378,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "read-fonts" +version = "0.43.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", +] + [[package]] name = "reborrow" version = "0.5.5" @@ -5199,7 +5455,7 @@ dependencies = [ "js-sys", "libc", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -5293,7 +5549,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5306,22 +5562,36 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ + "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.103.15", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.15.1" @@ -5331,6 +5601,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.15", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + [[package]] name = "rustls-rustcrypto" version = "0.0.2-alpha" @@ -5431,6 +5728,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -5457,6 +5763,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.3.0" @@ -5468,6 +5797,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "send_wrapper" @@ -5513,9 +5846,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -5551,7 +5884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5625,7 +5958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.41.0", ] [[package]] @@ -5836,6 +6169,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -5846,7 +6189,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix 1.1.5", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5918,6 +6261,36 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tokio" version = "1.53.1" @@ -6042,6 +6415,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" +[[package]] +name = "typed-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" + [[package]] name = "typenum" version = "1.20.1" @@ -6056,7 +6435,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6152,6 +6531,41 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "ureq-proto", + "utf8-zero", +] + +[[package]] +name = "ureq-proto" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841583fc36089d7d59419568ae157f43a5a03a2e5a81ba87c77d1596ef46e922" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "uuid" version = "1.26.1" @@ -6201,6 +6615,16 @@ dependencies = [ "vello_common", ] +[[package]] +name = "verifysign" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6acfcc42a8d44f340d001193d97ef56bf33210b51ad760ae8308c97d5241ca51" +dependencies = [ + "core-foundation 0.10.1", + "windows-sys 0.61.2", +] + [[package]] name = "version_check" version = "0.9.5" @@ -6416,7 +6840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.41.0", "quote", ] @@ -6453,10 +6877,19 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "1.0.9" +name = "webpki-root-certs" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" dependencies = [ "rustls-pki-types", ] @@ -6595,7 +7028,7 @@ dependencies = [ "naga", "naga-types", "ndk-sys", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -6656,7 +7089,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6766,16 +7199,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -6793,31 +7226,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -6835,96 +7251,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winit" version = "0.30.13" @@ -6939,7 +7307,7 @@ dependencies = [ "calloop 0.13.0", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -6985,15 +7353,31 @@ dependencies = [ ] [[package]] -name = "winresource" -version = "0.1.31" +name = "winreg" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "winresource" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b" dependencies = [ "toml", "version_check", ] +[[package]] +name = "winsafe" +version = "0.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef0ffc427f045c0cc9ebffd6f4f91153dcd2a1547b5c3c29ee3f541e16e95c6" + [[package]] name = "wit-bindgen" version = "0.57.1" @@ -7331,7 +7715,24 @@ dependencies = [ "crc32fast", "indexmap", "memchr", - "typed-path", + "typed-path 0.12.3", +] + +[[package]] +name = "zip" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886b00003a72c4ccee3f42cfc34908d33b2508a74d827dc2a4244706475c704" +dependencies = [ + "crc32fast", + "flate2", + "getrandom 0.4.3", + "indexmap", + "memchr", + "nix", + "typed-path 0.13.0", + "windows-sys 0.61.2", + "zopfli", ] [[package]] @@ -7346,6 +7747,18 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +[[package]] +name = "zopfli" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zune-core" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index f61b3b868..b68c6e6ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ repository = "https://github.com/storytold/lightcraft" [workspace.dependencies] craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "25da1314d443171b4adeaad2cd9454b273f35c6f", features = ["serde"] } craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "82a24b006eefa036b43c09a67fb9a3fbd17ffe63" } -craft-window = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f", features = ["egui"] } +craft-window = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", features = ["egui"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", features = ["egui"] } # internal lightcraft-denoise-core = { path = "crates/denoise-core" } lightcraft-denoise = { path = "crates/denoise", default-features = false } @@ -62,7 +63,7 @@ dhat = "0.3" # opt-in heap profiling (lightcraft-cli --features dhat-heap) and h # formats quick-xml = "0.41" -miniz_oxide = "0.8" +miniz_oxide = "0.9" # codecs image = { version = "0.25", default-features = false, features = ["png", "jpeg", "tiff", "webp"] } diff --git a/README.md b/README.md index 78b3b3234..71f30d4c2 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ LightCraft is young and moving fast: **alpha**. **Where we honestly stand** (det casual use.** It's great for JPEG/DNG and most Sony / Panasonic / Fujifilm / Nikon / older-Canon raws on one machine; see [docs/raw-parity.md](docs/raw-parity.md) for your camera. - **The biggest gaps:** - - **camera colour calibration:** Sony, Nikon, Panasonic, Fujifilm and Canon CR3 raws have guarded estimates from their camera JPEGs, with built-in ILCA-99M2, ILCE-7CR, ILCE-7M4, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300 profiles; measured calibration is missing, and other raws or rejected fits retain a neutral matrix; + - **camera colour calibration:** Sony, Nikon, Panasonic, Fujifilm and Canon CR3 raws have guarded estimates from their camera JPEGs, with built-in ILCA-99M2, ILCE-7CR, ILCE-7M4, ILCE-7RM5, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300 profiles; measured calibration is missing, and other raws or rejected fits retain a neutral matrix; - **compressed Olympus raws and unsupported CR3 variants:** these use embedded JPEG previews when present. Fujifilm lossless/lossy compressed RAF now decodes sensor data; [verification and existing-library reload instructions](docs/raf-compression.md); - **AI quality and model setup:** Subject and Sky selection are classical heuristics; Object/Describe use an optional SAM 3 model. AI Denoise supports Bayer RAW with separately installed weights, while X-Trans and diff --git a/README.zh-hans.md b/README.zh-hans.md index 2800db410..2c6ca8191 100644 --- a/README.zh-hans.md +++ b/README.zh-hans.md @@ -305,7 +305,7 @@ LightCraft 很年轻,而且进展很快。**我们如实所处的位置**( - **作为日常使用的工具,我们更接近 60–70%。** 在单台机器上处理 JPEG/DNG 以及大多数尼康/索尼/ 较老佳能的 raw 都很出色。 - **最大的缺口:** - - **相机色彩校准:** 索尼、尼康、松下、富士和佳能 CR3 的 raw 会从各自的相机 JPEG 得到受保护的估计值,内置 ILCA-99M2、ILCE-7CR、ILCE-7M4、X-H2S、X-T4、尼康 Z 6II、Z 8 和 D5300 配置文件;实测校准仍然缺失,其他 raw 或被拒绝的拟合会保留中性矩阵; + - **相机色彩校准:** 索尼、尼康、松下、富士和佳能 CR3 的 raw 会从各自的相机 JPEG 得到受保护的估计值,内置 ILCA-99M2、ILCE-7CR、ILCE-7M4、ILCE-7RM5、X-H2S、X-T4、尼康 Z 6II、Z 8 和 D5300 配置文件;实测校准仍然缺失,其他 raw 或被拒绝的拟合会保留中性矩阵; - **压缩的奥林巴斯 raw 和不受支持的 CR3 变体:** 这些在有内嵌 JPEG 预览时使用预览。富士无损/有损压缩 RAF 现在能解码传感器数据;[验证与已有图库重新加载说明](docs/raf-compression.md); - **AI 蒙版与降噪:** 主体和天空选择目前是经典启发式算法; - **屏幕上的 HDR 显示、视频,以及经典版式中的打印/画册/地图模块。** diff --git a/README.zh-hant.md b/README.zh-hant.md index 12fcfb11b..ca624ce68 100644 --- a/README.zh-hant.md +++ b/README.zh-hant.md @@ -320,7 +320,7 @@ LightCraft 還很年輕,而且進展飛快:**Alpha 階段**。**老實說, 在單機上處理 JPEG/DNG,以及大多數 Sony/Panasonic/ Fujifilm/Nikon/較舊款 Canon 的 raw 都很出色;各機型詳情見 [docs/raw-parity.md](docs/raw-parity.md)。 - **最大的缺口:** - - **相機色彩校正:** Sony、Nikon、Panasonic、Fujifilm 和 Canon CR3 的 raw 會從各自的相機 JPEG 得到受保護的估計值,內建 ILCA-99M2、ILCE-7CR、ILCE-7M4、X-H2S、X-T4、Nikon Z 6II、Z 8 和 D5300 描述檔;仍缺少實測校正,其他 raw 或被拒絕的擬合會保留中性矩陣; + - **相機色彩校正:** Sony、Nikon、Panasonic、Fujifilm 和 Canon CR3 的 raw 會從各自的相機 JPEG 得到受保護的估計值,內建 ILCA-99M2、ILCE-7CR、ILCE-7M4、ILCE-7RM5、X-H2S、X-T4、Nikon Z 6II、Z 8 和 D5300 描述檔;仍缺少實測校正,其他 raw 或被拒絕的擬合會保留中性矩陣; - **壓縮的 Olympus raw 與不受支援的 CR3 變體:** 這些在有內嵌 JPEG 預覽時會改用預覽畫面。Fujifilm 無損/有損壓縮 RAF 現在能解碼感光元件資料;[驗證與現有照片庫重新載入說明](docs/raf-compression.md); - **AI 遮色片與降噪:** 主體與天空選取目前是傳統啟發式演算法; - **網路照片庫:** 預覽只讀取 raw 的標頭 — 或者,對於 CR3 或預覽標籤位於其後的 TIFF raw,只讀取稀疏規劃器指定的範圍 — 以及其內嵌的 JPEG(56.5 MB → 5.9 MB,10 秒 → 1 秒,測得於測試用的網路共用資料夾),但*顯影後*的畫面仍需要透過網路傳輸感光元件資料;詳見 [docs/raw-preview-reads.md](docs/raw-preview-reads.md); diff --git a/apps/lightcraft/Cargo.toml b/apps/lightcraft/Cargo.toml index f93bd2ccd..d00bc41e9 100644 --- a/apps/lightcraft/Cargo.toml +++ b/apps/lightcraft/Cargo.toml @@ -55,3 +55,6 @@ objc2-foundation = { workspace = true } [dev-dependencies] # tests record why they skip (`cargo xtask ci` lists the skips) lightcraft-testkit = { workspace = true } + +[target.'cfg(windows)'.dependencies] +craft-update = { workspace = true } diff --git a/apps/lightcraft/src/main.rs b/apps/lightcraft/src/main.rs index b666a0cdc..815fbd70f 100644 --- a/apps/lightcraft/src/main.rs +++ b/apps/lightcraft/src/main.rs @@ -881,6 +881,11 @@ fn panic_log_path_with(env: impl Fn(&str) -> Option, temp: s } fn main() -> eframe::Result { + // Windows: the update helper is a copy of this program started with `--finish-update`. + #[cfg(windows)] + if let Some(result) = craft_update::self_update::finish(&craft_update::suite::LIGHTCRAFT, &std::env::args_os().skip(1).collect::>()) { + std::process::exit(i32::from(result.is_err())); + } // First, so every start-up record is kept for the log file (`logging`). let logger = logging::install(); lightcraft_engine::guard::install_hook(panic_log_path_with(|name| std::env::var_os(name), std::env::temp_dir())); @@ -1009,6 +1014,7 @@ fn main() -> eframe::Result { // HDR display: SDR unless the surface, platform and display all allow HDR hdr_display::report(cc.wgpu_render_state.as_ref().map(|r| r.target_format)); let mut app = LightcraftApp::new(session, services(cc, app_log_file.as_deref())); + app.set_startup_update_check(true); if let Some(ui) = prefs { app.ui = ui; } diff --git a/assets/ATTRIBUTION.md b/assets/ATTRIBUTION.md index 4bc2e927e..e5ed8090e 100644 --- a/assets/ATTRIBUTION.md +++ b/assets/ATTRIBUTION.md @@ -22,6 +22,7 @@ covers a whole directory), or if a licence file referenced in the Licence column | `assets/camera-profiles/NIKON_Z_6_2.json` | Nikon Z 6II camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 296 usable Z 6II NEFs of my own (of 299, 2024 to 2026, Standard and Auto picture controls; NIKKOR Z 20mm f/1.8 S, Z 24-70mm f/4 S and AF-S 70-200mm f/2.8G on the FTZ) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/NIKON_Z_8.json` | Nikon Z 8 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 261 usable Z 8 NEFs of my own (of 278, 2023 and 2026, nearly all Standard picture control, lossless 14-bit; NIKKOR Z 24-120mm f/4 S and AF-S 70-200mm f/2.8G on the FTZ) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/NIKON_D5300.json` | Nikon D5300 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 264 usable D5300 NEFs of my own (of 273, 2014 to 2017, mostly Standard picture control, lossy 12-bit, mostly the AF-S DX 35mm f/1.8G) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | +| `assets/camera-profiles/ILCE-7RM5.json` | Sony ILCE-7RM5 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Yule (Brian) Wang (@peaperfish) | original work: fitted by `lightcraft-cli calibrate` to 220 usable ILCE-7RM5 ARWs of my own (of 241, 2025 to 2026, Standard creative style; Sigma 24-70mm F2.8 DG DN II Art and Sony FE 200-600mm F5.6-6.3 G OSS) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-26 | none | | `crates/raw/src/spectral_table.rs` | Camera colour matrices (DNG ColorMatrix1/2 and ForwardMatrix1/2 for Standard Illuminant A and D65) for 52 camera models, and the model alias list | Derived by LightCraft contributors from rawtoaces-data (Academy Software Foundation): camera spectral sensitivities measured by Weta Digital (44 cameras), scitech (7) and Jonathon Irons at the National Physical Laboratory (Nikon D5100); training reflectances; CIE 1931 colour-matching functions (CIE, CC-BY-4.0, as shipped in rawtoaces-data) | https://github.com/AcademySoftwareFoundation/rawtoaces-data, tag v1.1.0, commit 5cfa53707b053594b2d0d48e8019fb56e6582d6c | Apache-2.0 (`LICENSE-APACHE`); the colour-matching functions CC-BY-4.0 | 2026-10-08 | generated by `tools/spectral_camera_table.py`: a 3×3 matrix per illuminant fitted to the spectral data (no measurement copied verbatim); aliases copied from `data/aliases.json`; no Adobe or third-party camera matrix | | `assets/camera-tone/sony-dro-off.json` | Sony global tone curve with the Dynamic Range Optimizer off (32 knots), built into LightCraft for ARWs shot with DRO on | LightCraft contributors; source photographs by raw.pixls.us contributors | original work: tone curve quantile-matched over the pooled pixel pairs of 12 CC0 raw.pixls.us ARWs (DRO off, Standard creative style; ILCE-3500, -5000, -6600, -7CR, -7M3 ×3, -7S, -9M2, ILCA-77M2, SLT-A57, NEX-F3) and their embedded camera JPEGs; the file lists each source URL and SHA-256 and how the curve was derived (see also `docs/camera-preview-colour.md`); aggregate tone statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 (sources CC0) | 2026-10-09 | none | | `crates/scenes/` (generated images) | Procedural demo photographs | LightCraft contributors | original work (generated at runtime, no source imagery) | MIT OR Apache-2.0 | 2026-09-30 | n/a | diff --git a/assets/camera-profiles/ILCE-7RM5.json b/assets/camera-profiles/ILCE-7RM5.json new file mode 100644 index 000000000..787bfb062 --- /dev/null +++ b/assets/camera-profiles/ILCE-7RM5.json @@ -0,0 +1,9031 @@ +{ + "version": 1, + "model": "ILCE-7RM5", + "files": 220, + "samples": 790553, + "matrix": [ + [ + 1.249584732786861, + -0.3300570108987344, + 0.07738524309065298 + ], + [ + -0.09371116474358132, + 1.2038743929103433, + -0.1120131503658719 + ], + [ + -0.03422832387281005, + -0.8688172281950557, + 1.937872050399747 + ] + ], + "hue_sat": { + "hue_divisions": 72, + "sat_divisions": 5, + "val_divisions": 5, + "data": [ + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.6575432, + 1.0169075, + 1.0 + ], + [ + 0.045065638, + 1.0544671, + 1.0 + ], + [ + 0.2621381, + 1.0540911, + 1.0 + ], + [ + 0.5927048, + 1.0288227, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.568119, + 1.0037564, + 1.0 + ], + [ + -0.4427149, + 1.0345306, + 1.0 + ], + [ + -0.41775778, + 1.0446787, + 1.0 + ], + [ + 0.086332865, + 1.0200883, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.272573, + 1.0058666, + 1.0 + ], + [ + -0.5253304, + 0.9976382, + 1.0 + ], + [ + -1.19747, + 1.0018761, + 1.0 + ], + [ + -0.79130363, + 0.98982924, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.8305438, + 1.013145, + 1.0 + ], + [ + -0.64092255, + 1.0028768, + 1.0 + ], + [ + -1.6476865, + 0.98830545, + 1.0 + ], + [ + -1.8518519, + 0.9801241, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.9341725, + 1.02301, + 1.0 + ], + [ + -1.0185463, + 1.0157639, + 1.0 + ], + [ + -2.5776067, + 0.99842805, + 1.0 + ], + [ + -3.2994924, + 0.9785849, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.5423042, + 1.0483397, + 1.0 + ], + [ + -1.4740537, + 1.032285, + 1.0 + ], + [ + -3.7110608, + 1.0046949, + 1.0 + ], + [ + -5.396528, + 0.967783, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.6425831, + 1.0778131, + 1.0 + ], + [ + -2.4365096, + 1.0424513, + 1.0 + ], + [ + -4.570945, + 1.0063766, + 1.0 + ], + [ + -6.660486, + 0.95838326, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.14437525, + 1.0805831, + 1.0 + ], + [ + -3.1429949, + 1.0347922, + 1.0 + ], + [ + -4.7743, + 1.0044911, + 1.0 + ], + [ + -6.743695, + 0.9554359, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.7822553, + 1.099207, + 1.0 + ], + [ + -2.1562428, + 1.0492018, + 1.0 + ], + [ + -4.6814117, + 0.9976052, + 1.0 + ], + [ + -7.334759, + 0.9328078, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4186356, + 1.1193541, + 1.0 + ], + [ + -1.2648852, + 1.0724756, + 1.0 + ], + [ + -4.010651, + 1.001225, + 1.0 + ], + [ + -6.9021163, + 0.93410987, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0874636, + 1.1538868, + 1.0 + ], + [ + 0.23989654, + 1.0897437, + 1.0 + ], + [ + -2.9069233, + 1.0008082, + 1.0 + ], + [ + -6.942227, + 0.9103677, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.2319875, + 1.1964883, + 1.0 + ], + [ + 1.6361779, + 1.1026742, + 1.0 + ], + [ + -1.7622405, + 0.99407303, + 1.0 + ], + [ + -7.6075315, + 0.87470096, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.200221, + 1.2336057, + 1.0 + ], + [ + 2.4819555, + 1.1164554, + 1.0 + ], + [ + -0.67436194, + 0.9800342, + 1.0 + ], + [ + -6.7233605, + 0.8354304, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.7635767, + 1.258256, + 1.0 + ], + [ + 2.755172, + 1.1187295, + 1.0 + ], + [ + 0.58612347, + 0.95909715, + 1.0 + ], + [ + -3.4647129, + 0.8201187, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7450001, + 1.2654432, + 1.0 + ], + [ + 2.2866368, + 1.1127598, + 1.0 + ], + [ + 1.7782547, + 0.96788186, + 1.0 + ], + [ + 0.64800644, + 0.8437623, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4756403, + 1.2495352, + 1.0 + ], + [ + 1.8023143, + 1.1058493, + 1.0 + ], + [ + 2.4051156, + 0.97594136, + 1.0 + ], + [ + 3.1766913, + 0.8713939, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.15415932, + 1.2347906, + 1.0 + ], + [ + 1.3704019, + 1.0905327, + 1.0 + ], + [ + 2.8927534, + 0.95705473, + 1.0 + ], + [ + 4.9556255, + 0.85762197, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.45234466, + 1.2126856, + 1.0 + ], + [ + 1.3297142, + 1.0610285, + 1.0 + ], + [ + 3.5847495, + 0.92722595, + 1.0 + ], + [ + 6.0845156, + 0.84810686, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.0763642, + 1.1915482, + 1.0 + ], + [ + 1.087915, + 1.0379584, + 1.0 + ], + [ + 3.107092, + 0.9336573, + 1.0 + ], + [ + 4.6096416, + 0.87201273, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.8927168, + 1.1797738, + 1.0 + ], + [ + 0.3604307, + 1.0387832, + 1.0 + ], + [ + 1.8885725, + 0.9472845, + 1.0 + ], + [ + 2.5527375, + 0.8916758, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.8154569, + 1.1746649, + 1.0 + ], + [ + -0.75731444, + 1.0539931, + 1.0 + ], + [ + 0.15679865, + 0.99418485, + 1.0 + ], + [ + 0.26365867, + 0.97036415, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.6557531, + 1.1808895, + 1.0 + ], + [ + -2.146421, + 1.0872095, + 1.0 + ], + [ + -1.216422, + 1.0318732, + 1.0 + ], + [ + -0.14712709, + 1.0002339, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.503687, + 1.1711676, + 1.0 + ], + [ + -2.6333165, + 1.0882001, + 1.0 + ], + [ + -0.9562701, + 1.0260637, + 1.0 + ], + [ + -0.035742544, + 1.0006093, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -5.3313165, + 1.1779693, + 1.0 + ], + [ + -3.511713, + 1.0934967, + 1.0 + ], + [ + -0.77989405, + 1.0125425, + 1.0 + ], + [ + -0.15130846, + 0.9985428, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.7568846, + 1.1773652, + 1.0 + ], + [ + -4.3852744, + 1.0850232, + 1.0 + ], + [ + -2.9159627, + 0.9900688, + 1.0 + ], + [ + -2.1400974, + 0.9775968, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.7892175, + 1.1440657, + 1.0 + ], + [ + -4.395312, + 1.0594687, + 1.0 + ], + [ + -3.1968632, + 0.9905179, + 1.0 + ], + [ + -1.8385077, + 0.98149014, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.615582, + 1.1392046, + 1.0 + ], + [ + -4.00538, + 1.0335472, + 1.0 + ], + [ + -0.9200221, + 0.99787354, + 1.0 + ], + [ + -0.11120706, + 0.9992187, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.523595, + 1.1210346, + 1.0 + ], + [ + -4.8383403, + 1.0227709, + 1.0 + ], + [ + -1.0064363, + 0.9959147, + 1.0 + ], + [ + -0.049839716, + 0.99975395, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.567221, + 1.109696, + 1.0 + ], + [ + -3.7778788, + 1.0227439, + 1.0 + ], + [ + -0.3094657, + 0.99754596, + 1.0 + ], + [ + -0.01160631, + 0.9998476, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.9242926, + 1.1014805, + 1.0 + ], + [ + -2.6735833, + 1.0197709, + 1.0 + ], + [ + -0.10607915, + 0.9988134, + 1.0 + ], + [ + -0.0016143449, + 0.99994075, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.489351, + 1.0988349, + 1.0 + ], + [ + -2.0020316, + 1.0113986, + 1.0 + ], + [ + -0.060160246, + 0.9993989, + 1.0 + ], + [ + -0.00045636468, + 0.99998945, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.7909346, + 1.0950327, + 1.0 + ], + [ + -3.1690855, + 1.0192778, + 1.0 + ], + [ + -0.20098883, + 1.0008932, + 1.0 + ], + [ + -0.0010797405, + 1.0000042, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.812833, + 1.0921811, + 1.0 + ], + [ + -3.6212065, + 1.016884, + 1.0 + ], + [ + -0.2872315, + 1.000208, + 1.0 + ], + [ + -0.0022921215, + 0.9999986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.7691603, + 1.0748369, + 1.0 + ], + [ + -2.2901673, + 1.0061964, + 1.0 + ], + [ + -0.2540361, + 0.9997461, + 1.0 + ], + [ + -0.002999882, + 0.9999967, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.285355, + 1.0752903, + 1.0 + ], + [ + -2.8977985, + 1.008037, + 1.0 + ], + [ + -0.389354, + 1.0001613, + 1.0 + ], + [ + -0.0048222253, + 1.0000017, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.331701, + 1.0774896, + 1.0 + ], + [ + -3.3062727, + 1.0165391, + 1.0 + ], + [ + -0.24283074, + 1.0005523, + 1.0 + ], + [ + -0.0019910862, + 1.0000027, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.720209, + 1.0690521, + 1.0 + ], + [ + -4.8786044, + 1.0175062, + 1.0 + ], + [ + -0.48560837, + 1.0014203, + 1.0 + ], + [ + -0.0033854088, + 1.0000086, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.556501, + 1.0456746, + 1.0 + ], + [ + -4.985604, + 1.0003452, + 1.0 + ], + [ + -0.41515103, + 0.9995448, + 1.0 + ], + [ + -0.003054782, + 0.9999918, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.729045, + 1.0217354, + 1.0 + ], + [ + -3.521123, + 0.98780435, + 1.0 + ], + [ + -0.30965137, + 0.99440813, + 1.0 + ], + [ + -0.008598875, + 0.9997742, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.221271, + 0.98520076, + 1.0 + ], + [ + -4.486184, + 0.97454756, + 1.0 + ], + [ + -0.41762093, + 0.9899926, + 1.0 + ], + [ + -0.03436083, + 0.9983804, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.70429, + 0.9197576, + 1.0 + ], + [ + -6.0378523, + 0.93986017, + 1.0 + ], + [ + -0.41832632, + 0.99216044, + 1.0 + ], + [ + -0.01641741, + 0.99925196, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.093513, + 0.8699881, + 1.0 + ], + [ + -6.8281274, + 0.9147011, + 1.0 + ], + [ + -0.30128792, + 0.99418074, + 1.0 + ], + [ + -0.0026745356, + 0.9999154, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.529678, + 0.84677386, + 1.0 + ], + [ + -7.347991, + 0.897593, + 1.0 + ], + [ + -0.39531615, + 0.9915446, + 1.0 + ], + [ + -0.000654911, + 0.9999173, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -10.297008, + 0.8246169, + 1.0 + ], + [ + -6.991395, + 0.8822463, + 1.0 + ], + [ + -0.8904233, + 0.98326087, + 1.0 + ], + [ + -0.005210178, + 0.99982196, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.1014814, + 0.870403, + 1.0 + ], + [ + -4.2737536, + 0.9510795, + 1.0 + ], + [ + -1.0143899, + 0.9928547, + 1.0 + ], + [ + 0.0069134138, + 0.9990259, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.2389834, + 1.0089513, + 1.0 + ], + [ + 0.59233487, + 1.0836766, + 1.0 + ], + [ + 2.109882, + 1.0953512, + 1.0 + ], + [ + 0.57811993, + 1.0165061, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.42605868, + 1.0736833, + 1.0 + ], + [ + 2.6112878, + 1.1127571, + 1.0 + ], + [ + 3.734751, + 1.1181016, + 1.0 + ], + [ + 2.895584, + 1.0769328, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.7645093, + 1.0585829, + 1.0 + ], + [ + 2.7074714, + 1.1086032, + 1.0 + ], + [ + 4.612825, + 1.121013, + 1.0 + ], + [ + 4.2159166, + 1.090578, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.317804, + 1.0096856, + 1.0 + ], + [ + 2.410078, + 1.0813844, + 1.0 + ], + [ + 4.022777, + 1.0866787, + 1.0 + ], + [ + 1.5960958, + 1.0285214, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7581048, + 0.94621634, + 1.0 + ], + [ + 3.344702, + 1.031301, + 1.0 + ], + [ + 1.6271458, + 1.0360217, + 1.0 + ], + [ + 0.045802835, + 1.0040928, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.2102604, + 0.8877613, + 1.0 + ], + [ + 2.5063229, + 0.9831511, + 1.0 + ], + [ + 1.0589613, + 1.0087296, + 1.0 + ], + [ + 0.044849876, + 1.0006486, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8181234, + 0.84908885, + 1.0 + ], + [ + 1.2979459, + 0.9558577, + 1.0 + ], + [ + 0.95269287, + 0.9990587, + 1.0 + ], + [ + 0.13956869, + 0.99899775, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.4218108, + 0.8245256, + 1.0 + ], + [ + 0.980773, + 0.95178086, + 1.0 + ], + [ + 0.44153172, + 1.004565, + 1.0 + ], + [ + 0.083737634, + 0.9998738, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.3112286, + 0.81245995, + 1.0 + ], + [ + 1.2433338, + 0.94633913, + 1.0 + ], + [ + -0.45147318, + 1.0128739, + 1.0 + ], + [ + -0.040671885, + 1.0012027, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.0959775, + 0.82676107, + 1.0 + ], + [ + 0.19708328, + 0.9494545, + 1.0 + ], + [ + -0.71126884, + 1.0130774, + 1.0 + ], + [ + -0.066266306, + 1.0014489, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0682087, + 0.85580033, + 1.0 + ], + [ + 0.33310115, + 0.9754608, + 1.0 + ], + [ + -0.038120847, + 1.0069447, + 1.0 + ], + [ + -0.0039369226, + 1.0006703, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.4665134, + 0.8869471, + 1.0 + ], + [ + 1.394241, + 0.9861353, + 1.0 + ], + [ + 0.39206934, + 1.0029631, + 1.0 + ], + [ + 0.012310251, + 1.0002207, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.840249, + 0.885747, + 1.0 + ], + [ + 2.030714, + 0.97694564, + 1.0 + ], + [ + 0.48313063, + 0.9986679, + 1.0 + ], + [ + 0.015092909, + 1.0000398, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.364709, + 0.93935746, + 1.0 + ], + [ + 3.27353, + 1.0063933, + 1.0 + ], + [ + 0.47039714, + 1.0006171, + 1.0 + ], + [ + 0.0095254835, + 0.9999786, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.708454, + 0.9936273, + 1.0 + ], + [ + 3.0144715, + 1.0396764, + 1.0 + ], + [ + 0.76019174, + 1.0015196, + 1.0 + ], + [ + 0.031606715, + 0.9997963, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.081264, + 1.007644, + 1.0 + ], + [ + 0.9569218, + 1.0373055, + 1.0 + ], + [ + -0.29632938, + 1.0081192, + 1.0 + ], + [ + -0.020595783, + 1.0002273, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.4333196, + 0.9922466, + 1.0 + ], + [ + 0.8624292, + 1.0051231, + 1.0 + ], + [ + -0.124487296, + 1.0007266, + 1.0 + ], + [ + 0.0014794656, + 1.0001725, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.0025764, + 0.9966404, + 1.0 + ], + [ + 0.32528183, + 1.0222802, + 1.0 + ], + [ + -1.4467791, + 1.0029583, + 1.0 + ], + [ + -0.09876483, + 1.0001086, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.9974155, + 0.9894437, + 1.0 + ], + [ + 2.0329673, + 1.0394019, + 1.0 + ], + [ + -0.49002603, + 1.0067779, + 1.0 + ], + [ + -0.091952406, + 0.9999813, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.90883, + 0.9736301, + 1.0 + ], + [ + 2.7153323, + 1.0357952, + 1.0 + ], + [ + 0.37018985, + 1.0083603, + 1.0 + ], + [ + -0.0096038, + 1.0001452, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.1170373, + 0.98292226, + 1.0 + ], + [ + 1.2927599, + 1.035257, + 1.0 + ], + [ + 0.3150436, + 1.0090348, + 1.0 + ], + [ + 0.0004185312, + 1.0000962, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.3353724, + 0.9857392, + 1.0 + ], + [ + 0.9401049, + 1.0468453, + 1.0 + ], + [ + -0.13837458, + 1.0182233, + 1.0 + ], + [ + -0.024829892, + 1.0004286, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.5100236, + 1.0068982, + 1.0 + ], + [ + 0.93617123, + 1.0617714, + 1.0 + ], + [ + 0.047028802, + 1.0331767, + 1.0 + ], + [ + 0.0046950956, + 1.0010607, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.6971107, + 1.0113055, + 1.0 + ], + [ + -0.20543085, + 1.0561123, + 1.0 + ], + [ + -0.47802702, + 1.0383303, + 1.0 + ], + [ + 0.07525184, + 1.0021627, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.8135715, + 1.0148535, + 1.0 + ], + [ + 0.23203266, + 1.0550131, + 1.0 + ], + [ + -0.023389658, + 1.04285, + 1.0 + ], + [ + 0.19465668, + 1.002981, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.6685414, + 1.0124575, + 1.0 + ], + [ + 0.59984475, + 1.0448725, + 1.0 + ], + [ + 0.69587445, + 1.0372711, + 1.0 + ], + [ + 0.60554105, + 1.005815, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.5002728, + 1.0161368, + 1.0 + ], + [ + 0.56248856, + 1.058378, + 1.0 + ], + [ + 0.9347412, + 1.0511781, + 1.0 + ], + [ + 0.8927735, + 1.0201077, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.8510935, + 1.0004079, + 1.0 + ], + [ + -0.13690601, + 1.0438355, + 1.0 + ], + [ + 0.5490022, + 1.0381058, + 1.0 + ], + [ + 1.4847147, + 1.0113307, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2298164, + 0.98695815, + 1.0 + ], + [ + -0.5338396, + 1.008648, + 1.0 + ], + [ + -0.1529312, + 1.0192995, + 1.0 + ], + [ + 1.3007274, + 0.98695505, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.9663947, + 0.9861699, + 1.0 + ], + [ + 0.3281501, + 0.9828017, + 1.0 + ], + [ + -0.9316485, + 0.9753089, + 1.0 + ], + [ + -0.45177975, + 0.9492595, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.8109272, + 0.98814505, + 1.0 + ], + [ + -0.06094338, + 0.99206847, + 1.0 + ], + [ + -1.5175383, + 0.982531, + 1.0 + ], + [ + -1.9885272, + 0.954517, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.5236363, + 0.9898073, + 1.0 + ], + [ + -0.5354513, + 1.0057786, + 1.0 + ], + [ + -2.319607, + 1.0002798, + 1.0 + ], + [ + -3.4267511, + 0.97033256, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.6655283, + 1.0100801, + 1.0 + ], + [ + -1.0105478, + 1.0113044, + 1.0 + ], + [ + -3.3712995, + 0.9907962, + 1.0 + ], + [ + -5.3614125, + 0.957974, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.55430204, + 1.0288806, + 1.0 + ], + [ + -2.2823086, + 1.0114845, + 1.0 + ], + [ + -4.29412, + 0.9850471, + 1.0 + ], + [ + -6.70813, + 0.94654834, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.28653958, + 1.029249, + 1.0 + ], + [ + -2.9026222, + 1.0021783, + 1.0 + ], + [ + -4.3481326, + 0.98601437, + 1.0 + ], + [ + -6.742446, + 0.94504565, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.840411, + 1.0304451, + 1.0 + ], + [ + -1.7543678, + 1.0166836, + 1.0 + ], + [ + -4.263933, + 0.98349094, + 1.0 + ], + [ + -7.782672, + 0.9144162, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.9039959, + 1.0615926, + 1.0 + ], + [ + -0.6004897, + 1.0442605, + 1.0 + ], + [ + -3.2590501, + 0.9852762, + 1.0 + ], + [ + -6.0829635, + 0.92023736, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.113609, + 1.1155086, + 1.0 + ], + [ + 0.7005239, + 1.0668454, + 1.0 + ], + [ + -1.9628628, + 0.9854875, + 1.0 + ], + [ + -5.3814754, + 0.90371615, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.0877285, + 1.1595732, + 1.0 + ], + [ + 1.9560493, + 1.0802444, + 1.0 + ], + [ + -0.77529097, + 0.9800677, + 1.0 + ], + [ + -5.565922, + 0.8722543, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.288913, + 1.2051771, + 1.0 + ], + [ + 2.74089, + 1.1004237, + 1.0 + ], + [ + 0.012938409, + 0.9697918, + 1.0 + ], + [ + -5.393885, + 0.8341304, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.8245485, + 1.2282706, + 1.0 + ], + [ + 2.9895375, + 1.1039253, + 1.0 + ], + [ + 1.2145354, + 0.95314676, + 1.0 + ], + [ + -2.3480604, + 0.8217049, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0588024, + 1.2314723, + 1.0 + ], + [ + 2.7796125, + 1.0972726, + 1.0 + ], + [ + 2.4223437, + 0.9613367, + 1.0 + ], + [ + 1.6759906, + 0.8447568, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.8357061, + 1.210052, + 1.0 + ], + [ + 2.2408845, + 1.0848253, + 1.0 + ], + [ + 2.808005, + 0.9623734, + 1.0 + ], + [ + 3.4482584, + 0.8608864, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.69474894, + 1.1947756, + 1.0 + ], + [ + 1.6912163, + 1.0640128, + 1.0 + ], + [ + 3.070553, + 0.93303436, + 1.0 + ], + [ + 5.026965, + 0.83831453, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.02924209, + 1.1594467, + 1.0 + ], + [ + 1.437672, + 1.0297697, + 1.0 + ], + [ + 3.5909321, + 0.90887827, + 1.0 + ], + [ + 5.7714977, + 0.8360717, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.73282367, + 1.1322978, + 1.0 + ], + [ + 1.0370998, + 1.0152173, + 1.0 + ], + [ + 2.9553826, + 0.93621993, + 1.0 + ], + [ + 4.670117, + 0.86914015, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.619349, + 1.1252851, + 1.0 + ], + [ + 0.15239729, + 1.0244952, + 1.0 + ], + [ + 1.6071638, + 0.960652, + 1.0 + ], + [ + 2.7668586, + 0.8957443, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.4471934, + 1.1201713, + 1.0 + ], + [ + -0.9853024, + 1.0402389, + 1.0 + ], + [ + -0.10679894, + 1.0003766, + 1.0 + ], + [ + 0.38707855, + 0.9576168, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.6268017, + 1.1333272, + 1.0 + ], + [ + -2.5076745, + 1.071064, + 1.0 + ], + [ + -1.8380454, + 1.0375947, + 1.0 + ], + [ + -0.49257776, + 1.0004728, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.8864617, + 1.1463132, + 1.0 + ], + [ + -3.6842506, + 1.0934086, + 1.0 + ], + [ + -2.3747447, + 1.0497583, + 1.0 + ], + [ + -0.121673696, + 1.0016263, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.1770673, + 1.1691642, + 1.0 + ], + [ + -4.8939047, + 1.1140888, + 1.0 + ], + [ + -2.1122801, + 1.0347022, + 1.0 + ], + [ + -0.2781079, + 0.9971531, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.824204, + 1.1899806, + 1.0 + ], + [ + -6.7190065, + 1.1285441, + 1.0 + ], + [ + -5.1226296, + 0.9854947, + 1.0 + ], + [ + -4.016408, + 0.9494332, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.926484, + 1.1482611, + 1.0 + ], + [ + -7.6249037, + 1.0958742, + 1.0 + ], + [ + -5.6792645, + 0.9760871, + 1.0 + ], + [ + -4.0723734, + 0.94808024, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.670957, + 1.1153084, + 1.0 + ], + [ + -8.038458, + 1.0422579, + 1.0 + ], + [ + -5.8509502, + 0.9850574, + 1.0 + ], + [ + -0.8264564, + 0.9961184, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -10.342434, + 1.0856272, + 1.0 + ], + [ + -9.040556, + 1.0213751, + 1.0 + ], + [ + -7.0447855, + 0.9760523, + 1.0 + ], + [ + -0.8563134, + 0.9967798, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.15017, + 1.0738698, + 1.0 + ], + [ + -8.293659, + 1.0015079, + 1.0 + ], + [ + -3.465691, + 0.95468867, + 1.0 + ], + [ + -0.26872262, + 0.996449, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.361571, + 1.0671077, + 1.0 + ], + [ + -8.069062, + 0.9878739, + 1.0 + ], + [ + -1.8393602, + 0.9616174, + 1.0 + ], + [ + -0.062451784, + 0.99806386, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.813561, + 1.0733019, + 1.0 + ], + [ + -6.277449, + 0.9938862, + 1.0 + ], + [ + -0.91688836, + 0.98827964, + 1.0 + ], + [ + -0.015721286, + 0.99975455, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.033608, + 1.0607992, + 1.0 + ], + [ + -5.8924174, + 1.0059938, + 1.0 + ], + [ + -0.4630321, + 0.99918526, + 1.0 + ], + [ + -0.0028961068, + 0.9999799, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.24634, + 1.0676687, + 1.0 + ], + [ + -8.926855, + 1.0089554, + 1.0 + ], + [ + -1.2582607, + 0.998966, + 1.0 + ], + [ + -0.01142765, + 0.9999851, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.128018, + 1.0785146, + 1.0 + ], + [ + -8.119614, + 1.0094862, + 1.0 + ], + [ + -1.2649353, + 0.99898714, + 1.0 + ], + [ + -0.015170822, + 0.9999889, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.451852, + 1.0737165, + 1.0 + ], + [ + -6.9139895, + 1.0113754, + 1.0 + ], + [ + -1.088725, + 1.0001466, + 1.0 + ], + [ + -0.013777146, + 1.0000031, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.380149, + 1.0823865, + 1.0 + ], + [ + -6.0622687, + 1.0224757, + 1.0 + ], + [ + -0.49693757, + 1.0009257, + 1.0 + ], + [ + -0.004322603, + 1.0000051, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.628408, + 1.0830122, + 1.0 + ], + [ + -7.603063, + 1.0293987, + 1.0 + ], + [ + -0.7915703, + 1.0023602, + 1.0 + ], + [ + -0.005485728, + 1.0000141, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.070874, + 1.025278, + 1.0 + ], + [ + -8.004763, + 0.9920342, + 1.0 + ], + [ + -0.6927761, + 0.99896514, + 1.0 + ], + [ + -0.0051751113, + 0.99998105, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.791659, + 0.9914543, + 1.0 + ], + [ + -7.6757627, + 0.9670585, + 1.0 + ], + [ + -0.94827396, + 0.98121995, + 1.0 + ], + [ + -0.042875327, + 0.99850494, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.504122, + 0.918028, + 1.0 + ], + [ + -9.548977, + 0.89464694, + 1.0 + ], + [ + -2.6915827, + 0.9125134, + 1.0 + ], + [ + -0.46877646, + 0.9763951, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.834205, + 0.86167675, + 1.0 + ], + [ + -11.029793, + 0.8641353, + 1.0 + ], + [ + -2.304663, + 0.940338, + 1.0 + ], + [ + -0.22860166, + 0.9885806, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.163353, + 0.80446744, + 1.0 + ], + [ + -10.703714, + 0.82697487, + 1.0 + ], + [ + -1.8142037, + 0.948325, + 1.0 + ], + [ + -0.0477379, + 0.998123, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.573051, + 0.76530474, + 1.0 + ], + [ + -10.532188, + 0.808058, + 1.0 + ], + [ + -1.7215453, + 0.94441307, + 1.0 + ], + [ + -0.0065259486, + 0.99902344, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.712678, + 0.78053474, + 1.0 + ], + [ + -9.12928, + 0.8284735, + 1.0 + ], + [ + -3.0647676, + 0.9377219, + 1.0 + ], + [ + -0.02522077, + 0.9987442, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.4553695, + 0.88699067, + 1.0 + ], + [ + -4.486537, + 0.9664551, + 1.0 + ], + [ + -1.3492911, + 1.0004994, + 1.0 + ], + [ + 0.20210887, + 0.98605, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.5626588, + 1.0187794, + 1.0 + ], + [ + -0.16849272, + 1.0859944, + 1.0 + ], + [ + 1.1092399, + 1.1023259, + 1.0 + ], + [ + 1.3777262, + 1.0548018, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.040617283, + 1.066993, + 1.0 + ], + [ + 1.53646, + 1.1038263, + 1.0 + ], + [ + 2.3726995, + 1.106449, + 1.0 + ], + [ + 2.8319159, + 1.0958794, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8091955, + 1.042359, + 1.0 + ], + [ + 2.302053, + 1.0939299, + 1.0 + ], + [ + 3.1255443, + 1.0935813, + 1.0 + ], + [ + 3.7047086, + 1.0906761, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4341065, + 0.9560042, + 1.0 + ], + [ + 2.3559563, + 1.0608075, + 1.0 + ], + [ + 3.3579242, + 1.0881857, + 1.0 + ], + [ + 3.2111607, + 1.0706342, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0547562, + 0.8983519, + 1.0 + ], + [ + 3.383354, + 0.9991967, + 1.0 + ], + [ + 1.8556306, + 1.0425386, + 1.0 + ], + [ + 0.0885949, + 1.0138919, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7682338, + 0.8603291, + 1.0 + ], + [ + 3.0132825, + 0.9588983, + 1.0 + ], + [ + 1.9995759, + 1.0111612, + 1.0 + ], + [ + 0.47482064, + 1.006286, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.6232177, + 0.8233142, + 1.0 + ], + [ + 2.0865932, + 0.9309424, + 1.0 + ], + [ + 2.7574844, + 0.97783035, + 1.0 + ], + [ + 0.95219344, + 0.99299604, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.6943194, + 0.79197764, + 1.0 + ], + [ + 1.8879877, + 0.920102, + 1.0 + ], + [ + 1.2647737, + 0.9986511, + 1.0 + ], + [ + 0.3897333, + 1.0005867, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7574468, + 0.78670424, + 1.0 + ], + [ + 2.1780686, + 0.9121051, + 1.0 + ], + [ + -0.5525949, + 1.0213124, + 1.0 + ], + [ + -0.2346502, + 1.0064204, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.4999485, + 0.7841847, + 1.0 + ], + [ + 1.9435306, + 0.9184122, + 1.0 + ], + [ + -1.5851839, + 1.0305399, + 1.0 + ], + [ + -0.36774108, + 1.0069425, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.909163, + 0.80230415, + 1.0 + ], + [ + 2.1358707, + 0.935365, + 1.0 + ], + [ + -0.84193116, + 1.0221872, + 1.0 + ], + [ + -0.14351588, + 1.0037925, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.5533886, + 0.8221586, + 1.0 + ], + [ + 3.3628576, + 0.95415354, + 1.0 + ], + [ + 0.8394116, + 1.0065193, + 1.0 + ], + [ + 0.021697784, + 1.000693, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 8.034471, + 0.84136575, + 1.0 + ], + [ + 4.6422033, + 0.9475814, + 1.0 + ], + [ + 1.4912386, + 0.99571365, + 1.0 + ], + [ + 0.018330399, + 1.0000721, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.851116, + 0.9006105, + 1.0 + ], + [ + 5.2049785, + 0.9920339, + 1.0 + ], + [ + 1.2569566, + 0.99717367, + 1.0 + ], + [ + 0.0052652257, + 0.9997678, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.6648865, + 0.9650344, + 1.0 + ], + [ + 4.729117, + 1.0339372, + 1.0 + ], + [ + 2.091848, + 0.99583614, + 1.0 + ], + [ + 0.10322704, + 0.99907774, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.6680355, + 0.9877355, + 1.0 + ], + [ + 3.6761901, + 1.0451121, + 1.0 + ], + [ + -0.04351923, + 1.0153469, + 1.0 + ], + [ + -0.03283479, + 1.0004294, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.463548, + 0.96174765, + 1.0 + ], + [ + 2.6405559, + 1.0113807, + 1.0 + ], + [ + -0.0145218745, + 1.0030446, + 1.0 + ], + [ + 0.02793165, + 1.0006396, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.7405667, + 0.97143346, + 1.0 + ], + [ + 1.3641241, + 1.0326787, + 1.0 + ], + [ + -3.4862273, + 1.0015736, + 1.0 + ], + [ + -0.47250292, + 0.99943143, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 8.19507, + 0.96321476, + 1.0 + ], + [ + 3.4350495, + 1.058396, + 1.0 + ], + [ + -3.5258145, + 1.0094136, + 1.0 + ], + [ + -1.0841179, + 0.9979366, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.8372226, + 0.95261025, + 1.0 + ], + [ + 4.014715, + 1.0542111, + 1.0 + ], + [ + 0.26240677, + 1.0264678, + 1.0 + ], + [ + -0.21519896, + 1.0007783, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.1908545, + 0.95756376, + 1.0 + ], + [ + 1.8382027, + 1.0398521, + 1.0 + ], + [ + 0.32465455, + 1.0176383, + 1.0 + ], + [ + -0.07286978, + 0.99991214, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.5853324, + 0.9502199, + 1.0 + ], + [ + 0.9021832, + 1.0475446, + 1.0 + ], + [ + -0.38347927, + 1.0296501, + 1.0 + ], + [ + -0.10875285, + 1.0017142, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.5904784, + 0.95001745, + 1.0 + ], + [ + 0.3187001, + 1.0552145, + 1.0 + ], + [ + -0.065457314, + 1.0441794, + 1.0 + ], + [ + 0.1682706, + 1.0044458, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.727416, + 0.9608402, + 1.0 + ], + [ + -0.4404374, + 1.0511845, + 1.0 + ], + [ + 0.13549781, + 1.0374392, + 1.0 + ], + [ + 1.0298581, + 1.0050887, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.1869535, + 0.96325994, + 1.0 + ], + [ + 0.21250498, + 1.0488186, + 1.0 + ], + [ + 0.5558834, + 1.0353485, + 1.0 + ], + [ + 1.3571175, + 1.0007496, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.2386184, + 0.97497666, + 1.0 + ], + [ + 0.37397635, + 1.0394269, + 1.0 + ], + [ + 0.7773281, + 1.034625, + 1.0 + ], + [ + 1.4875422, + 1.0045793, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.65055, + 0.98630524, + 1.0 + ], + [ + 0.3505162, + 1.0497316, + 1.0 + ], + [ + 1.0327646, + 1.0434405, + 1.0 + ], + [ + 1.5126252, + 1.0201807, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.1305985, + 0.95614517, + 1.0 + ], + [ + -0.6686687, + 0.96170896, + 1.0 + ], + [ + 0.31405145, + 0.94526744, + 1.0 + ], + [ + 0.7571128, + 0.93466496, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.7789507, + 0.96007, + 1.0 + ], + [ + 0.025442632, + 0.9322739, + 1.0 + ], + [ + 1.002043, + 0.92365426, + 1.0 + ], + [ + 1.4359783, + 0.9207567, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.5217903, + 0.9649529, + 1.0 + ], + [ + 1.3698361, + 0.93013364, + 1.0 + ], + [ + 0.81097263, + 0.8966511, + 1.0 + ], + [ + 1.3134359, + 0.9056901, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.4688148, + 0.94516605, + 1.0 + ], + [ + 1.1258384, + 0.9285592, + 1.0 + ], + [ + -0.03493635, + 0.9379082, + 1.0 + ], + [ + -0.34919712, + 0.92182875, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0855563, + 0.9416833, + 1.0 + ], + [ + 0.61865765, + 0.95953137, + 1.0 + ], + [ + -0.53801084, + 0.98488677, + 1.0 + ], + [ + -1.3929236, + 0.9545244, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.280524, + 0.9401836, + 1.0 + ], + [ + 0.5206633, + 0.91849095, + 1.0 + ], + [ + -0.5855471, + 0.9109686, + 1.0 + ], + [ + -1.857449, + 0.9108761, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.1066036, + 0.91177595, + 1.0 + ], + [ + -0.2379211, + 0.90215147, + 1.0 + ], + [ + -0.662678, + 0.8674564, + 1.0 + ], + [ + -1.6655282, + 0.8560494, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.3806509, + 0.91369975, + 1.0 + ], + [ + -1.1977674, + 0.9291132, + 1.0 + ], + [ + -2.1955984, + 0.9157609, + 1.0 + ], + [ + -3.2753534, + 0.8795514, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.27487, + 0.8657369, + 1.0 + ], + [ + 0.84525156, + 0.8752064, + 1.0 + ], + [ + -1.1533172, + 0.88577944, + 1.0 + ], + [ + -4.5261264, + 0.8575033, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.6447818, + 0.8502878, + 1.0 + ], + [ + 1.9611719, + 0.87647694, + 1.0 + ], + [ + -0.25773066, + 0.88421917, + 1.0 + ], + [ + -2.0909984, + 0.84323335, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.3915353, + 0.95410204, + 1.0 + ], + [ + 1.9980977, + 0.9531656, + 1.0 + ], + [ + 0.5931035, + 0.88896406, + 1.0 + ], + [ + -1.3361316, + 0.8371896, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.3264365, + 1.0299196, + 1.0 + ], + [ + 2.5364761, + 0.97348475, + 1.0 + ], + [ + 1.3221674, + 0.88718134, + 1.0 + ], + [ + -0.9853643, + 0.8285287, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.1308208, + 1.0718769, + 1.0 + ], + [ + 2.790149, + 0.9912555, + 1.0 + ], + [ + 1.4749019, + 0.8944228, + 1.0 + ], + [ + -1.1555754, + 0.8163407, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2958386, + 1.088511, + 1.0 + ], + [ + 2.6916971, + 0.9975709, + 1.0 + ], + [ + 1.9740455, + 0.88601756, + 1.0 + ], + [ + 0.36857194, + 0.80993265, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.5474374, + 1.0788504, + 1.0 + ], + [ + 2.225414, + 0.97936237, + 1.0 + ], + [ + 1.8757105, + 0.88728917, + 1.0 + ], + [ + 1.5990429, + 0.8291341, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.2438161, + 1.036749, + 1.0 + ], + [ + 1.3613421, + 0.9619165, + 1.0 + ], + [ + 1.4527313, + 0.8871177, + 1.0 + ], + [ + 2.0332718, + 0.8303382, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.09551922, + 1.0317862, + 1.0 + ], + [ + 0.43391225, + 0.9460805, + 1.0 + ], + [ + 1.4488848, + 0.8737923, + 1.0 + ], + [ + 3.4753993, + 0.81625134, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.7019963, + 1.0077614, + 1.0 + ], + [ + -0.051761873, + 0.9282602, + 1.0 + ], + [ + 1.9012142, + 0.8732041, + 1.0 + ], + [ + 4.1987333, + 0.82554394, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.6378045, + 1.0135831, + 1.0 + ], + [ + -0.38506448, + 0.9445609, + 1.0 + ], + [ + 1.5109704, + 0.91002667, + 1.0 + ], + [ + 3.479174, + 0.8619353, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.4618201, + 1.033089, + 1.0 + ], + [ + -1.2937772, + 0.97869325, + 1.0 + ], + [ + 0.10574721, + 0.9512708, + 1.0 + ], + [ + 1.488456, + 0.92858106, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.1203666, + 1.0350168, + 1.0 + ], + [ + -2.2538068, + 0.99723995, + 1.0 + ], + [ + -1.1310667, + 0.97431004, + 1.0 + ], + [ + 0.084608465, + 0.98049074, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.3735642, + 1.0520235, + 1.0 + ], + [ + -3.6140828, + 1.0261108, + 1.0 + ], + [ + -1.7979896, + 1.003871, + 1.0 + ], + [ + -0.05716326, + 0.9986102, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -5.2478585, + 1.0614483, + 1.0 + ], + [ + -4.203233, + 1.0365448, + 1.0 + ], + [ + -1.1785761, + 1.0052958, + 1.0 + ], + [ + -0.012518613, + 0.999905, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -5.7618976, + 1.0709513, + 1.0 + ], + [ + -4.2346697, + 1.041181, + 1.0 + ], + [ + -0.55711687, + 1.0027059, + 1.0 + ], + [ + -0.012013823, + 0.99982893, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.164432, + 1.0977043, + 1.0 + ], + [ + -4.544374, + 1.0551589, + 1.0 + ], + [ + -0.9486156, + 1.0006344, + 1.0 + ], + [ + -0.2880987, + 0.99641603, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.89083, + 1.0878369, + 1.0 + ], + [ + -5.120464, + 1.0464156, + 1.0 + ], + [ + -0.8068461, + 0.99902976, + 1.0 + ], + [ + -0.2889861, + 0.9960283, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.618337, + 1.0371728, + 1.0 + ], + [ + -5.1906323, + 1.007005, + 1.0 + ], + [ + -2.135625, + 0.9941443, + 1.0 + ], + [ + -0.21111596, + 0.99932176, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.985677, + 1.0003744, + 1.0 + ], + [ + -5.3432465, + 0.98975044, + 1.0 + ], + [ + -2.6925411, + 0.99021024, + 1.0 + ], + [ + -0.2637423, + 0.99901223, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.557486, + 1.0012228, + 1.0 + ], + [ + -5.126186, + 0.97562605, + 1.0 + ], + [ + -1.487278, + 0.9767831, + 1.0 + ], + [ + -0.09417072, + 0.99855137, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.93245, + 0.9848428, + 1.0 + ], + [ + -5.005292, + 0.95344836, + 1.0 + ], + [ + -1.0024476, + 0.97530013, + 1.0 + ], + [ + -0.03428691, + 0.9989181, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -10.067035, + 0.99813294, + 1.0 + ], + [ + -3.760256, + 0.9666209, + 1.0 + ], + [ + -0.43569535, + 0.9912011, + 1.0 + ], + [ + -0.007490502, + 0.99986094, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.267269, + 0.975714, + 1.0 + ], + [ + -3.3425367, + 0.97395366, + 1.0 + ], + [ + -0.16193047, + 0.99658626, + 1.0 + ], + [ + -0.00091553305, + 0.9999671, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.993388, + 1.0000288, + 1.0 + ], + [ + -4.422481, + 0.985607, + 1.0 + ], + [ + -0.32398883, + 0.9973332, + 1.0 + ], + [ + -0.0023061063, + 0.99997354, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.084186, + 0.9982832, + 1.0 + ], + [ + -2.7218752, + 0.9938026, + 1.0 + ], + [ + -0.17255202, + 0.9993347, + 1.0 + ], + [ + -0.0015168872, + 0.99999547, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.26013, + 0.9777609, + 1.0 + ], + [ + -2.3467531, + 0.98704755, + 1.0 + ], + [ + -0.09092708, + 0.9993146, + 1.0 + ], + [ + -0.00067042705, + 0.9999976, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.507622, + 0.9864571, + 1.0 + ], + [ + -1.6202773, + 0.9965017, + 1.0 + ], + [ + -0.03161555, + 0.999892, + 1.0 + ], + [ + -0.00015287292, + 0.9999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.677804, + 0.9922909, + 1.0 + ], + [ + -1.5864677, + 1.0017024, + 1.0 + ], + [ + -0.029533267, + 1.0000547, + 1.0 + ], + [ + -0.00012411954, + 1.0000002, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.914085, + 0.95310205, + 1.0 + ], + [ + -2.334698, + 0.99508375, + 1.0 + ], + [ + -0.04895491, + 0.99995136, + 1.0 + ], + [ + -0.00017408784, + 0.9999993, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.237178, + 0.8746589, + 1.0 + ], + [ + -3.9431214, + 0.96841735, + 1.0 + ], + [ + -0.13730685, + 0.99794483, + 1.0 + ], + [ + -0.0053191665, + 0.999769, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.975474, + 0.80612254, + 1.0 + ], + [ + -5.441822, + 0.93426883, + 1.0 + ], + [ + -0.5847607, + 0.97942346, + 1.0 + ], + [ + -0.0973578, + 0.99491835, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -16.96701, + 0.7174082, + 1.0 + ], + [ + -7.501978, + 0.8796432, + 1.0 + ], + [ + -0.55444115, + 0.9846425, + 1.0 + ], + [ + -0.0493976, + 0.9975046, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -19.691572, + 0.6349826, + 1.0 + ], + [ + -11.398152, + 0.7708063, + 1.0 + ], + [ + -0.78708935, + 0.97114944, + 1.0 + ], + [ + -0.013223002, + 0.99905497, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -18.446426, + 0.61982644, + 1.0 + ], + [ + -14.164779, + 0.6866288, + 1.0 + ], + [ + -1.6122752, + 0.95558673, + 1.0 + ], + [ + -0.004578955, + 0.9995476, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.488046, + 0.67327017, + 1.0 + ], + [ + -11.041826, + 0.6985784, + 1.0 + ], + [ + -3.797718, + 0.8836202, + 1.0 + ], + [ + -0.021354746, + 0.9991143, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.215271, + 0.79168403, + 1.0 + ], + [ + -5.9288893, + 0.82115835, + 1.0 + ], + [ + -3.627646, + 0.8675918, + 1.0 + ], + [ + -0.10416387, + 0.9819024, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.4996793, + 0.90832794, + 1.0 + ], + [ + -1.8338926, + 0.95480883, + 1.0 + ], + [ + -1.2572361, + 0.96503127, + 1.0 + ], + [ + -0.7744646, + 0.9684802, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.1851404, + 0.9754471, + 1.0 + ], + [ + -0.38070896, + 0.9967761, + 1.0 + ], + [ + -0.20595744, + 0.99183595, + 1.0 + ], + [ + -0.04291819, + 0.991432, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.24601784, + 0.9362298, + 1.0 + ], + [ + 0.4131334, + 1.0015087, + 1.0 + ], + [ + 0.6097395, + 1.0106862, + 1.0 + ], + [ + 0.80979943, + 1.0157323, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.15171635, + 0.7862158, + 1.0 + ], + [ + 0.88354856, + 0.9524625, + 1.0 + ], + [ + 1.2969238, + 1.0249856, + 1.0 + ], + [ + 1.3480076, + 1.0304265, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.2697306, + 0.74219435, + 1.0 + ], + [ + 2.2566214, + 0.8504037, + 1.0 + ], + [ + 0.43116477, + 0.98642737, + 1.0 + ], + [ + -0.62197345, + 0.9856124, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.9409392, + 0.7197661, + 1.0 + ], + [ + 2.657014, + 0.82564926, + 1.0 + ], + [ + 0.75268286, + 0.9767613, + 1.0 + ], + [ + 0.055744026, + 0.9941756, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.8314111, + 0.7361832, + 1.0 + ], + [ + 2.2688785, + 0.83848625, + 1.0 + ], + [ + 0.86895645, + 0.979231, + 1.0 + ], + [ + 0.0529236, + 1.0007346, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.6000655, + 0.7524115, + 1.0 + ], + [ + 2.0968463, + 0.888389, + 1.0 + ], + [ + 0.18316315, + 0.9902795, + 1.0 + ], + [ + -0.048968393, + 0.99920696, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.5598903, + 0.763862, + 1.0 + ], + [ + 1.1714306, + 0.8932834, + 1.0 + ], + [ + -0.57951224, + 0.985895, + 1.0 + ], + [ + -0.13511501, + 0.99782085, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.1605225, + 0.7730435, + 1.0 + ], + [ + 1.3596042, + 0.9008872, + 1.0 + ], + [ + -0.47168514, + 0.99376947, + 1.0 + ], + [ + -0.07464302, + 1.000353, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.752158, + 0.79429775, + 1.0 + ], + [ + 0.96804696, + 0.9385464, + 1.0 + ], + [ + -1.3032031, + 0.99664253, + 1.0 + ], + [ + -0.2467647, + 0.99934804, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.692927, + 0.817162, + 1.0 + ], + [ + 1.2399592, + 0.9601324, + 1.0 + ], + [ + -0.2092135, + 0.99869674, + 1.0 + ], + [ + -0.04914082, + 0.9998154, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.3077126, + 0.8646981, + 1.0 + ], + [ + 1.169301, + 0.9719851, + 1.0 + ], + [ + -0.14287929, + 0.9983894, + 1.0 + ], + [ + -0.070898615, + 0.9997087, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.741663, + 0.89205533, + 1.0 + ], + [ + 0.64960855, + 0.9830752, + 1.0 + ], + [ + -0.11191467, + 0.9987406, + 1.0 + ], + [ + -0.047313914, + 0.99977046, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.5162182, + 0.94403803, + 1.0 + ], + [ + 0.07143193, + 0.9925508, + 1.0 + ], + [ + -0.72076577, + 0.9948075, + 1.0 + ], + [ + -0.1948217, + 0.9987798, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.585983, + 0.96052504, + 1.0 + ], + [ + 1.0876106, + 0.9904767, + 1.0 + ], + [ + -0.27299738, + 0.99780345, + 1.0 + ], + [ + -0.080156155, + 0.99953175, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.779256, + 0.9310572, + 1.0 + ], + [ + 0.7266878, + 0.986474, + 1.0 + ], + [ + 0.035173222, + 0.99929446, + 1.0 + ], + [ + 0.0016433608, + 1.0000198, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.0365615, + 0.94872975, + 1.0 + ], + [ + 0.30465418, + 0.9817023, + 1.0 + ], + [ + -0.34721652, + 0.99478245, + 1.0 + ], + [ + -0.061744135, + 0.99972767, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.8961987, + 0.92921335, + 1.0 + ], + [ + 0.23940657, + 0.9766385, + 1.0 + ], + [ + -1.319209, + 0.99436516, + 1.0 + ], + [ + -0.2552591, + 0.9993648, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.820354, + 0.9000567, + 1.0 + ], + [ + 0.93661356, + 0.9792745, + 1.0 + ], + [ + -1.2972772, + 1.0013816, + 1.0 + ], + [ + -0.2005057, + 1.0001276, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.8547032, + 0.8771825, + 1.0 + ], + [ + -0.40298477, + 0.92672235, + 1.0 + ], + [ + -0.76510406, + 0.983101, + 1.0 + ], + [ + -0.10773284, + 0.9988855, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.1437955, + 0.87208176, + 1.0 + ], + [ + -0.35798803, + 0.9345046, + 1.0 + ], + [ + -0.6719991, + 0.9889946, + 1.0 + ], + [ + -0.12287665, + 0.9989714, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.7768846, + 0.90311426, + 1.0 + ], + [ + -0.6762936, + 0.95233446, + 1.0 + ], + [ + -1.8836145, + 0.9920913, + 1.0 + ], + [ + -0.860416, + 0.9946867, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.8961434, + 0.8824976, + 1.0 + ], + [ + -0.33391425, + 0.92908406, + 1.0 + ], + [ + -0.3221192, + 0.9918024, + 1.0 + ], + [ + -0.09044509, + 0.9966171, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.8632536, + 0.90186435, + 1.0 + ], + [ + 0.58749425, + 0.9437153, + 1.0 + ], + [ + 0.48553416, + 0.98712, + 1.0 + ], + [ + 0.19696854, + 0.99386424, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.8013225, + 0.9050866, + 1.0 + ], + [ + -0.49256903, + 0.9264555, + 1.0 + ], + [ + -1.7974619, + 0.9501046, + 1.0 + ], + [ + -1.5920177, + 0.97445244, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.4498076, + 0.92861384, + 1.0 + ], + [ + -0.7343135, + 0.9649644, + 1.0 + ], + [ + -1.2369624, + 0.9541305, + 1.0 + ], + [ + -1.1794292, + 0.9540083, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.9042714, + 0.9180204, + 1.0 + ], + [ + -2.219778, + 0.86444235, + 1.0 + ], + [ + -2.4816806, + 0.823798, + 1.0 + ], + [ + -1.5569929, + 0.8375031, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.83755153, + 0.91707826, + 1.0 + ], + [ + 0.22283489, + 0.86605746, + 1.0 + ], + [ + 0.4647223, + 0.8345799, + 1.0 + ], + [ + 0.72768146, + 0.84671277, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.523536, + 0.9087665, + 1.0 + ], + [ + 1.2768013, + 0.85424995, + 1.0 + ], + [ + 2.1177902, + 0.8432673, + 1.0 + ], + [ + 2.3680518, + 0.8740304, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2746108, + 0.81840515, + 1.0 + ], + [ + 2.7676985, + 0.7900528, + 1.0 + ], + [ + 1.9201541, + 0.8856915, + 1.0 + ], + [ + 1.8866074, + 0.9185119, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.6828327, + 0.8136951, + 1.0 + ], + [ + 2.175628, + 0.84009886, + 1.0 + ], + [ + 2.0939028, + 0.8847502, + 1.0 + ], + [ + 2.5929403, + 0.8947538, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.768817, + 0.7967659, + 1.0 + ], + [ + 3.2244751, + 0.7584971, + 1.0 + ], + [ + 3.6050847, + 0.7831417, + 1.0 + ], + [ + 4.268032, + 0.8045191, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.3562844, + 0.7890957, + 1.0 + ], + [ + 3.7188308, + 0.7199401, + 1.0 + ], + [ + 5.9207273, + 0.6976366, + 1.0 + ], + [ + 7.6120353, + 0.70215327, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0832772, + 0.7600011, + 1.0 + ], + [ + 2.2862017, + 0.76166797, + 1.0 + ], + [ + 3.6557348, + 0.74157053, + 1.0 + ], + [ + 5.3892546, + 0.731114, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.7183886, + 0.6808121, + 1.0 + ], + [ + 3.3387003, + 0.70804834, + 1.0 + ], + [ + 3.3728793, + 0.7371294, + 1.0 + ], + [ + 4.041886, + 0.73437375, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.624325, + 0.6674578, + 1.0 + ], + [ + 4.5378666, + 0.6732078, + 1.0 + ], + [ + 4.398033, + 0.71216196, + 1.0 + ], + [ + 5.5895276, + 0.7182609, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.306699, + 0.747718, + 1.0 + ], + [ + 4.879655, + 0.70281416, + 1.0 + ], + [ + 5.1314225, + 0.7001976, + 1.0 + ], + [ + 5.486214, + 0.72053, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.146373, + 0.78986615, + 1.0 + ], + [ + 3.90747, + 0.7194833, + 1.0 + ], + [ + 4.436473, + 0.7135913, + 1.0 + ], + [ + 3.9732492, + 0.7439657, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.051203, + 0.8220685, + 1.0 + ], + [ + 2.5377185, + 0.73653644, + 1.0 + ], + [ + 3.6690881, + 0.73991066, + 1.0 + ], + [ + 2.8405402, + 0.76611686, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.0629168, + 0.88988215, + 1.0 + ], + [ + 1.5405259, + 0.80136687, + 1.0 + ], + [ + 2.3380709, + 0.755409, + 1.0 + ], + [ + 1.6457901, + 0.78002536, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.9272873, + 0.8686442, + 1.0 + ], + [ + 0.6143116, + 0.7969848, + 1.0 + ], + [ + 0.5216986, + 0.7725941, + 1.0 + ], + [ + 0.27696848, + 0.79700214, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.18347983, + 0.7820147, + 1.0 + ], + [ + -0.2920065, + 0.78137934, + 1.0 + ], + [ + -0.7294265, + 0.8024376, + 1.0 + ], + [ + -0.6821398, + 0.81814516, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.23992704, + 0.7879301, + 1.0 + ], + [ + -0.88038236, + 0.8075125, + 1.0 + ], + [ + -0.9809185, + 0.8284354, + 1.0 + ], + [ + -0.07751103, + 0.8428534, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.80605406, + 0.8354792, + 1.0 + ], + [ + -1.559409, + 0.80948555, + 1.0 + ], + [ + -0.7388032, + 0.83500856, + 1.0 + ], + [ + 0.69942343, + 0.88329875, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.6294653, + 0.85172844, + 1.0 + ], + [ + -1.7645487, + 0.8297184, + 1.0 + ], + [ + -0.7100293, + 0.8840055, + 1.0 + ], + [ + 0.2274858, + 0.9601816, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.2752452, + 0.8992305, + 1.0 + ], + [ + -2.2524478, + 0.9024475, + 1.0 + ], + [ + -0.6900573, + 0.96735644, + 1.0 + ], + [ + -0.01443623, + 0.99743444, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.3342621, + 0.9171061, + 1.0 + ], + [ + -2.169138, + 0.9233468, + 1.0 + ], + [ + -0.38522747, + 0.9775049, + 1.0 + ], + [ + 0.0013864042, + 0.9985635, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.9786882, + 0.9581507, + 1.0 + ], + [ + -1.0984265, + 0.9696264, + 1.0 + ], + [ + -0.10668975, + 0.9950265, + 1.0 + ], + [ + -0.0001844623, + 0.99983454, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.8451796, + 0.9728855, + 1.0 + ], + [ + -0.75453144, + 0.9875004, + 1.0 + ], + [ + -0.03869543, + 0.998888, + 1.0 + ], + [ + -0.00016324317, + 0.99999046, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.4833093, + 0.9710839, + 1.0 + ], + [ + -0.4503776, + 0.99415845, + 1.0 + ], + [ + -0.014969094, + 0.9998039, + 1.0 + ], + [ + -0.00005460353, + 0.99999875, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.155806, + 0.9922136, + 1.0 + ], + [ + -0.24781401, + 0.9983759, + 1.0 + ], + [ + -0.009547606, + 0.9999699, + 1.0 + ], + [ + -0.00066759216, + 0.9999967, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.4516653, + 0.99805427, + 1.0 + ], + [ + -0.20918414, + 0.999779, + 1.0 + ], + [ + -0.006043913, + 0.9999931, + 1.0 + ], + [ + -0.00048581572, + 0.99999654, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.9392998, + 0.98668915, + 1.0 + ], + [ + -0.14349174, + 0.99852246, + 1.0 + ], + [ + -0.01090069, + 0.99995995, + 1.0 + ], + [ + -0.00083713053, + 0.99999756, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.1864663, + 0.9834442, + 1.0 + ], + [ + -0.18628179, + 0.99777365, + 1.0 + ], + [ + -0.014109625, + 0.99992806, + 1.0 + ], + [ + -0.001064983, + 0.9999959, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.0547693, + 0.98770183, + 1.0 + ], + [ + -0.17180885, + 0.9951577, + 1.0 + ], + [ + -0.014593571, + 0.99926215, + 1.0 + ], + [ + -0.00052392326, + 0.99998504, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.3548598, + 0.97179633, + 1.0 + ], + [ + -0.51838243, + 0.95328885, + 1.0 + ], + [ + -0.06852023, + 0.99208575, + 1.0 + ], + [ + -0.00085176574, + 0.99991536, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.3932482, + 0.9842496, + 1.0 + ], + [ + -0.21709155, + 0.9691371, + 1.0 + ], + [ + -0.014957193, + 0.99505883, + 1.0 + ], + [ + -0.00015723599, + 0.99995285, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.95742047, + 0.9800242, + 1.0 + ], + [ + -0.09572666, + 0.9719048, + 1.0 + ], + [ + -0.0004223666, + 0.9954867, + 1.0 + ], + [ + 0.000010266345, + 0.9999555, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8219641, + 0.97176534, + 1.0 + ], + [ + -0.10358055, + 0.9572014, + 1.0 + ], + [ + -0.0028769998, + 0.99260867, + 1.0 + ], + [ + -0.0000138675605, + 0.9999241, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.81078154, + 0.9853913, + 1.0 + ], + [ + -0.07060721, + 0.99701566, + 1.0 + ], + [ + -0.0011506415, + 0.99966466, + 1.0 + ], + [ + -0.0000053977897, + 0.99999666, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8799515, + 0.9880938, + 1.0 + ], + [ + -0.09139433, + 0.99895227, + 1.0 + ], + [ + -0.0011206032, + 0.99998325, + 1.0 + ], + [ + -0.0000023958048, + 0.99999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.203021, + 0.98468393, + 1.0 + ], + [ + -0.09533536, + 0.9993307, + 1.0 + ], + [ + -0.0006138411, + 0.9999971, + 1.0 + ], + [ + -4.9110037e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.98682314, + 0.98741007, + 1.0 + ], + [ + -0.07803176, + 0.99942106, + 1.0 + ], + [ + -0.0005139681, + 0.9999974, + 1.0 + ], + [ + -3.048391e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.0533085, + 0.95174795, + 1.0 + ], + [ + -0.15526569, + 0.9967907, + 1.0 + ], + [ + -0.0011854534, + 0.9999825, + 1.0 + ], + [ + -0.0000012432189, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.3160176, + 0.8893779, + 1.0 + ], + [ + -0.37086096, + 0.99144685, + 1.0 + ], + [ + -0.0031809295, + 0.9999518, + 1.0 + ], + [ + -0.000015129414, + 0.9999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.65379, + 0.80011606, + 1.0 + ], + [ + -0.97619027, + 0.98192537, + 1.0 + ], + [ + -0.011185538, + 0.9998816, + 1.0 + ], + [ + -0.00027599925, + 0.999986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.900999, + 0.6643516, + 1.0 + ], + [ + -2.2590377, + 0.93988913, + 1.0 + ], + [ + -0.021305703, + 0.99952126, + 1.0 + ], + [ + -0.00016454396, + 0.9999922, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -18.50311, + 0.5672969, + 1.0 + ], + [ + -6.324091, + 0.8226143, + 1.0 + ], + [ + -0.09798097, + 0.99709153, + 1.0 + ], + [ + -0.0001432513, + 0.99998575, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -17.64413, + 0.5391097, + 1.0 + ], + [ + -10.467655, + 0.66710144, + 1.0 + ], + [ + -0.2899716, + 0.9890324, + 1.0 + ], + [ + -0.00047773414, + 0.9999807, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.92279, + 0.5472615, + 1.0 + ], + [ + -10.895535, + 0.59090734, + 1.0 + ], + [ + -2.0764418, + 0.9041057, + 1.0 + ], + [ + -0.006843486, + 0.9997131, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.823085, + 0.59954053, + 1.0 + ], + [ + -8.007232, + 0.64031166, + 1.0 + ], + [ + -5.934222, + 0.7216933, + 1.0 + ], + [ + -0.21140227, + 0.98925734, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.0854692, + 0.68398213, + 1.0 + ], + [ + -4.9988737, + 0.7475099, + 1.0 + ], + [ + -4.5387487, + 0.783282, + 1.0 + ], + [ + -3.1189833, + 0.85521823, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.5944576, + 0.7649848, + 1.0 + ], + [ + -4.2297106, + 0.8085376, + 1.0 + ], + [ + -4.0575027, + 0.8316775, + 1.0 + ], + [ + -3.6946223, + 0.857067, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.07587, + 0.76617646, + 1.0 + ], + [ + -3.7157166, + 0.84208333, + 1.0 + ], + [ + -3.0963829, + 0.88380915, + 1.0 + ], + [ + -2.4804156, + 0.9129343, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.87624, + 0.6232938, + 1.0 + ], + [ + -2.6126328, + 0.7971155, + 1.0 + ], + [ + -1.7279347, + 0.92270774, + 1.0 + ], + [ + -1.2290946, + 0.95807064, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.7994735, + 0.57053953, + 1.0 + ], + [ + -0.84233916, + 0.6636242, + 1.0 + ], + [ + -2.5714352, + 0.87382954, + 1.0 + ], + [ + -3.015385, + 0.8965249, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.3739244, + 0.61843336, + 1.0 + ], + [ + 0.4184842, + 0.76101744, + 1.0 + ], + [ + -0.6304034, + 0.94809633, + 1.0 + ], + [ + -0.7494771, + 0.9597989, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.97610384, + 0.7327941, + 1.0 + ], + [ + -0.014398668, + 0.87566924, + 1.0 + ], + [ + -0.052118707, + 0.9924021, + 1.0 + ], + [ + 0.008521295, + 0.99918205, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.5996346, + 0.8233891, + 1.0 + ], + [ + 0.3935372, + 0.9229845, + 1.0 + ], + [ + -0.09391027, + 0.99190533, + 1.0 + ], + [ + -0.03780514, + 0.998876, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.9291275, + 0.9039172, + 1.0 + ], + [ + 0.74847955, + 0.9787055, + 1.0 + ], + [ + -0.16242525, + 0.9931886, + 1.0 + ], + [ + -0.072900325, + 0.9978617, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.3570204, + 0.94836956, + 1.0 + ], + [ + 0.2503319, + 0.99067676, + 1.0 + ], + [ + -0.06907828, + 0.9989688, + 1.0 + ], + [ + -0.017280824, + 0.9998503, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.26804498, + 0.9617563, + 1.0 + ], + [ + -0.30944353, + 0.9933201, + 1.0 + ], + [ + -0.83434904, + 0.9959094, + 1.0 + ], + [ + -0.16132122, + 0.999225, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.45194325, + 0.97071046, + 1.0 + ], + [ + -0.040852666, + 0.99767536, + 1.0 + ], + [ + -0.18153699, + 0.9991148, + 1.0 + ], + [ + -0.03368746, + 0.99983805, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.58231443, + 0.987187, + 1.0 + ], + [ + -0.11279778, + 0.9996504, + 1.0 + ], + [ + -0.007223121, + 0.999977, + 1.0 + ], + [ + -0.0013776413, + 0.9999941, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.27319077, + 0.9664841, + 1.0 + ], + [ + -0.21705957, + 0.9958635, + 1.0 + ], + [ + -0.017606068, + 0.99986523, + 1.0 + ], + [ + -0.0026193615, + 0.9999856, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.21634439, + 0.9787581, + 1.0 + ], + [ + -0.2651225, + 0.9962405, + 1.0 + ], + [ + -0.19124968, + 0.99883384, + 1.0 + ], + [ + -0.042182684, + 0.99975127, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.008343396, + 0.96074563, + 1.0 + ], + [ + -0.06034358, + 0.99365026, + 1.0 + ], + [ + -0.07409725, + 0.99946153, + 1.0 + ], + [ + -0.0167451, + 0.99990094, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.21916604, + 0.97861654, + 1.0 + ], + [ + 0.011656337, + 0.9968962, + 1.0 + ], + [ + 0.000026073394, + 0.9999265, + 1.0 + ], + [ + -0.00011303921, + 0.9999988, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8516588, + 0.983058, + 1.0 + ], + [ + 0.05317016, + 0.9960106, + 1.0 + ], + [ + 0.0021729777, + 0.9996684, + 1.0 + ], + [ + -0.00008834622, + 0.9999944, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.33492264, + 0.97982824, + 1.0 + ], + [ + -0.023441888, + 0.9941379, + 1.0 + ], + [ + -0.012700134, + 0.9997447, + 1.0 + ], + [ + -0.0015705364, + 0.9999959, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.98577476, + 0.9558412, + 1.0 + ], + [ + 0.11146073, + 0.9861902, + 1.0 + ], + [ + -0.039014447, + 0.9995021, + 1.0 + ], + [ + -0.004039938, + 0.9999989, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4198768, + 0.92014015, + 1.0 + ], + [ + 0.017510591, + 0.95569116, + 1.0 + ], + [ + -0.07619311, + 0.99643916, + 1.0 + ], + [ + -0.0026185187, + 0.99994844, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.02523843, + 0.90478045, + 1.0 + ], + [ + -1.0690092, + 0.92566043, + 1.0 + ], + [ + -0.27379805, + 0.99030024, + 1.0 + ], + [ + -0.012234708, + 0.9998211, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.112236924, + 0.94679946, + 1.0 + ], + [ + -0.619657, + 0.96057886, + 1.0 + ], + [ + -0.9257425, + 0.9906025, + 1.0 + ], + [ + -0.29495418, + 0.99792683, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.21400073, + 0.91529566, + 1.0 + ], + [ + -0.7458604, + 0.93239814, + 1.0 + ], + [ + -1.0380262, + 0.98454165, + 1.0 + ], + [ + -0.59706265, + 0.99349016, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.4457225, + 0.91300154, + 1.0 + ], + [ + -1.336635, + 0.9316419, + 1.0 + ], + [ + -2.2979734, + 0.9655022, + 1.0 + ], + [ + -1.344974, + 0.9845655, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.25027204, + 0.9015076, + 1.0 + ], + [ + -3.4001799, + 0.8766961, + 1.0 + ], + [ + -6.167441, + 0.87958527, + 1.0 + ], + [ + -3.0434742, + 0.9592666, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8902801, + 0.87916595, + 1.0 + ], + [ + -3.0949912, + 0.86760527, + 1.0 + ], + [ + -5.8532324, + 0.81513554, + 1.0 + ], + [ + -4.114237, + 0.86317617, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.026975641, + 0.99708, + 1.0 + ], + [ + -0.64516276, + 0.9682286, + 1.0 + ], + [ + -2.8310719, + 0.86257124, + 1.0 + ], + [ + -1.7226968, + 0.90401584, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.082811065, + 0.9958276, + 1.0 + ], + [ + 0.029035931, + 0.98919505, + 1.0 + ], + [ + -0.10363939, + 0.9313849, + 1.0 + ], + [ + 0.010599485, + 0.93757856, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.30115137, + 0.995358, + 1.0 + ], + [ + 0.13033178, + 0.99306947, + 1.0 + ], + [ + 0.6179642, + 0.9668538, + 1.0 + ], + [ + 0.8925493, + 0.96109754, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.46534216, + 0.9799625, + 1.0 + ], + [ + 0.38193727, + 0.97773105, + 1.0 + ], + [ + 0.24684222, + 0.9894079, + 1.0 + ], + [ + 0.37070882, + 0.987069, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.28666112, + 0.9715252, + 1.0 + ], + [ + 0.6129641, + 0.9612684, + 1.0 + ], + [ + 1.5519472, + 0.9354917, + 1.0 + ], + [ + 2.0365298, + 0.9501482, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.5976872, + 0.95618755, + 1.0 + ], + [ + 2.4345472, + 0.8642554, + 1.0 + ], + [ + 4.334619, + 0.8086935, + 1.0 + ], + [ + 4.563015, + 0.845538, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8365076, + 0.92973554, + 1.0 + ], + [ + 4.732671, + 0.7514679, + 1.0 + ], + [ + 8.749074, + 0.67412585, + 1.0 + ], + [ + 9.21404, + 0.70109785, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.420841, + 0.77831614, + 1.0 + ], + [ + 3.1613727, + 0.7190518, + 1.0 + ], + [ + 5.003848, + 0.7066909, + 1.0 + ], + [ + 6.8984137, + 0.7076504, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2887714, + 0.6649921, + 1.0 + ], + [ + 3.2296984, + 0.6861676, + 1.0 + ], + [ + 3.542693, + 0.7290553, + 1.0 + ], + [ + 3.7580287, + 0.7677797, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.8353093, + 0.7206155, + 1.0 + ], + [ + 3.213582, + 0.7210075, + 1.0 + ], + [ + 3.050782, + 0.7906282, + 1.0 + ], + [ + 2.910971, + 0.86843896, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.73087895, + 0.9480793, + 1.0 + ], + [ + 1.3393304, + 0.90520597, + 1.0 + ], + [ + 1.8712325, + 0.8813981, + 1.0 + ], + [ + 1.7535455, + 0.9178119, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.2656817, + 0.9784777, + 1.0 + ], + [ + 0.6957168, + 0.93848366, + 1.0 + ], + [ + 1.0034746, + 0.92618793, + 1.0 + ], + [ + 0.6805362, + 0.95591986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.07101682, + 0.9880643, + 1.0 + ], + [ + 0.3550513, + 0.96534646, + 1.0 + ], + [ + 0.7367733, + 0.952579, + 1.0 + ], + [ + 0.41733772, + 0.96733385, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.021202909, + 0.99511874, + 1.0 + ], + [ + 0.03845068, + 0.98727, + 1.0 + ], + [ + 0.13032648, + 0.9788461, + 1.0 + ], + [ + 0.08247721, + 0.9886881, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.019193187, + 0.99454707, + 1.0 + ], + [ + -0.01639945, + 0.9824369, + 1.0 + ], + [ + -0.034814365, + 0.97299594, + 1.0 + ], + [ + 0.000092838956, + 0.9879655, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0020077226, + 0.98501676, + 1.0 + ], + [ + -0.01932977, + 0.98477453, + 1.0 + ], + [ + -0.08714962, + 0.9838237, + 1.0 + ], + [ + -0.05911569, + 0.99138635, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.028955147, + 0.9820292, + 1.0 + ], + [ + -0.0068421336, + 0.9893989, + 1.0 + ], + [ + -0.066570766, + 0.99078333, + 1.0 + ], + [ + -0.044429842, + 0.995392, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.032754235, + 0.99281675, + 1.0 + ], + [ + -0.024249062, + 0.9940026, + 1.0 + ], + [ + -0.016773215, + 0.99763656, + 1.0 + ], + [ + -0.00368944, + 0.99956965, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.013820456, + 0.9963605, + 1.0 + ], + [ + -0.023061087, + 0.99682546, + 1.0 + ], + [ + -0.006765129, + 0.99912953, + 1.0 + ], + [ + -0.00025614, + 0.99994946, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.03831513, + 0.9965622, + 1.0 + ], + [ + -0.026358912, + 0.9984241, + 1.0 + ], + [ + -0.0031044614, + 0.99983025, + 1.0 + ], + [ + -0.00004853841, + 0.99999666, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.042360652, + 0.99709636, + 1.0 + ], + [ + -0.02741898, + 0.99857044, + 1.0 + ], + [ + -0.0024811549, + 0.99984425, + 1.0 + ], + [ + -0.0000068021504, + 0.99999624, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.015694104, + 0.99947315, + 1.0 + ], + [ + -0.004300875, + 0.9996656, + 1.0 + ], + [ + -0.00022572257, + 0.99996036, + 1.0 + ], + [ + -2.7103656e-8, + 0.9999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.031804975, + 0.9993172, + 1.0 + ], + [ + -0.0036146475, + 0.9998796, + 1.0 + ], + [ + -0.00006266851, + 0.9999905, + 1.0 + ], + [ + -2.0947002e-7, + 0.99999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.03432048, + 0.99876255, + 1.0 + ], + [ + -0.0027313528, + 0.999917, + 1.0 + ], + [ + -0.00002901146, + 0.99999934, + 1.0 + ], + [ + -5.272942e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.010446178, + 0.9998674, + 1.0 + ], + [ + -0.0009102314, + 0.9999884, + 1.0 + ], + [ + -0.000010674661, + 0.9999999, + 1.0 + ], + [ + -6.7580764e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0119908275, + 0.9999285, + 1.0 + ], + [ + -0.00078701315, + 0.9999947, + 1.0 + ], + [ + -0.0000061383644, + 0.99999994, + 1.0 + ], + [ + -3.791176e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.005170042, + 0.99979734, + 1.0 + ], + [ + -0.0004141604, + 0.9999835, + 1.0 + ], + [ + -0.0000034080406, + 0.9999999, + 1.0 + ], + [ + -4.582497e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.00895596, + 0.9998542, + 1.0 + ], + [ + -0.000709496, + 0.99998856, + 1.0 + ], + [ + -0.0000056245453, + 0.99999994, + 1.0 + ], + [ + -5.8625062e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0146880755, + 0.9997208, + 1.0 + ], + [ + -0.0018232282, + 0.9998647, + 1.0 + ], + [ + -0.00015326608, + 0.9999809, + 1.0 + ], + [ + -0.0000014805827, + 0.9999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.022968672, + 0.99913424, + 1.0 + ], + [ + -0.014809832, + 0.99827707, + 1.0 + ], + [ + -0.0020878739, + 0.9997331, + 1.0 + ], + [ + -0.000020184694, + 0.99999744, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.011431702, + 0.99970055, + 1.0 + ], + [ + -0.0039257053, + 0.999323, + 1.0 + ], + [ + -0.00048654518, + 0.9998965, + 1.0 + ], + [ + -0.000004697737, + 0.999999, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0041034487, + 0.99944717, + 1.0 + ], + [ + -0.00006548014, + 0.99873835, + 1.0 + ], + [ + 0.000027589553, + 0.9997964, + 1.0 + ], + [ + 2.7555294e-7, + 0.999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.009381899, + 0.9987364, + 1.0 + ], + [ + -0.00043260615, + 0.9972713, + 1.0 + ], + [ + 0.000017650782, + 0.9995557, + 1.0 + ], + [ + 2.0419672e-7, + 0.99999547, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.008616836, + 0.99971557, + 1.0 + ], + [ + -0.00045231776, + 0.9998694, + 1.0 + ], + [ + -9.984535e-7, + 0.9999807, + 1.0 + ], + [ + 7.740104e-9, + 0.9999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0056563327, + 0.9998474, + 1.0 + ], + [ + -0.00033137825, + 0.99999225, + 1.0 + ], + [ + -0.0000016325387, + 1.0, + 1.0 + ], + [ + -1.0257498e-9, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.014827684, + 0.99960244, + 1.0 + ], + [ + -0.0007938461, + 0.99998146, + 1.0 + ], + [ + -0.00000293517, + 0.99999994, + 1.0 + ], + [ + -8.377253e-10, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.011590649, + 0.9997409, + 1.0 + ], + [ + -0.00065725995, + 0.9999863, + 1.0 + ], + [ + -0.0000025172164, + 0.99999994, + 1.0 + ], + [ + -7.089178e-10, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.11034412, + 0.99728817, + 1.0 + ], + [ + -0.006188652, + 0.9998468, + 1.0 + ], + [ + -0.000022063721, + 0.99999946, + 1.0 + ], + [ + -5.30563e-9, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.11615519, + 0.9962155, + 1.0 + ], + [ + -0.006629864, + 0.9997772, + 1.0 + ], + [ + -0.000026530815, + 0.99999917, + 1.0 + ], + [ + -9.810042e-9, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.2699671, + 0.992111, + 1.0 + ], + [ + -0.01619867, + 0.99955577, + 1.0 + ], + [ + -0.0000889456, + 0.99999845, + 1.0 + ], + [ + -7.3219084e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.84938484, + 0.9749287, + 1.0 + ], + [ + -0.0546363, + 0.99835104, + 1.0 + ], + [ + -0.00042785166, + 0.99998784, + 1.0 + ], + [ + -6.3949227e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.9408712, + 0.91166395, + 1.0 + ], + [ + -0.28487697, + 0.99084896, + 1.0 + ], + [ + -0.0041404446, + 0.99987274, + 1.0 + ], + [ + -0.00000840042, + 0.99999976, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.1285825, + 0.7972419, + 1.0 + ], + [ + -0.82483685, + 0.96542543, + 1.0 + ], + [ + -0.007825383, + 0.99962455, + 1.0 + ], + [ + -0.0000082233155, + 0.9999996, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.914042, + 0.6662763, + 1.0 + ], + [ + -3.2296083, + 0.84786505, + 1.0 + ], + [ + -0.09797766, + 0.9948829, + 1.0 + ], + [ + -0.000322063, + 0.999986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.49761, + 0.5991851, + 1.0 + ], + [ + -8.209785, + 0.6332046, + 1.0 + ], + [ + -2.2356362, + 0.88977855, + 1.0 + ], + [ + -0.03691413, + 0.9986572, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.16435, + 0.6105177, + 1.0 + ], + [ + -8.482127, + 0.6289409, + 1.0 + ], + [ + -8.195307, + 0.6653821, + 1.0 + ], + [ + -1.5681657, + 0.93180954, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.227832, + 0.6095772, + 1.0 + ], + [ + -9.688233, + 0.62955517, + 1.0 + ], + [ + -9.770227, + 0.6534116, + 1.0 + ], + [ + -7.684848, + 0.7312607, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.040249, + 0.61375934, + 1.0 + ], + [ + -9.414042, + 0.64601433, + 1.0 + ], + [ + -9.124609, + 0.6859369, + 1.0 + ], + [ + -6.8619404, + 0.76940507, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.1956196, + 0.65328926, + 1.0 + ], + [ + -6.4505258, + 0.6780245, + 1.0 + ], + [ + -6.7652173, + 0.777878, + 1.0 + ], + [ + -5.9136004, + 0.9036394, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8208507, + 0.76508826, + 1.0 + ], + [ + -1.3313887, + 0.8808544, + 1.0 + ], + [ + -9.083516, + 0.86273944, + 1.0 + ], + [ + -10.492065, + 0.8505087, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.051133215, + 0.93782914, + 1.0 + ], + [ + -0.07260832, + 0.9849166, + 1.0 + ], + [ + -0.9264943, + 0.9826398, + 1.0 + ], + [ + -1.1677287, + 0.97983795, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.07751575, + 0.97048557, + 1.0 + ], + [ + -0.043188777, + 0.9823848, + 1.0 + ], + [ + -0.002534433, + 0.99882054, + 1.0 + ], + [ + -0.00013920956, + 0.9999524, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.07916742, + 0.97018796, + 1.0 + ], + [ + -0.031966496, + 0.97699416, + 1.0 + ], + [ + -0.0035643438, + 0.99836385, + 1.0 + ], + [ + -0.00040987533, + 0.99998116, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.01877379, + 0.9972593, + 1.0 + ], + [ + 0.01205297, + 0.9989108, + 1.0 + ], + [ + -0.0013474375, + 0.99986976, + 1.0 + ], + [ + -0.0007788474, + 0.9999769, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.00081947824, + 0.99965537, + 1.0 + ], + [ + 0.00408295, + 0.9999879, + 1.0 + ], + [ + -0.0004688936, + 0.9999954, + 1.0 + ], + [ + -0.00016395445, + 0.9999985, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0022904403, + 0.9998439, + 1.0 + ], + [ + -0.0030316855, + 0.9999691, + 1.0 + ], + [ + -0.008458131, + 0.9999591, + 1.0 + ], + [ + -0.0015517423, + 0.99999255, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.008486173, + 0.9998458, + 1.0 + ], + [ + 0.00014019103, + 0.99998724, + 1.0 + ], + [ + -0.0017433885, + 0.99999154, + 1.0 + ], + [ + -0.00032057063, + 0.99999845, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.005678298, + 0.9997919, + 1.0 + ], + [ + -0.0006329326, + 0.99999183, + 1.0 + ], + [ + -0.000014824767, + 1.0, + 1.0 + ], + [ + -0.0000014804741, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.024680974, + 0.9990245, + 1.0 + ], + [ + 0.0004288364, + 0.9999345, + 1.0 + ], + [ + -0.000051545532, + 0.99999934, + 1.0 + ], + [ + -0.000005476217, + 0.99999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0061284457, + 0.9994695, + 1.0 + ], + [ + -0.0008772137, + 0.9999514, + 1.0 + ], + [ + -0.0005471564, + 0.9999964, + 1.0 + ], + [ + -0.00011416538, + 0.99999934, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.019823374, + 0.99714184, + 1.0 + ], + [ + 0.0006233722, + 0.99973315, + 1.0 + ], + [ + -0.00020987287, + 0.99999684, + 1.0 + ], + [ + -0.000045174842, + 0.99999976, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0055233804, + 0.99932915, + 1.0 + ], + [ + 0.00019314369, + 0.999932, + 1.0 + ], + [ + -0.000004102488, + 0.9999994, + 1.0 + ], + [ + -3.2671397e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.017069608, + 0.99980605, + 1.0 + ], + [ + 0.0011378126, + 0.9999736, + 1.0 + ], + [ + 0.0000045061006, + 0.9999994, + 1.0 + ], + [ + 4.9558377e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0025704305, + 0.9998648, + 1.0 + ], + [ + -0.00016181398, + 0.9999653, + 1.0 + ], + [ + -0.000009765289, + 0.9999992, + 1.0 + ], + [ + -2.9514266e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.011738374, + 0.99957114, + 1.0 + ], + [ + 0.0013190353, + 0.99990016, + 1.0 + ], + [ + 9.2287684e-7, + 0.99999744, + 1.0 + ], + [ + -0.0000011511228, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.04013104, + 0.99884504, + 1.0 + ], + [ + 0.00018569271, + 0.99945897, + 1.0 + ], + [ + -0.0010192711, + 0.9999544, + 1.0 + ], + [ + -0.000013688198, + 0.9999996, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.02411725, + 0.99782336, + 1.0 + ], + [ + -0.09172129, + 0.9966743, + 1.0 + ], + [ + -0.017824568, + 0.9994446, + 1.0 + ], + [ + -0.00024212734, + 0.9999933, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0085046105, + 0.9991983, + 1.0 + ], + [ + -0.032236904, + 0.9988923, + 1.0 + ], + [ + -0.03258688, + 0.9995004, + 1.0 + ], + [ + -0.022160465, + 0.99972326, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.047657337, + 0.9974346, + 1.0 + ], + [ + -0.17287616, + 0.9967666, + 1.0 + ], + [ + -1.7228909, + 0.97827595, + 1.0 + ], + [ + -1.5301245, + 0.9807228, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.1092619, + 0.9939699, + 1.0 + ], + [ + -0.28823772, + 0.9942406, + 1.0 + ], + [ + -2.4151208, + 0.9694442, + 1.0 + ], + [ + -2.120977, + 0.973369, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.046760842, + 0.99341387, + 1.0 + ], + [ + -0.46058732, + 0.9879337, + 1.0 + ], + [ + -0.9992375, + 0.97709274, + 1.0 + ], + [ + -0.27791965, + 0.9942543, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.055317637, + 0.988727, + 1.0 + ], + [ + -0.7323104, + 0.971684, + 1.0 + ], + [ + -2.933847, + 0.90179116, + 1.0 + ], + [ + -1.3042065, + 0.95250934, + 1.0 + ] + ], + "srgb_value": true + } +} \ No newline at end of file diff --git a/crates/denoise-core/Cargo.toml b/crates/denoise-core/Cargo.toml index b65f90ae4..d0a0b10c1 100644 --- a/crates/denoise-core/Cargo.toml +++ b/crates/denoise-core/Cargo.toml @@ -24,3 +24,4 @@ web-time = { workspace = true } [dev-dependencies] proptest = { workspace = true } +dhat = { workspace = true } diff --git a/crates/denoise-core/src/product.rs b/crates/denoise-core/src/product.rs index 5a607c8f4..c33b1d4c2 100644 --- a/crates/denoise-core/src/product.rs +++ b/crates/denoise-core/src/product.rs @@ -9,7 +9,7 @@ //! compressed length and CRC-32, then the strips' bytes. `key` says what the product was made from (the photo's //! content, the model, this code's version, the settings it depends on); a file with another key is stale. -use std::io::{Read, Write}; +use std::io::{Read, Seek, Write}; use std::path::Path; use lightcraft_raster::Rgb32f; @@ -216,6 +216,10 @@ fn read_start(r: &mut impl Read) -> Result { r.read_exact(&mut e)?; let l = u32::from_le_bytes([e[0], e[1], e[2], e[3]]) as usize; let c = u32::from_le_bytes([e[4], e[5], e[6], e[7]]); + // a strip never compresses to nothing (it has at least one row, so at least one deflated pixel) + if l == 0 { + return Err(ProductError::Format("a strip is empty".into())); + } // a strip never compresses to much more than its raw size if l > 12 * h.strip * h.width + 4096 { return Err(ProductError::Format("a strip is larger than it can be".into())); @@ -305,7 +309,6 @@ pub fn read_window(path: &Path, key: &str, window: Option, factor: usize } // the picture rows and columns the blocks cover let (x_end, y_end) = if whole { (w.x + w.width, w.y + w.height) } else { (w.x + ow * factor, w.y + oh * factor) }; - let mut out = Rgb32f { width: ow, height: oh, data: allocate(ow * oh, [0.0; 3])? }; let lut = f16_table(); let value = |raw: &[u8], c: usize, n: usize, i: usize| -> f32 { let (lo, hi) = (raw.get(2 * c * n + i).copied().unwrap_or(0), raw.get((2 * c + 1) * n + i).copied().unwrap_or(0)); @@ -318,6 +321,21 @@ pub fn read_window(path: &Path, key: &str, window: Option, factor: usize let strips: Vec = (0..table.len()).filter(|&s| wanted(s)).collect(); // skip the strips before the first one wanted let skipped: usize = table.iter().take(strips.first().copied().unwrap_or(0)).map(|e| e.0).sum(); + // The output is reserved only after the wanted strips' compressed bytes are present and match their checksums: + // a malformed cache whose strips are empty, damaged or short are rejected here, before the (large) result is + // allocated, not after. Reading them twice is cheap next to decoding them, and the fold below re-seeks here. + let strips_at = f.stream_position()?; + f.seek_relative(i64::try_from(skipped).map_err(|_| ProductError::Format("the file is too large".into()))?)?; + for &s in &strips { + let Some(&(len, expected)) = table.get(s) else { return Err(ProductError::Format("a strip is missing".into())) }; + let mut packed = allocate(len, 0u8)?; + f.read_exact(&mut packed)?; + if crc(&packed) != expected { + return Err(ProductError::Format(format!("strip {s} is damaged"))); + } + } + let mut out = Rgb32f { width: ow, height: oh, data: allocate(ow * oh, [0.0; 3])? }; + f.seek(std::io::SeekFrom::Start(strips_at))?; f.seek_relative(i64::try_from(skipped).map_err(|_| ProductError::Format("the file is too large".into()))?)?; for group in strips.chunks(GROUP) { let mut packed = Vec::with_capacity(group.len()); @@ -422,6 +440,21 @@ mod tests { let _ = std::fs::remove_file(p); } + #[test] + fn an_empty_strip_entry_is_rejected_by_the_header() { + let p = temp("empty-strip"); + let header = serde_json::to_vec(&Header { width: 16, height: 1, strip: STRIP_ROWS, key: "k".into() }).unwrap(); + let mut bytes = Vec::from(MAGIC.as_slice()); + bytes.extend_from_slice(&[VERSION, 0, 0, 0]); + bytes.extend_from_slice(&(header.len() as u32).to_le_bytes()); + bytes.extend_from_slice(&header); + // one strip with a zero-length entry: it has a row, so its deflated bytes are never empty + bytes.extend_from_slice(&[0; 8]); + std::fs::write(&p, bytes).unwrap(); + assert!(matches!(read_header_at(&p), Err(ProductError::Format(why)) if why.contains("empty"))); + let _ = std::fs::remove_file(p); + } + fn temp(name: &str) -> std::path::PathBuf { std::env::temp_dir().join(format!("lc-denoise-{name}-{}.lcdn", std::process::id())) } diff --git a/crates/denoise-core/tests/product_heap.rs b/crates/denoise-core/tests/product_heap.rs new file mode 100644 index 000000000..40c170861 --- /dev/null +++ b/crates/denoise-core/tests/product_heap.rs @@ -0,0 +1,50 @@ +//! Peak heap of a `read_window` that must reject a product's strips (`crates/denoise-core/src/product.rs`), measured +//! with dhat's testing mode. The result is allocated only after the wanted strips' checksums are verified, so a cache +//! whose compressed bytes are damaged or absent fails without first reserving the (multi-megabyte) output. One test +//! per binary (dhat's profiler is global). + +use lightcraft_denoise_core::product::read_window; + +#[global_allocator] +static ALLOC: dhat::Alloc = dhat::Alloc; + +fn temp(name: &str) -> std::path::PathBuf { + std::env::temp_dir().join(format!("lc-denoise-{name}-{}.lcdn", std::process::id())) +} + +/// A 2000 × 1024 product of key `k` (a 24 MB result): its 8 strips each claim 8 bytes that do not match their +/// checksum, and the file is exactly the length its header and table declare. A 24 MB peak means the output was +/// reserved; the cap here bounds the header, the table and the strips' own bytes. +fn damaged_product() -> Vec { + let header = br#"{"width":2000,"height":1024,"strip":128,"key":"k"}"#; + const STRIP_LEN: usize = 8; + let mut bytes = Vec::from(b"LCDN".as_slice()); + bytes.extend_from_slice(&[1, 0, 0, 0]); + bytes.extend_from_slice(&(header.len() as u32).to_le_bytes()); + bytes.extend_from_slice(header); + let expected_len = 12 + header.len() + 8 * 8 + 8 * STRIP_LEN; + for _ in 0..8 { + // 8 claimed bytes whose recorded checksum does not match theirs (0xAA bytes do not have this CRC) + bytes.extend_from_slice(&(STRIP_LEN as u32).to_le_bytes()); + bytes.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]); + } + for _ in 0..8 { + bytes.extend_from_slice(&[0xAAu8; STRIP_LEN]); + } + assert_eq!(bytes.len(), expected_len, "the crafted file lies about its length"); + bytes +} + +#[test] +fn a_damaged_product_is_rejected_before_its_output_is_allocated() { + let bytes = damaged_product(); + let p = temp("damaged-product-heap"); + std::fs::write(&p, &bytes).unwrap(); + let _profiler = dhat::Profiler::builder().testing().build(); + let why = read_window(&p, "k", None, 1, None).err().unwrap().to_string(); + let peak = dhat::HeapStats::get().max_bytes; + let _ = std::fs::remove_file(p); + assert!(why.contains("damaged"), "{why}"); + eprintln!("peak heap {peak} bytes"); + assert!(peak < 4 << 20, "peak heap {peak} bytes: the output was reserved before the strips were checked"); +} diff --git a/crates/denoise/src/reference.rs b/crates/denoise/src/reference.rs index ef58b5b9c..488e9a7b7 100644 --- a/crates/denoise/src/reference.rs +++ b/crates/denoise/src/reference.rs @@ -4,6 +4,12 @@ use crate::net::{Net, NetError, Op, TensorId}; use crate::run::{Error, TileRunner}; +/// The tensors' size cap: the same one the real runner's buffers share (`crates/denoise/src/cpu.rs`). A hostile `Net` +/// only ever has to satisfy the final shape: a `ConvT2` doubles the sides and a `MaxPool` halves them, so a small +/// accepted graph can zoom its intermediates far out and back. Every tensor is counted here, and a run whose layers +/// would retain more than one real tile's whole working set is refused before the memory behind it is reserved. +const MAX_VALUES: usize = 128 * 1024 * 1024; + /// A tensor in `[channels][side][side]` order. struct T { channels: usize, @@ -11,12 +17,36 @@ struct T { data: Vec, } +/// A tensor's plain float count, in bounds. +fn count(channels: usize, side: usize) -> Result { + channels + .checked_mul(side) + .and_then(|n| n.checked_mul(side)) + .filter(|&n| n > 0 && n <= MAX_VALUES) + .ok_or_else(|| NetError::Invalid("a tensor has no size or is too large".into())) +} + +/// A zeroed tensor, keeping the memory the run keeps alive in bounds (`tensors` retains every layer's result). +fn tensor(channels: usize, side: usize, total: &mut usize) -> Result, NetError> { + let n = count(channels, side)?; + *total = (*total).saturating_add(n); + if *total > MAX_VALUES { + return Err(NetError::Invalid("the tile needs too much working memory".into())); + } + let mut data = Vec::new(); + data.try_reserve_exact(n).map_err(|_| NetError::Invalid("not enough memory for a reference tile".into()))?; + data.resize(n, 0.0); + Ok(data) +} + /// Run `net` on `input` (`in_channels` planes of `tile × tile`). pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> { if !net.fits_tile(tile) || input.len() != net.in_channels * tile * tile { return Err(NetError::Invalid("the tile does not fit the network".into())); } - let mut tensors: Vec = vec![T { channels: net.in_channels, side: tile, data: input.to_vec() }]; + let input = input.to_vec(); + let mut total = input.len(); + let mut tensors = vec![T { channels: net.in_channels, side: tile, data: input }]; let get = |tensors: &[T], id: TensorId| -> Result { if id < tensors.len() { Ok(id) } else { Err(NetError::Invalid("a layer reads a tensor that does not exist yet".into())) } }; @@ -27,7 +57,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> let b = c.src2.map(|s| get(&tensors, s)).transpose()?; let side = tensors[a].side; let pad = (c.k - 1) / 2; - let mut data = vec![0f32; c.cout * side * side]; + let mut data = tensor(c.cout, side, &mut total)?; // the input channels in order: the first source's, then the second's let planes: Vec<&[f32]> = std::iter::once(&tensors[a]).chain(b.map(|b| &tensors[b])).flat_map(|t| t.data.chunks(side * side)).collect(); @@ -56,7 +86,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> let a = get(&tensors, c.src)?; let side = tensors[a].side; let out_side = 2 * side; - let mut data = vec![0f32; c.cout * out_side * out_side]; + let mut data = tensor(c.cout, out_side, &mut total)?; for co in 0..c.cout { for y in 0..side { for x in 0..side { @@ -77,7 +107,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> Op::MaxPool2 { src } => { let a = get(&tensors, *src)?; let (channels, side) = (tensors[a].channels, tensors[a].side / 2); - let mut data = vec![0f32; channels * side * side]; + let mut data = tensor(channels, side, &mut total)?; for ch in 0..channels { for y in 0..side { for x in 0..side { @@ -92,7 +122,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> let a = get(&tensors, *src)?; let (channels, side) = (tensors[a].channels / 4, tensors[a].side); let out_side = 2 * side; - let mut data = vec![0f32; channels * out_side * out_side]; + let mut data = tensor(channels, out_side, &mut total)?; for ch in 0..channels { for y in 0..side { for x in 0..side { @@ -131,3 +161,39 @@ impl TileRunner for ReferenceRunner { run(&self.net, self.tile, input).map_err(|e| Error::Runtime(e.to_string())) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::net::ConvT2; + + fn convt(src: TensorId, cin: usize, cout: usize) -> Op { + Op::ConvT2(ConvT2 { src, cin, cout, weight: vec![0.1; cin * cout * 4], bias: vec![0.0; cout], leaky: None }) + } + + /// A graph that fits the final-shape check while zooming its intermediates out and back: 256 `ConvT2`s double + /// the sides (256 tiers), 255 `MaxPool`s halve them back to the layer above the input. + fn hostile() -> Net { + let mut ops = Vec::new(); + let mut last = 0usize; + for _ in 0..256 { + ops.push(convt(last, 1, 1)); + last += 1; + } + for _ in 0..255 { + ops.push(Op::MaxPool2 { src: last }); + last += 1; + } + Net::new(1, ops).unwrap() + } + + /// The zoomed-out layers' memory was never reserved: either one tensor or the retained whole (the two bounds + /// share the cap) says why the run stops. + #[test] + fn a_graph_that_zooms_its_intermediates_out_and_back_is_refused_before_their_memory_is_reserved() { + assert!(matches!( + run(&hostile(), 1, &[1.0]), + Err(NetError::Invalid(why)) if why.contains("too large") || why.contains("too much working memory") + )); + } +} diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 9caa8cdb0..0b203f795 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -39,7 +39,7 @@ thiserror = { workspace = true } log = { workspace = true } ab_glyph = { workspace = true } # Apply the font’s vertical substitutions and origins to Japanese watermarks. -harfrust = { version = "0.12", default-features = false, features = ["std"] } +harfrust = { version = "0.13", default-features = false, features = ["std"] } unicode-segmentation = "1.13" lightcraft-segment = { workspace = true, optional = true } diff --git a/crates/engine/src/camera_profiles.rs b/crates/engine/src/camera_profiles.rs index c7b6c437b..9e6d2094e 100644 --- a/crates/engine/src/camera_profiles.rs +++ b/crates/engine/src/camera_profiles.rs @@ -28,6 +28,7 @@ pub const BUNDLED: &[(&str, &str)] = &[ ("ILCA-99M2", include_str!("../../../assets/camera-profiles/ILCA-99M2.json")), ("ILCE-7CR", include_str!("../../../assets/camera-profiles/ILCE-7CR.json")), ("ILCE-7M4", include_str!("../../../assets/camera-profiles/ILCE-7M4.json")), + ("ILCE-7RM5", include_str!("../../../assets/camera-profiles/ILCE-7RM5.json")), ("NIKON D5300", include_str!("../../../assets/camera-profiles/NIKON_D5300.json")), ("NIKON Z 6_2", include_str!("../../../assets/camera-profiles/NIKON_Z_6_2.json")), ("NIKON Z 8", include_str!("../../../assets/camera-profiles/NIKON_Z_8.json")), diff --git a/crates/heif/src/census.rs b/crates/heif/src/census.rs new file mode 100644 index 000000000..6dbe035fc --- /dev/null +++ b/crates/heif/src/census.rs @@ -0,0 +1,122 @@ +//! Caps on the container's metadata boxes, counted before heic-rs touches the file. +//! +//! heic-rs 0.1.4 (pinned `=0.1.4`) has no aggregate work budget inside its container parsing: +//! every `ipma` association entry rescans the vector of already-seen item ids (quadratic in +//! the number of entries), and an `iloc` extent using construction method 2 reconstructs its +//! source item afresh for every extent that references it, with no shared cache. Legitimate +//! phone-photo metadata never comes near these caps — real corpora keep both well under a few +//! kilobytes — so anything above them is hostile or corrupt and is refused before the +//! countable CPU work happens. +//! +//! [`depth_boxes`] stops silently at structural blips: heic-rs's own parser reports those +//! better than this walk could, and an `ipma` box past the cap is caught wherever it parses. + +use heic_rs::boxes::BoxIter; + +use crate::Error; + +/// The largest `ipma` box (item ↔ property associations) payload accepted, 64 KiB. An entry +/// costs at least 3 bytes, so this bounds heic-rs's per-entry rescan of earlier item ids to +/// ~21 000 entries ≈ 220 million comparisons: long enough to be annoying, never minutes. +const MAX_IPMA_BYTES: usize = 64 << 10; + +/// The largest `iloc` box (item locations) payload accepted, 64 KiB. An extent costs at least +/// 12 bytes, so this bounds both the parse itself and how many extents a +/// construction-method-2 reconstruction chain can visit. +const MAX_ILOC_BYTES: usize = 64 << 10; + +/// Falls through unless the container declares more `ipma` or `iloc` payload than a +/// legitimate phone photo ever does. +pub fn cap(bytes: &[u8]) -> Result<(), Error> { + let mut c = Census::default(); + depth_boxes(&mut c, BoxIter::new(bytes), 0); + if c.ipma > MAX_IPMA_BYTES { + return Err(Error::Limit(format!("the image carries {} bytes of property associations, over the {MAX_IPMA_BYTES}-byte cap", c.ipma))); + } + if c.iloc > MAX_ILOC_BYTES { + return Err(Error::Limit(format!("the image carries {} bytes of item locations, over the {MAX_ILOC_BYTES}-byte cap", c.iloc))); + } + Ok(()) +} + +/// The metadata-box payload sizes seen so far. +#[derive(Default)] +struct Census { + ipma: usize, + iloc: usize, +} + +/// Walks boxes, adding the payload sizes of those named in [`Census`]. +/// +/// Only `meta` and `iprp` (the two nesting boxes the metadata is built from) are descended +/// into, no deeper than that depth, so a nested-box trick can't hide a box from the count. +fn depth_boxes(c: &mut Census, boxes: BoxIter<'_>, depth: u32) { + if depth > 3 { + return; + } + for b in boxes { + // A structural error stops the walk; `heic_rs::probe` reports it properly afterwards. + let Ok(b) = b else { return }; + if b.is(b"ipma") { + c.ipma = c.ipma.saturating_add(b.payload.len()); + } else if b.is(b"iloc") { + c.iloc = c.iloc.saturating_add(b.payload.len()); + } else if b.is(b"meta") { + // `meta` is a FullBox: version and flags sit between its header and its children. + if b.payload.len() >= 4 { + depth_boxes(c, BoxIter::new(&b.payload[4..]), depth + 1); + } + } else if b.is(b"iprp") { + depth_boxes(c, b.children(), depth + 1); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Assembles ftyp + meta(version/flags + iprp(ipco, ipma)) by hand; only the structure + /// the census walks matters, no item is ever defined. + fn file_with(ipma_payload: &[u8]) -> Vec { + let mut out = b"\0\0\0\x18ftypheic\0\0\0\0mif1heic".to_vec(); + let mut iprp = Vec::new(); + iprp.extend(box_of(b"ipco", b"")); + iprp.extend(box_of(b"ipma", ipma_payload)); + let mut meta = vec![0u8, 0, 0, 0]; // version 0, flags 0 + meta.extend(box_of(b"iprp", &iprp)); + out.extend(box_of(b"meta", &meta)); + out + } + + fn box_of(kind: &[u8; 4], body: &[u8]) -> Vec { + let mut out = Vec::with_capacity(8 + body.len()); + let size = u32::try_from(8 + body.len()).expect("test box fits in u32"); + out.extend_from_slice(&size.to_be_bytes()); + out.extend_from_slice(kind); + out.extend_from_slice(body); + out + } + + #[test] + fn an_oversized_ipma_is_refused() { + // 40 000 entries of 6 bytes: 240 KiB of ipma, far over the cap, and quadratic if + // parsed (each entry rescans the earlier item ids in heic-rs 0.1.4). + let mut ipma = Vec::new(); + for _ in 0..40_000u32 { + ipma.extend_from_slice(&[0u8, 0, 0, 0, 0, 0]); + } + let bytes = file_with(&ipma); + let r = crate::probe(&bytes).map(|_| ()); + assert!(matches!(r, Err(Error::Limit(ref m)) if m.contains("associations")), "{r:?}"); + } + + #[test] + fn a_undersized_ipma_is_untouched_by_the_caps() { + // The same shape with a normal ipma (two entries): the caps pass, and whatever error + // follows must not come from them. + let bytes = file_with(&[0, 0, 4, 1, 1, 2, 0, 0]); + let r = crate::probe(&bytes); + assert!(!matches!(r, Err(Error::Limit(ref m)) if m.contains("cap")), "{r:?}"); + } +} diff --git a/crates/heif/src/lib.rs b/crates/heif/src/lib.rs index 21b9cf647..5b20e31d7 100644 --- a/crates/heif/src/lib.rs +++ b/crates/heif/src/lib.rs @@ -19,6 +19,7 @@ #![forbid(unsafe_code)] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +mod census; mod orient; /// Lossless synthetic HEIF files for tests, here and in the crates above (feature `testdata`). #[cfg(any(test, feature = "testdata"))] @@ -149,20 +150,50 @@ pub fn probe(bytes: &[u8]) -> Result { } fn probe_unguarded(bytes: &[u8]) -> Result { - let info = heic_rs::probe(bytes).map_err(|e| match e { - // No `meta` box: no still image, only a `moov` image sequence (a video track). + census::cap(bytes)?; + // No `meta` box: no still image, only a `moov` image sequence (a video track). + let open_error = |e| match e { heic_rs::Error::MissingBox("meta") => Error::Unsupported(SEQUENCE), e => err(e), - })?; - let ctx = Context::open(bytes).map_err(err)?; + }; + let ctx = Context::open(bytes).map_err(open_error)?; let id = ctx.meta.primary; - let coded = match ctx.grid(id).map_err(err)? { + // heic-rs 0.1.4 caps the size of what it assembles, not the work: an item built from other + // items (`iloc` construction method 2) is rebuilt once per extent, so a chain of such items + // multiplies at every level while each result stays small. Count those visits from the + // `iloc` table before reconstructing anything: the primary item first (`heic_rs::probe` and + // `Context::grid` both rebuild its descriptor), then its alpha plane, then every tile + // reference, repeats included. + let mut visits = 0_usize; + item_visits(&ctx, id, 0, &mut visits)?; + let info = heic_rs::probe(bytes).map_err(open_error)?; + if let Some(alpha) = ctx.alpha_item(id).map_err(err)? { + item_visits(&ctx, alpha, 0, &mut visits)?; + } + let mut coded = match ctx.grid(id).map_err(err)? { Some((_, tiles)) => tiles, None => vec![id], }; + for &item in coded.iter().filter(|&&item| item != id) { + item_visits(&ctx, item, 0, &mut visits)?; + } + // A `dimg` listing may name the same tile more than once, and reconstructable items are + // moreover rebuilt for every extent that references them in heic-rs 0.1.4. For the size + // check each unique item counts once: the total reconstruction (all of it assembled from + // `bytes` by heic-rs) is held below one copy of the file, above which a repeat or chain + // wants more bytes than exist. Order doesn't matter here: this only checks that every + // tile's bytes lie in the file. + coded.sort_unstable(); + coded.dedup(); + let mut reconstructed = 0_usize; for item in coded { - // Borrowed from `bytes` unless split across extents: cheap. - ctx.item_data(item).map_err(err)?; + reconstructed = reconstructed.saturating_add(ctx.item_data(item).map_err(err)?.len()); + if reconstructed > bytes.len() { + return Err(Error::Limit(format!( + "the container holds {reconstructed} bytes of item data, over the {} bytes of the file itself", + bytes.len() + ))); + } } Ok(Info { width: info.width, @@ -174,6 +205,36 @@ fn probe_unguarded(bytes: &[u8]) -> Result { }) } +/// Most extent visits heic-rs may spend reconstructing the primary image's coded items; real +/// files need one per tile (plus a few for the rare derived item). +const MAX_ITEM_VISITS: usize = 4096; +/// Deepest chain of items built from items we follow (heic-rs refuses beyond eight itself). +const MAX_ITEM_CHAIN: u32 = 8; + +/// Adds to `visits` the extents heic-rs would read to reconstruct `id`, following items built +/// from other items, and fails once that passes [`MAX_ITEM_VISITS`] or the chain is too deep. +fn item_visits(ctx: &Context<'_>, id: u32, depth: u32, visits: &mut usize) -> Result<(), Error> { + use heic_rs::meta::iloc::Construction; + if depth > MAX_ITEM_CHAIN { + return Err(Error::Limit(format!("items built from items nest deeper than {MAX_ITEM_CHAIN} levels"))); + } + let Some(loc) = ctx.meta.location(id) else { return Ok(()) }; + *visits = visits.saturating_add(loc.extents.len().max(1)); + if *visits > MAX_ITEM_VISITS { + return Err(Error::Limit(format!("reconstructing the image takes over {MAX_ITEM_VISITS} item extent reads"))); + } + if loc.construction == Construction::Item { + for e in &loc.extents { + if let Ok(source) = u32::try_from(e.index) + && source != id + { + item_visits(ctx, source, depth + 1, visits)?; + } + } + } + Ok(()) +} + /// Decodes the primary image, with its metadata. pub fn decode(bytes: &[u8], options: &Options) -> Result { guarded(|| decode_unguarded(bytes, options)) @@ -188,7 +249,13 @@ pub fn decode_thumbnail(bytes: &[u8], min_edge: u32, options: &Options) -> Resul let ctx = Context::open(bytes).map_err(err)?; let primary = ctx.meta.primary; let mut best: Option<(u32, u32)> = None; + // A thumbnail whose own reconstruction would cost too much is not a candidate; the visits + // of all the candidates share one budget. + let mut candidate_visits = 0_usize; for r in ctx.meta.refs.iter().filter(|r| r.kind == heic_rs::meta::iref::RefKind::Thmb && r.to.contains(&primary)) { + if item_visits(&ctx, r.from, 0, &mut candidate_visits).is_err() { + continue; + } let Ok(p) = ctx.props(r.from) else { continue }; let Ok((w, h)) = ctx.coded_size(r.from, &p) else { continue }; let edge = w.max(h); @@ -197,6 +264,15 @@ pub fn decode_thumbnail(bytes: &[u8], min_edge: u32, options: &Options) -> Resul } } let Some((id, _)) = best else { return Ok(None) }; + // The same pre-scan as `probe`: the thumbnail's own descriptor and every tile reference + // are counted before heic-rs rebuilds any of them. + let mut visits = 0_usize; + item_visits(&ctx, id, 0, &mut visits)?; + if let Some((_, tiles)) = ctx.grid(id).map_err(err)? { + for &tile in &tiles { + item_visits(&ctx, tile, 0, &mut visits)?; + } + } let mut decoded = decode_item(&ctx, id, options, false)?; if decoded.icc.is_none() { decoded.icc = item_icc(&ctx, primary); @@ -232,6 +308,13 @@ fn decode_unguarded(bytes: &[u8], options: &Options) -> Result { /// `with_alpha` and the item has an alpha auxiliary image. fn decode_item(ctx: &Context<'_>, id: u32, options: &Options, with_alpha: bool) -> Result { let p = ctx.props(id).map_err(err)?; + // heic-rs 0.1.4 keeps every associated transform and its [`orient::apply`] allocates the + // full picture again for each: a file could chain the repetition and multiply decode CPU + // without changing any checked size. Real files carry at most one `clap`, `irot` and + // `imir`, so refuse anything approaching [`MAX_TRANSFORMS`] (CodeRabbit #44). + if p.transforms.len() > MAX_TRANSFORMS { + return Err(Error::Limit(format!("the image carries {} transform properties, over the {MAX_TRANSFORMS}-property cap", p.transforms.len()))); + } let limit = options.max_pixels; // The declared sizes are refused before anything is decoded or allocated. let (cw, ch) = ctx.coded_size(id, &p).map_err(err)?; @@ -245,6 +328,14 @@ fn decode_item(ctx: &Context<'_>, id: u32, options: &Options, with_alpha: bool) let signal = ycc::Signal { matrix: colour.matrix, full_range: colour.full_range }; let alpha_id = if with_alpha { ctx.alpha_item(id).map_err(err)? } else { None }; let (w, h) = planes.size(); + // A grid's canvas dimensions come from its payload, not the checked `ispe` (CodeRabbit + // #48): check the real one, before [`ycc::to_rgb16`] allocates for it. + match (u32::try_from(w), u32::try_from(h)) { + (Ok(wu), Ok(hu)) => { + heic_rs::image::check_pixels(wu, hu, limit).map_err(err)?; + } + _ => return Err(Error::Limit(format!("the image canvas is {w}x{h}, beyond the {limit}-pixel limit"))), + } let mode = if options.apply_transforms && orient::odd_crop(w, h, &p.transforms)? { ycc::Upsampling::Bilinear } else { ycc::Upsampling::Nearest }; let mut data = ycc::to_rgb16(&planes, signal, alpha_id.is_some(), mode) .ok_or_else(|| Error::Malformed("the decoded pictures don't cover the image".into()))?; @@ -281,6 +372,10 @@ fn decode_item(ctx: &Context<'_>, id: u32, options: &Options, with_alpha: bool) /// cover its canvas rounded up to whole tiles, so legitimate grids sit near 1. const GRID_BUDGET: u64 = 2; +/// The most transform properties (`clap`/`irot`/`imir`) one item may carry. Real files have at +/// most one of each, so anything approaching this is a reordered or hostile association list. +const MAX_TRANSFORMS: usize = 12; + /// The decoded pictures of an item: one, or a grid's tiles in `dimg` order. struct Coded<'a> { frames: Vec, @@ -370,8 +465,12 @@ fn colour(p: &ItemProps<'_>, coded: &ItemProps<'_>) -> Colour { /// metadata item never fails a decode whose pixels came out fine. fn metadata(ctx: &Context<'_>) -> (Option>, Option) { let id = ctx.meta.primary; - let exif = ctx.exif(id).ok().flatten().map(<[u8]>::to_vec); - let xmp = ctx.xmp_item(id).and_then(|x| ctx.item_data(x).ok()).and_then(|d| match d { + // Metadata is best effort, so an item that would cost too much to rebuild is skipped, not an error. + let mut visits = 0_usize; + let cheap = |item: Option, visits: &mut usize| item.is_none_or(|item| item_visits(ctx, item, 0, visits).is_ok()); + let exif = if cheap(ctx.exif_item(id), &mut visits) { ctx.exif(id).ok().flatten().map(<[u8]>::to_vec) } else { None }; + let xmp_item = ctx.xmp_item(id).filter(|&x| cheap(Some(x), &mut visits)); + let xmp = xmp_item.and_then(|x| ctx.item_data(x).ok()).and_then(|d| match d { Cow::Borrowed(b) => String::from_utf8(b.to_vec()).ok(), Cow::Owned(b) => String::from_utf8(b).ok(), }); diff --git a/crates/heif/src/tests_synthetic.rs b/crates/heif/src/tests_synthetic.rs index 3259dc96d..1286ff82a 100644 --- a/crates/heif/src/tests_synthetic.rs +++ b/crates/heif/src/tests_synthetic.rs @@ -185,3 +185,16 @@ fn truncated_and_corrupted_files_are_errors_not_panics() { let _ = decode_thumbnail(&b, 1, &Options::default()); } } + +/// CodeRabbit #44: heic-rs keeps every associated transform and each one re-allocates the +/// whole picture, so 13 of them are over the [`crate::MAX_TRANSFORMS`] cap and refused. +#[test] +fn too_many_transform_properties_are_refused() { + let bytes = build(&Spec { props: std::iter::repeat_n(Prop::Irot(0), 13).collect(), ..Spec::new(32, 32, &grey) }); + let r = decode(&bytes, &Options::default()); + assert!(matches!(r, Err(Error::Limit(ref m)) if m.contains("transform")), "{r:?}"); + // At the cap they're not: 12 identity rotations decode normally. + let bytes = build(&Spec { props: std::iter::repeat_n(Prop::Irot(0), 12).collect(), ..Spec::new(32, 32, &grey) }); + let d = decode(&bytes, &Options::default()).unwrap(); + assert_eq!((d.width, d.height), (32, 32)); +} diff --git a/crates/heif/src/ycc.rs b/crates/heif/src/ycc.rs index a0429b453..384d25f2a 100644 --- a/crates/heif/src/ycc.rs +++ b/crates/heif/src/ycc.rs @@ -161,6 +161,13 @@ pub fn to_rgb16(planes: &Planes<'_>, signal: Signal, alpha: bool, mode: Upsampli if !(8..=16).contains(&bd) || w == 0 || h == 0 { return None; } + // The default caller limit is 2²8 pixels, at most four channels per pixel, so the largest + // legitimate `w·h·ch` is 2²30 samples. Anything more is a malformed or hostile canvas the + // decode path would have refused before calling here (CodeRabbit #48): refuse rather than + // ask for a multi-gigabuffer (or worse, over usize). + if w.checked_mul(h)?.checked_mul(ch)? > 1 << 30 { + return None; + } let max = ((1u32 << bd) - 1) as f32; let half = (1u32 << (bd - 1)) as f32; let scale = (1u32 << (bd - 8)) as f32; @@ -323,4 +330,23 @@ mod tests { .is_none() ); } + + /// CodeRabbit #48: a plane claiming 500 000 × 500 000 pixels must be refused without ever + /// allocating for it (before the cap this vec was never built; upper-bounding allocation + /// would have been a 7.5-gibabuffer or a panic instead of an error). + #[test] + fn an_oversized_canvas_is_refused_without_allocation() { + let f = Frame { + width: 500_000, + height: 500_000, + bit_depth: 8, + chroma: ChromaFormat::Yuv420, + y: vec![128], + cb: vec![128; 4], + cr: vec![128; 4], + y_stride: 0, + c_stride: 0, + }; + assert!(to_rgb16(&Planes::Picture(&f), Signal { matrix: 6, full_range: true }, false, Upsampling::Nearest).is_none()); + } } diff --git a/crates/merge/src/lib.rs b/crates/merge/src/lib.rs index b5ccd4bb8..ca7f91821 100644 --- a/crates/merge/src/lib.rs +++ b/crates/merge/src/lib.rs @@ -34,6 +34,8 @@ pub use pano::{PanoOptions, PanoResult, Projection, stitch}; pub enum MergeError { #[error("{0} photo(s) selected; at least {1} are needed")] TooFew(usize, usize), + #[error("{0} photo(s) selected; a stitch of more than {1} is refused: matching every pair would take far too long")] + TooMany(usize, usize), #[error("{0}")] Mismatch(String), #[error("could not read a photo: {0}")] diff --git a/crates/merge/src/pano/camera.rs b/crates/merge/src/pano/camera.rs index 7625170c3..4589f88db 100644 --- a/crates/merge/src/pano/camera.rs +++ b/crates/merge/src/pano/camera.rs @@ -61,10 +61,12 @@ pub struct PairMatch { /// Match every pair of images. `feats[k]` are in work coordinates scaled by `scales[k]`. pub fn match_pairs(feats: &[Features], scales: &[f64]) -> Vec { let n = feats.len(); - let pairs: Vec<(usize, usize)> = (0..n).flat_map(|i| (i + 1..n).map(move |j| (i, j))).collect(); - let mut out: Vec = pairs - .par_iter() - .filter_map(|&(i, j)| { + // the pairs are generated lazily: materialising all n(n−1)/2 up front is quadratic memory, including + // when every comparison comes up empty (no usable features) + let mut out: Vec = (0..n) + .into_par_iter() + .flat_map_iter(|i| ((i + 1)..n).map(move |j| (i, j))) + .filter_map(|(i, j)| { let m = features::match_features(&feats[i], &feats[j], 0.8); if m.len() < 12 { return None; diff --git a/crates/merge/src/pano/mod.rs b/crates/merge/src/pano/mod.rs index 5ce439681..ba7e1c63f 100644 --- a/crates/merge/src/pano/mod.rs +++ b/crates/merge/src/pano/mod.rs @@ -53,6 +53,11 @@ impl Default for PanoOptions { /// The largest panorama, in megapixels (Lightroom's limit too); a bigger one is scaled down. pub const MAX_MEGAPIXELS: f64 = 512.0; +/// Most frames one stitch accepts: pair matching compares every pair of frames (n(n−1)/2 runs), so an +/// unbounded frame count is quadratic CPU as well as quadratic memory while the frames load. A panorama +/// with more than [`MAX_FRAMES`] shots is refused with an error, not attempted. +pub const MAX_FRAMES: usize = 256; + /// The longest edge of a panorama, in pixels (Lightroom's limit too); a longer one is scaled down. pub const MAX_EDGE: f64 = 65_000.0; @@ -224,6 +229,9 @@ pub fn stitch(mut frames: Vec, opts: &PanoOptions, progress: &Progress) - if n < 2 { return Err(MergeError::TooFew(n, 2)); } + if n > MAX_FRAMES { + return Err(MergeError::TooMany(n, MAX_FRAMES)); + } check(progress, 0.01, "Finding features")?; let (cams, rms_before, rms) = register(&frames, progress)?; let used: Vec = (0..n).filter(|&i| cams[i].is_some()).collect(); diff --git a/crates/merge/tests/it/pano.rs b/crates/merge/tests/it/pano.rs index fdb736524..aeecde64c 100644 --- a/crates/merge/tests/it/pano.rs +++ b/crates/merge/tests/it/pano.rs @@ -246,6 +246,18 @@ fn unrelated_photos_do_not_stitch() { assert!(stitch(frames, &PanoOptions::default(), &no_progress).is_err()); } +/// Pair matching compares every pair of frames before anything is known about them: an unbounded +/// photo count is quadratic CPU and memory (issue #1519). More than `MAX_FRAMES` is refused at once, +/// before any frame is worked on. +#[test] +fn a_stitch_of_more_than_the_frame_cap_is_refused_up_front() { + let frames: Vec = (0..lightcraft_merge::pano::MAX_FRAMES + 1) + .map(|i| Frame::from_linear_rec2020(Rgb32f::from_fn(8, 8, |_, _| [(i % 255) as f32 / 255.0, 0.0, 0.0]), None)) + .collect(); + let err = stitch(frames, &PanoOptions::default(), &no_progress).unwrap_err(); + assert!(err.to_string().contains("refused"), "the frame cap errors, it does not begin the quadratic match: {err}"); +} + /// Issue #1455: a panorama came out at 40 MP whatever its photos' resolution. It now keeps their /// own resolution unless a size is asked for (up to Lightroom's 512 MP); a size scales it down, /// and a NaN one (from an agent) is no size at all, not a 0.1 MP thumbnail. diff --git a/crates/meta/src/exif.rs b/crates/meta/src/exif.rs index f24bb5f2f..adcba0f80 100644 --- a/crates/meta/src/exif.rs +++ b/crates/meta/src/exif.rs @@ -1,7 +1,7 @@ //! Exif / TIFF tags → [`Metadata`] (CIPA DC-008 Exif 2.32, TIFF 6.0, DNG 1.7 tag semantics). use crate::{DateTime, Flash, Gps, Metadata, Orientation}; -use lightcraft_tiff::{Ifd, Tiff, TiffError, tags as t}; +use lightcraft_tiff::{Ifd, ParseOptions, Tiff, TiffError, tags as t}; /// Strip an `Exif\0\0` (or `Exif\0\xff`) prefix as found in JPEG APP1 and some WebP `EXIF` chunks. pub fn strip_exif_header(b: &[u8]) -> &[u8] { @@ -16,8 +16,16 @@ pub fn read_exif(bytes: &[u8]) -> Metadata { /// Like [`read_exif`] but reports a structurally invalid TIFF header / IFD0. pub fn try_read_exif(bytes: &[u8]) -> Result { + try_read_exif_with(bytes, &ParseOptions::default()) +} + +/// [`try_read_exif`] with the caller's parser options (its value allocation budget in particular). +/// A decoder working from a sparse buffer — only selected ranges of the logical file are actually +/// loaded, the rest is zeros — must not size its allocations by the logical file length, which +/// unrelated bytes of the file can inflate at no cost to the attacker. +pub fn try_read_exif_with(bytes: &[u8], opts: &ParseOptions) -> Result { let data = strip_exif_header(bytes); - let tiff = Tiff::parse(data)?; + let tiff = Tiff::parse_with(data, opts)?; let mut m = from_tiff(&tiff); // Panasonic writes no LensModel, only the name in its maker note (and in a RW2 that note is not in the raw's own Exif) if m.lens_model.is_none() { diff --git a/crates/meta/src/focus_read.rs b/crates/meta/src/focus_read.rs index 5a5d349d1..65adbee75 100644 --- a/crates/meta/src/focus_read.rs +++ b/crates/meta/src/focus_read.rs @@ -171,7 +171,15 @@ fn tiff(r: &mut Reader, head: Vec) -> io::Result> { loaded.insert(0, head.len() as u64); for _ in 0..MAX_ROUNDS { match plan(&buf, &loaded) { - Step::Ready => return Ok(crate::try_read_exif(&buf).ok().and_then(|m| m.focus)), + Step::Ready => { + // The decoder's allocation budget follows the bytes actually fetched, not the logical + // file length: only the loaded ranges are data, and an unrelated entry that claims a + // huge value must be cut by what leaving it cost, not by what the buffer claims to hold. + // The same 8 × / + 1 MiB shape the general TIFF reader uses, over `reader.bytes` instead. + let opts = + lightcraft_tiff::ParseOptions { value_budget: Some(r.bytes.saturating_mul(8).saturating_add(1 << 20)), ..Default::default() }; + return Ok(crate::try_read_exif_with(&buf, &opts).ok().and_then(|m| m.focus)); + } Step::Nothing => return Ok(None), Step::Need(ranges) => { for (a, b) in ranges { diff --git a/crates/meta/src/lib.rs b/crates/meta/src/lib.rs index baebbfe0f..46c7a1043 100644 --- a/crates/meta/src/lib.rs +++ b/crates/meta/src/lib.rs @@ -27,7 +27,7 @@ mod xmp_merge; pub use container::{Embedded, embedded, jpeg_segments, png_chunks, webp_chunks}; pub use datetime::DateTime; -pub use exif::{from_tiff, read_exif, strip_exif_header, try_read_exif, write_exif}; +pub use exif::{from_tiff, read_exif, strip_exif_header, try_read_exif, try_read_exif_with, write_exif}; pub use focus::FocusPoint; pub use gpx::{GpxError, Match, TrackPoint, Tracklog, parse_gpx}; pub use iptc::parse_iptc; diff --git a/crates/meta/src/xmp.rs b/crates/meta/src/xmp.rs index 2dbf59482..ac0a8808a 100644 --- a/crates/meta/src/xmp.rs +++ b/crates/meta/src/xmp.rs @@ -154,6 +154,11 @@ const MAX_NODES: usize = 200_000; // Expanding a long namespace URI on many short names must not amplify a small packet without bound. pub(crate) const MAX_OPAQUE_NAME_BYTES: usize = 8 * 1024 * 1024; +/// Most text a converter's flattened property keys may add up to. Keys repeat their whole ancestor +/// path (`parent/field`), so a document with a long nested name and a wide child fan-out otherwise +/// retains its prefix once per property: node limits alone cannot bound that duplication. +const MAX_KEY_BYTES: usize = 1 << 20; + /// Names canonicalised by namespace URI (see [`NAMESPACES`]); conflicting foreign prefixes are opaque. pub(crate) fn canonical_name(qname: &str, scopes: &[Vec<(String, String)>], opaque_budget: &mut usize) -> Result { canonical(qname, scopes, opaque_budget) @@ -332,18 +337,29 @@ fn is_rdf_meta_attr(k: &str) -> bool { matches!(k, "rdf:about" | "rdf:ID" | "rdf:nodeID" | "xml:lang" | "rdf:parseType" | "rdf:resource" | "rdf:datatype") || k.starts_with("xmlns") } -fn collect_description(d: &Node, prefix: &str, props: &mut BTreeMap>) { +fn collect_description(d: &Node, prefix: &str, props: &mut BTreeMap>, budget: &mut usize) { for (k, v) in &d.attrs { - if !is_rdf_meta_attr(k) { + if !is_rdf_meta_attr(k) && take_key_bytes(&format!("{prefix}{k}"), budget) { props.entry(format!("{prefix}{k}")).or_default().push(v.clone()); } } for c in &d.children { - collect_property(c, prefix, props); + collect_property(c, prefix, props, budget); } } -fn collect_property(p: &Node, prefix: &str, props: &mut BTreeMap>) { +/// Charge one flattened key's length to the key budget; `false` when the budget is spent (the +/// property is then left out, like other elements a limit has cut). +fn take_key_bytes(key: &str, budget: &mut usize) -> bool { + let n = key.len(); + if *budget < n { + return false; + } + *budget -= n; + true +} + +fn collect_property(p: &Node, prefix: &str, props: &mut BTreeMap>, budget: &mut usize) { let key = format!("{prefix}{}", p.name); if let Some(arr) = p.children.iter().find(|c| matches!(c.name.as_str(), "rdf:Seq" | "rdf:Bag" | "rdf:Alt")) { let mut items: Vec<(bool, String)> = arr @@ -357,29 +373,36 @@ fn collect_property(p: &Node, prefix: &str, props: &mut BTreeMap = p.attrs.iter().filter(|(k, _)| !is_rdf_meta_attr(k)).collect(); if !field_attrs.is_empty() && p.children.is_empty() && p.text.trim().is_empty() { for (k, v) in field_attrs { - props.entry(format!("{struct_prefix}{k}")).or_default().push(v.clone()); + let full = format!("{struct_prefix}{k}"); + if take_key_bytes(&full, budget) { + props.entry(full).or_default().push(v.clone()); + } } return; } let v = p.attr("rdf:resource").map(str::to_string).unwrap_or_else(|| p.text.trim().to_string()); - props.entry(key).or_default().push(v); + if take_key_bytes(&key, budget) { + props.entry(key).or_default().push(v); + } } fn find_descriptions<'a>(n: &'a Node, out: &mut Vec<&'a Node>) { @@ -497,8 +520,10 @@ pub fn parse_xmp(s: &str) -> Result { find_descriptions(&tree, &mut descs); let mut props = BTreeMap::new(); let mut values = BTreeMap::new(); + // One budget for the whole document: several descriptions cannot each spend a fresh one. + let mut key_budget = MAX_KEY_BYTES; for d in descs { - collect_description(d, "", &mut props); + collect_description(d, "", &mut props, &mut key_budget); if let XmpValue::Struct(m) = node_struct(d) { values.extend(m); } @@ -1171,4 +1196,32 @@ mod tests { assert_eq!(entity("#x41"), Some('A')); assert_eq!(entity("#9"), Some('\t')); } + + /// Flattened keys repeat their whole ancestor path, so a long nested name with a wide child + /// fan-out would otherwise retain its prefix once per leaf: the cumulative key budget stops it + /// (a 65 KB document with 3000 leaves must not retain ~2.4 MB of key text). + #[test] + fn flattened_keys_stay_within_the_cumulative_budget() { + let level = "n".repeat(200); + let mut doc = String::from( + "", + ); + for _ in 0..4 { + doc.push_str(&format!("")); + } + // each leaf has a name of its own, so each one adds a key + for i in 0..3000 { + doc.push_str(&format!("")); + } + doc.push_str(""); + for _ in 0..4 { + doc.push_str(&format!("")); + } + doc.push_str(""); + let d = parse_xmp(&doc).unwrap(); + let total: usize = d.properties.keys().map(String::len).sum(); + assert!(total <= MAX_KEY_BYTES, "flattened-key text {total} is over the {MAX_KEY_BYTES} budget"); + assert!(d.properties.len() < 3000, "keys stop being added once the budget is spent"); + assert!(!d.properties.is_empty(), "properties parsed before the budget are kept"); + } } diff --git a/crates/raw/src/dngwrite.rs b/crates/raw/src/dngwrite.rs index d7ba42776..fae6015b6 100644 --- a/crates/raw/src/dngwrite.rs +++ b/crates/raw/src/dngwrite.rs @@ -94,6 +94,17 @@ pub const SOFTWARE: &str = "LightCraft DNG 2"; /// (3.9 s vs 0.55 s on 4 cores) to save 3 % of the file (266 vs 275 MB); 5–12 % on raw and rendered DNGs. const DEFLATE_LEVEL: u8 = 1; +/// Largest Deflate tile edge the writer pads to (`tile` is caller-supplied in [`DngCompression::Deflate`]): +/// square-tile buffers grow with the square, so an unclamped edge lets a request pad a tiny valid image into +/// a multi-gigabyte buffer and billions of edge-replicate iterations. 2048² samples (≤ 24 MB of f32) is far +/// above the 256 default and every real tile this writer makes. +const MAX_DEFLATE_TILE: u32 = 2048; + +/// `tile` aligned down to a multiple of 16 and clamped to [`MAX_DEFLATE_TILE`]. +fn deflate_tile(tile: u32) -> usize { + (tile.max(16) & !15).min(MAX_DEFLATE_TILE) as usize +} + /// IEEE 754 binary32 → binary16 (round to nearest even; overflow → ±inf, NaN kept). pub fn f32_to_f16(v: f32) -> u16 { let b = v.to_bits(); @@ -318,7 +329,7 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result> { match (&raw.data, opts.compression) { (RawData::F32(v), DngCompression::Deflate { tile, half }) => { - let tile = (tile.max(16) & !15) as usize; + let tile = deflate_tile(tile); let bp = if half { 2 } else { 4 }; ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![(bp * 8) as u16; cpp])); ifd.set(t::SAMPLE_FORMAT, Value::Short(vec![3; cpp])); @@ -417,7 +428,7 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result> { ifd.set_image(ImageData::Strips { rows_per_strip: rows as u32, strips }); } DngCompression::Deflate { tile, .. } => { - let tile = (tile.max(16) & !15) as usize; + let tile = deflate_tile(tile); ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![16; cpp])); ifd.set(t::COMPRESSION, Value::Short(vec![compression::ADOBE_DEFLATE])); ifd.set(t::PREDICTOR, Value::Short(vec![2])); @@ -546,3 +557,45 @@ pub fn write_rendered_dng( let order = ByteOrder::Little; write_dng(&raw, &DngWriteOptions { compression: DngCompression::Deflate { tile: 256, half: true }, order, xmp }) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{BlackLevel, OpcodeLists, RawData, RawFormat, RawImage, Rect}; + + /// Deflate tiles of `tile × tile` pad into a square buffer that grows with the square: a + /// caller-supplied edge of `u32::MAX` would make the writer pad an 8 × 8 image into billions of + /// edge-replicate iterations and a multi-gigabyte buffer. The clamp keeps the bounded shape — same + /// file as requesting the cap exactly, and a DNG either way. + #[test] + fn a_hostile_deflate_tile_edge_is_clamped() { + let (w, h) = (8usize, 8usize); + let full = Rect::new(0, 0, w, h); + let raw = RawImage { + format: RawFormat::Dng, + width: w, + height: h, + cpp: 1, + data: RawData::U16((0..64).map(|i| i as u16 * 129).collect()), + cfa: None, + bits: 12, + black: BlackLevel::uniform(0.0), + white: vec![4095.0], + active_area: full, + crop: full, + orientation: lightcraft_geom::Orientation::Normal, + color: crate::ColorData::default(), + wb_multipliers: None, + linearized: false, + pixel_aspect: 1.0, + opcodes: OpcodeLists::default(), + metadata: Default::default(), + lens_metadata: Default::default(), + }; + let opts = |tile: u32| DngWriteOptions { compression: DngCompression::Deflate { tile, half: false }, ..DngWriteOptions::default() }; + let hostile = write_dng(&raw, &opts(u32::MAX)).expect("the clamped tile writes"); + assert_eq!(hostile, write_dng(&raw, &opts(2048)).expect("the cap itself writes"), "u32::MAX is clamped to the cap"); + assert_ne!(hostile, write_dng(&raw, &opts(256)).expect("a normal tile writes"), "the clamp is not a no-op"); + assert_eq!(crate::probe(&hostile), Some(RawFormat::Dng), "the clamped output is still a DNG"); + } +} diff --git a/crates/raw/src/opcodes.rs b/crates/raw/src/opcodes.rs index 6cf224984..d5a3ffd1e 100644 --- a/crates/raw/src/opcodes.rs +++ b/crates/raw/src/opcodes.rs @@ -656,18 +656,38 @@ fn fix_pixels(buf: &mut [f32], w: usize, h: usize, cpp: usize, cfa: Option<&Cfa> } } +/// Most full-image passes of one `OpcodeList3`: every warp record is a full-image allocation and resample +/// pass and every bad-pixel record a full flatten-and-rebuild, and a hostile record may repeat either +/// thousands of times. Real DNGs carry at most a couple; passes past the cap are skipped. +const MAX_LIST3_PASSES: usize = 8; + /// Apply `OpcodeList3` (on the demosaiced RGB image, values normalised to [0, 1]). pub fn apply_list3(list: &[Opcode], img: &mut Rgb32f) { if list.is_empty() { return; } let (w, h) = (img.width, img.height); + let mut passes = 0usize; for op in applied_ops(list) { match op { - Opcode::WarpRectilinear { planes, center } => *img = warp_rectilinear(img, planes, *center), - Opcode::WarpRectilinear2 { planes, center, reciprocal, .. } => *img = warp_rectilinear2(img, planes, *center, *reciprocal), + Opcode::WarpRectilinear { planes, center } => { + if passes < MAX_LIST3_PASSES { + passes += 1; + *img = warp_rectilinear(img, planes, *center); + } + } + Opcode::WarpRectilinear2 { planes, center, reciprocal, .. } => { + if passes < MAX_LIST3_PASSES { + passes += 1; + *img = warp_rectilinear2(img, planes, *center, *reciprocal); + } + } Opcode::WarpFisheye { .. } | Opcode::TrimBounds { .. } | Opcode::Unknown { .. } => {} other => { + if passes >= MAX_LIST3_PASSES { + continue; + } + passes += 1; let mut flat: Vec = img.data.iter().flat_map(|p| *p).collect(); apply_list(std::slice::from_ref(other), &mut flat, w, h, 3, None, 1.0); for (d, c) in img.data.iter_mut().zip(flat.as_chunks::<3>().0) { @@ -765,6 +785,27 @@ pub(crate) mod tests { a.iter().flat_map(|v| v.to_be_bytes()).collect() } + /// Warp and bad-pixel records each cost a full-image pass, and a hostile `OpcodeList3` repeats them + /// thousands of times ([`MAX_LIST3_PASSES`] bounds the multiplier); identity warps below the cap must + /// still apply, exactly, so the same image comes out. + #[test] + fn list3_pass_cap_skips_repeated_records() { + let mut img = Rgb32f::new(8, 6); + for (i, px) in img.data.iter_mut().enumerate() { + *px = [i as f32 / 2.0, (i % 8) as f32, (i / 8) as f32]; + } + let orig: Vec<[f32; 3]> = img.data.clone(); + let list: Vec = + (0..MAX_LIST3_PASSES * 5).map(|_| Opcode::WarpRectilinear { planes: vec![[1.0, 0.0, 0.0, 0.0, 0.0, 0.0]], center: [0.5, 0.5] }).collect(); + apply_list3(&list, &mut img); + assert_eq!(img.data, orig, "identity warps change nothing, and the cap skips the repeated rest"); + + // a real warp below the cap is still applied + let one = [Opcode::WarpRectilinear { planes: vec![[2.0, 0.0, 0.0, 0.0, 0.0, 0.0]], center: [0.5, 0.5] }]; + apply_list3(&one, &mut img); + assert_ne!(img.data, orig, "a genuine warp record is not skipped"); + } + #[test] fn parse_and_apply_area_ops() { let (w, h) = (4usize, 3usize); diff --git a/crates/raw/src/preview.rs b/crates/raw/src/preview.rs index 95f9bdcaa..4bcf6963e 100644 --- a/crates/raw/src/preview.rs +++ b/crates/raw/src/preview.rs @@ -103,8 +103,6 @@ fn jpeg_frame(b: &[u8]) -> Option<(u32, u32, u8)> { let be16 = |k: usize| Some(u32::from(u16::from_be_bytes([at(k)?, at(k + 1)?]))); Some((be16(7)?, be16(5)?, at(9)?)) } - -/// Whether `b` is a JPEG XL file: a bare codestream or the ISO-BMFF container's signature box. fn is_jxl(b: &[u8]) -> bool { b.starts_with(&[0xff, 0x0a]) || b.starts_with(&[0, 0, 0, 0x0c, b'J', b'X', b'L', b' ', 0x0d, 0x0a, 0x87, 0x0a]) } @@ -128,20 +126,17 @@ fn candidates<'a>(data: &'a [u8], ifd: &Ifd, base: u64, out: &mut Vec<&'a [u8]>) } if matches!(ifd.u16(t::COMPRESSION), Some(6) | Some(7) | Some(34892)) && let Ok(info) = ifd.image() + && let Some(chunk) = info.single_chunk(data.len() as u64) + && let Some(s) = chunk_bytes(data, &chunk) { - let chunks = info.chunks(data.len() as u64); - if chunks.len() == 1 - && let Some(s) = chunk_bytes(data, &chunks[0]) - { - out.push(s); - } + out.push(s); } // a rendered (RGB or grey, never CFA/LinearRaw/mask) JPEG XL preview in one chunk if ifd.u16(t::COMPRESSION) == Some(t::compression::JPEG_XL) && matches!(ifd.u16(t::PHOTOMETRIC), Some(t::photometric::BLACK_IS_ZERO | t::photometric::RGB)) && let Ok(info) = ifd.image() - && let [chunk] = info.chunks(data.len() as u64).as_slice() - && let Some(s) = chunk_bytes(data, chunk) + && let Some(chunk) = info.single_chunk(data.len() as u64) + && let Some(s) = chunk_bytes(data, &chunk) && is_jxl(s) { out.push(s); @@ -397,8 +392,14 @@ fn cr3_preview(bytes: &[u8]) -> Option<&[u8]> { /// Canon CR3 (ISO base media file): the `PRVW` box (Laurent Clévy's CR3 notes; layout confirmed on a CC0 /// sample) is `u32 size, "PRVW", u32 0, u16 ?, u16 width, u16 height, u16 ?, u32 jpeg length, JPEG`; the smaller /// `THMB` box has the same shape. Returns the larger valid one. +/// Marker-walk budget of the CR3 fallback: candidates can overlap (each claiming the rest of the file) and +/// [`is_dct_jpeg`] walks each marker by marker, so walking every candidate would be quadratic in file size. +/// The `THMB`/`PRVW` previews this finds are far below the cap together; once it is spent the scan stops. +const CR3_MAX_WALK: usize = 8 << 20; + fn cr3_preview_boxes(bytes: &[u8]) -> Option<&[u8]> { let mut best: Option<&[u8]> = None; + let mut walked = 0usize; for tag in [b"PRVW", b"THMB"] { let mut from = 0; while let Some(i) = bytes.get(from..).and_then(|s| s.windows(4).position(|w| w == tag)).map(|p| p + from) { @@ -411,6 +412,11 @@ fn cr3_preview_boxes(bytes: &[u8]) -> Option<&[u8]> { continue; } let jpeg = &bytes[j..j + len]; + // One that doesn't fit the budget left is skipped; a smaller one later may still fit. + if walked + jpeg.len() > CR3_MAX_WALK { + continue; + } + walked += jpeg.len(); if is_dct_jpeg(jpeg) { if best.is_none_or(|b| b.len() < jpeg.len()) { best = Some(jpeg); @@ -440,6 +446,52 @@ mod tests { j } + /// Preview extraction only serves single-chunk JPEG-compressed IFDs and asks the `single_chunk` first: + /// a TIFF whose strip array declares 100 000 one-row strips and no StripByteCounts must not build their + /// descriptor vector, and of course finds no preview in it. + #[test] + fn a_multi_strip_jpeg_ifd_costs_no_descriptor_vector() { + let mut ifd = IfdBuilder::new(); + ifd.set(t::COMPRESSION, Value::Short(vec![7])); + ifd.set(t::IMAGE_WIDTH, Value::Long(vec![16])); + ifd.set(t::IMAGE_LENGTH, Value::Long(vec![100_000])); + ifd.set(t::ROWS_PER_STRIP, Value::Short(vec![1])); + ifd.set(t::STRIP_OFFSETS, Value::Byte((0..100_000).map(|i| (i % 255 + 1) as u8).collect())); + let file = TiffWriter::default().write(&[ifd]).unwrap(); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || tx.send(embedded_preview(&file))); + assert_eq!(rx.recv_timeout(std::time::Duration::from_secs(2)), Ok(None)); + } + + /// CR3 candidates claim overlapping extents: each block's first bytes double as a 40-byte jump marker so + /// a walk jogs past the next header and reaches the end of the file, making every one of the 9000 + /// candidates walk its whole suffix before the marker-walk budget — quadratic without it. Nothing here + /// is a JPEG (no frame header), so the answer is `None` either way. + #[test] + fn cr3_preview_boxes_stop_walking_when_the_budget_is_spent() { + // one 256-byte block per candidate, all marker-aligned so walks chain through later blocks: + // 0..4, size (itself a jump marker of 40 bytes); 4..8, "PRVW"; 20..24, the claimed length + // (to the file end); 24..28, an SOI plus a marker jumping to the fill at 40; 64..256, a fill of + // 4-byte markers (`ff 01 00 02`). + let blocks = 9000usize; + let total = blocks * 256; + let mut f = Vec::with_capacity(total); + for k in 0..blocks { + let base = k * 256; + f.extend_from_slice(&[0xff, 0x01, 0x00, 0x26, b'P', b'R', b'V', b'W', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + f.extend_from_slice(&u32::try_from(total - base - 24).unwrap().to_be_bytes()); + f.extend_from_slice(&[0xff, 0xd8, 0x00, 0x24, 0xff, 0x01, 0x00, 0x26, 0, 0, 0, 0, 0, 0, 0, 0]); + f.resize(base + 64, 0); // 40..64 filler: walks jump over it, so its bytes are free + for _ in 0..48 { + f.extend_from_slice(&[0xff, 0x01, 0x00, 0x02]); + } + } + assert_eq!(f.len(), total); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || tx.send(cr3_preview_boxes(&f).map(|s| s.len()))); + assert_eq!(rx.recv_timeout(std::time::Duration::from_secs(2)), Ok(None), "no PRVW candidate is a JPEG"); + } + /// A minimal RAF: the fixed header (JPEG offset/length at 84/88), the JPEG, then a raw tail the /// caller must not have to read. `jpeg_len` is written as declared, so a test can claim more than /// the JPEG holds the way a padded container does. diff --git a/crates/raw/src/sparse.rs b/crates/raw/src/sparse.rs index d67a92b40..8a404a25e 100644 --- a/crates/raw/src/sparse.rs +++ b/crates/raw/src/sparse.rs @@ -35,6 +35,12 @@ const MAX_ENTRIES: u64 = 8192; const MAX_BOXES: usize = 4096; /// Child IFDs listed by one `SubIFDs` entry. const MAX_CHILDREN: u64 = 4096; +/// Pending child-IFD offsets the walker may queue at once: no more than it can ever visit +/// (the same [`MAX_IFDS`] the seen set is held to, deduplicated as they are popped), so an alley +/// of hostile `SubIFDs` entries can't grow the queue into the tens of millions of pointers. +/// The plan never guesses: the walk refuses and the plan reports the whole file when it would go +/// past the cap. +const MAX_PENDING: usize = MAX_IFDS; /// The RAF raw container's pointer to its raw IFD. const RAF_RAW_IFD: u16 = 0xf000; @@ -308,6 +314,13 @@ fn walk_tiff(w: &mut Want, origin: u64, data: &[u8], extra: &[u16]) -> Result = Vec::new(); while let Some(off) = stack.pop() { if off == 0 || off >= len || seen.len() >= MAX_IFDS || !seen.insert(off) { continue; @@ -329,7 +342,7 @@ fn walk_tiff(w: &mut Want, origin: u64, data: &[u8], extra: &[u16]) -> Result Result inline { - meta.insert(at(start), at(value_end)); + spans.push((at(start), at(value_end))); } let child = matches!(tag, t::SUB_IFDS | t::EXIF_IFD | t::GPS_IFD | t::INTEROP_IFD) || extra.contains(&tag); let locates = child || matches!(tag, t::STRIP_OFFSETS | t::STRIP_BYTE_COUNTS | t::TILE_OFFSETS | t::TILE_BYTE_COUNTS); @@ -359,14 +372,24 @@ fn walk_tiff(w: &mut Want, origin: u64, data: &[u8], extra: &[u16]) -> Result= len || !queued.insert(v) { + continue; + } + if stack.len() >= MAX_PENDING { + return Err("too many child IFDs queued to visit"); + } stack.push(v); } } } - if complete && let Some(next) = offset_at(table_end) { + if complete && let Some(next) = offset_at(table_end).filter(|&next| next != 0 && next < len && queued.insert(next)) { + if stack.len() >= MAX_PENDING { + return Err("too many child IFDs queued to visit"); + } stack.push(next); } } + meta.extend(spans); Ok(meta) } @@ -585,8 +608,74 @@ mod tests { assert_eq!(run(&file, 16, 0).0, HeaderPlan::Whole("an ARW6 header probe reads its tiles")); } - /// A TIFF with a million one-byte strips listed in descending order and all byte counts 0: the plan is - /// built in one sort and merge, not one pass over all strips (or all holes) per strip. + /// An IFD whose `SubIFDs` entries would queue 33 million child pointers (8192 entries of + /// 4096 values apiece, all pointing at the same array) is refused at the walker's pending + /// cap instead of growing the queue — and the plan reports the whole file, never guessing. + #[test] + fn a_subifds_alley_is_refused_at_the_pending_cap() { + // First read everything, so the walk reaches the directory table itself. + let n = 8192u32; + let len = 8 + 2 + (n * 12 + 4 + 4 * 4096) as usize; + let mut tiff = b"II*\0\x08\0\0\0".to_vec(); + tiff.extend_from_slice(&(n as u16).to_le_bytes()); + let mut entry = |tag: u16, typ: u16, count: u32, value: u32| { + tiff.extend_from_slice(&tag.to_le_bytes()); + tiff.extend_from_slice(&typ.to_le_bytes()); + tiff.extend_from_slice(&count.to_le_bytes()); + tiff.extend_from_slice(&value.to_le_bytes()); + }; + // header (8) + count field (2) + the {n} numbers and pointer (4), then the value array + let values_at = (8 + 2u64 + u64::from(n) * 12 + 4) as u32; + for _ in 0..n { + entry(t::SUB_IFDS, 4, MAX_CHILDREN as u32, values_at); + } + tiff.extend_from_slice(&0u32.to_le_bytes()); + tiff.extend((1..=4096u32).flat_map(u32::to_le_bytes)); + let mut loaded = Ranges::new(); + loaded.insert(0, len as u64); + let mut w = Want::new(&tiff, &loaded); + assert_eq!(walk_tiff(&mut w, 0, &tiff, &[]), Err("too many child IFDs queued to visit")); + } + + /// A TIFF with many out-of-line values across 512 directories is planned in one sort and + /// merge, not one pass over all accumulated ranges per value (CodeRabbit #52: before, each + /// `Ranges::insert` copied every range found before it — the ~million disjoint values of the + /// fixture below ask for more copying than the timeout allows). + #[test] + fn many_out_of_line_values_plan_fast() { + let (ifds, values) = (512usize, 2048usize); + let ifd_len = 2 + values * 12 + 4; + let values_at = 8 + ifds * ifd_len; + let len = values_at + values * 8; + let mut tiff = vec![0u8; len]; + tiff[..8].copy_from_slice(b"II*\0\x08\0\0\0"); + for i in 0..ifds { + let at = 8 + i * ifd_len; + // {values} out-of-line strings (count 8 > the 4 inline bytes), then the next-IFD + // pointer; every value is a disjoint 8-byte range for the merge to handle. + tiff[at..at + 2].copy_from_slice(&(values as u16).to_le_bytes()); + for k in 0..values { + let e = at + 2 + k * 12; + tiff[e..e + 2].copy_from_slice(&t::MAKE.to_le_bytes()); + tiff[e + 2..e + 4].copy_from_slice(&2u16.to_le_bytes()); // ASCII: 8 bytes > inline — a span per entry + tiff[e + 4..e + 8].copy_from_slice(&8u32.to_le_bytes()); + tiff[e + 8..e + 12].copy_from_slice(&(values_at as u32 + (k * 8) as u32).to_le_bytes()); + } + let next = if i == ifds - 1 { 0u32 } else { (at + ifd_len) as u32 }; + tiff[at + ifd_len - 4..at + ifd_len].copy_from_slice(&next.to_le_bytes()); + } + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + let mut loaded = Ranges::new(); + loaded.insert(0, len as u64); + let _ = tx.send(header_plan(&tiff, &loaded)); + }); + rx.recv_timeout(std::time::Duration::from_secs(20)).expect("header_plan took longer than 20 s"); + } + + /// A TIFF with a million one-byte strips listed in descending order and all byte counts 0: + /// the plan is built in one sort and merge, not one pass over all strips (or all holes) per + /// strip. #[test] fn many_strips_without_byte_counts_plan_fast() { let n = 1_000_000u32; diff --git a/crates/raw/src/tiffraw.rs b/crates/raw/src/tiffraw.rs index a781151b4..57878b881 100644 --- a/crates/raw/src/tiffraw.rs +++ b/crates/raw/src/tiffraw.rs @@ -105,48 +105,93 @@ pub fn read_image(data: &[u8], info: &ImageInfo, order: ByteOrder, packing: Pack let chunks = info.chunks(data.len() as u64); let planar = info.planar == 2 && cpp > 1; let ccpp = if planar { 1 } else { cpp }; - let decoded: Vec> = - chunks.par_iter().map(|c| decode_chunk(data, info, c, order, packing, ccpp, bits, float).map(|p| (*c, p))).collect(); + // The chunks are decoded and copied into the output in groups whose combined nominal size stays within + // a few times the output buffer's: everything decoded at once would retain the whole image twice, and + // a hostile tile table's chosen padding could stretch that much further (issue #1517). One group + // still decodes in parallel; the grid order groups make, the copies stay disjoint. + // The tile grid decides how much is decoded, whatever the file's bytes: the nominal sizes of all + // the chunks stay within a few times the image (edge tiles pad it) before any is decoded. + let nominal = chunks.iter().fold(0usize, |sum, c| sum.saturating_add(chunk_samples(c, ccpp))); + if nominal > total.saturating_mul(4).saturating_add(1 << 24) { + return Err(RawError::Corrupt("the tile grid is much larger than the image".into())); + } + let group_samples = (total / 4).max(1 << 20); let mut out_u16 = if float { Vec::new() } else { vec![0u16; total] }; let mut out_f32 = if float { vec![0f32; total] } else { Vec::new() }; - for r in decoded { - // a chunk that cannot be decoded is not a partial image: the file stays preview-only - let (c, px) = r?; - let (cw, ch) = (c.width as usize, c.height as usize); - let (x0, y0) = (c.x as usize, c.y as usize); - if x0 >= w || y0 >= h { - continue; + let mut decode_from = 0usize; + while decode_from < chunks.len() { + let mut to = decode_from; + let mut taken = 0usize; + for (k, c) in chunks[decode_from..].iter().enumerate() { + let n = chunk_samples(c, ccpp); + if taken > 0 && taken.saturating_add(n) > group_samples { + break; + } + taken = taken.saturating_add(n); + to = decode_from + k + 1; } - let copy_w = cw.min(w - x0); - let copy_h = ch.min(h - y0); - for y in 0..copy_h { - for x in 0..copy_w { - for s in 0..ccpp { - let src = (y * cw + x) * ccpp + s; - let dst_s = if planar { c.plane as usize } else { s }; - if dst_s >= cpp { - continue; - } - let dst = ((y0 + y) * w + x0 + x) * cpp + dst_s; - match &px { - ChunkPx::U16(v) => { - if let (Some(o), Some(&s)) = (out_u16.get_mut(dst), v.get(src)) { - *o = s; - } + let decoded: Vec> = + chunks[decode_from..to].par_iter().map(|c| decode_chunk(data, info, c, order, packing, ccpp, bits, float).map(|p| (*c, p))).collect(); + for r in decoded { + // a chunk that cannot be decoded is not a partial image: the file stays preview-only + let (c, px) = r?; + let (cw, ch) = (c.width as usize, c.height as usize); + let (x0, y0) = (c.x as usize, c.y as usize); + if x0 >= w || y0 >= h { + continue; + } + let copy_w = cw.min(w - x0); + let copy_h = ch.min(h - y0); + for y in 0..copy_h { + for x in 0..copy_w { + for s in 0..ccpp { + let src = (y * cw + x) * ccpp + s; + let dst_s = if planar { c.plane as usize } else { s }; + if dst_s >= cpp { + continue; } - ChunkPx::F32(v) => { - if let (Some(o), Some(&s)) = (out_f32.get_mut(dst), v.get(src)) { - *o = s; + let dst = ((y0 + y) * w + x0 + x) * cpp + dst_s; + match &px { + ChunkPx::U16(v) => { + if let (Some(o), Some(&s)) = (out_u16.get_mut(dst), v.get(src)) { + *o = s; + } + } + ChunkPx::F32(v) => { + if let (Some(o), Some(&s)) = (out_f32.get_mut(dst), v.get(src)) { + *o = s; + } } } } } } } + decode_from = to; } Ok(if float { RawData::F32(out_f32) } else { RawData::U16(out_u16) }) } +/// Bytes that `n` samples take in a row of an uncompressed or inflated chunk, laid out as +/// [`unpack_chunk`] reads them: whole bytes or 16-bit words, or 32-bit words for `Words32Msb`. +fn packed_row_bytes(n: u64, bits: u32, packing: Packing, float: bool) -> u64 { + if float { + return n.saturating_mul(u64::from(bits.div_ceil(8))); + } + let bit_len = n.saturating_mul(u64::from(bits)); + match (bits, packing) { + (8, _) => n, + (16, _) | (_, Packing::Word16) => n.saturating_mul(2), + (_, Packing::Msb | Packing::Lsb) => bit_len.div_ceil(8), + (_, Packing::Words32Msb) => bit_len.div_ceil(32).saturating_mul(4), + } +} + +/// A chunk's nominal decoded size in samples (per plane, as the decoder builds it). +fn chunk_samples(c: &Chunk, ccpp: usize) -> usize { + (c.width as usize).saturating_mul(c.height as usize).saturating_mul(ccpp) +} + #[allow(clippy::too_many_arguments)] fn decode_chunk(data: &[u8], info: &ImageInfo, c: &Chunk, order: ByteOrder, packing: Packing, cpp: usize, bits: u32, float: bool) -> Result { let src = chunk_bytes(data, c).ok_or_else(|| RawError::Corrupt("chunk offset past end of file".into()))?; @@ -179,9 +224,13 @@ fn decode_chunk(data: &[u8], info: &ImageInfo, c: &Chunk, order: ByteOrder, pack } comp::LOSSY_JPEG => { // lossy DNG: each chunk is a baseline (DCT) JPEG of 8-bit samples - let (px, jw, jh, jc) = lossy_jpeg(src, cpp)?; - let mut v = vec![0u16; n]; - for y in 0..ch.min(jh) { + let (px, jw, jh, jc) = lossy_jpeg(src, cpp, MAX_JPEG_SAMPLES)?; + // the buffer holds the rows the decoded JPEG covers (the caller zero-fills the tile's + // remainder and reads only rows it fills): a tile-many-larger decoded JPEG does not + // get the nominal tile's full buffer (issue #1517) + let rows = ch.min(jh); + let mut v = vec![0u16; rows.max(1).saturating_mul(cw).saturating_mul(cpp)]; + for y in 0..rows { for x in 0..cw.min(jw) { for k in 0..cpp { v[(y * cw + x) * cpp + k] = px[(y * jw + x) * jc + k.min(jc - 1)] as u16; @@ -191,13 +240,28 @@ fn decode_chunk(data: &[u8], info: &ImageInfo, c: &Chunk, order: ByteOrder, pack Ok(ChunkPx::U16(v)) } comp::ADOBE_DEFLATE | comp::DEFLATE => { - let bytes_per = if float { bits.div_ceil(8) as usize } else { 0 }; // row bytes in u64: a wrapping `cw · cpp · …` in usize could come out small on 32-bit targets and slip past the gate - let row_bytes = if float { cw as u64 * cpp as u64 * bytes_per as u64 } else { (cw as u64 * cpp as u64 * bits as u64).div_ceil(8) }; + let row_bytes = packed_row_bytes(cw as u64 * cpp as u64, bits, packing, float); // the gate caps at MAX_SAMPLES (≤ 2^30), so the `usize` cast below is lossless on 32-bit targets too let inflate_len = crate::checked_pixel_count(row_bytes, ch as u64).ok_or_else(|| RawError::Corrupt("bad TIFF chunk dimensions".into()))?; + // Deflate compresses at most about 1:1032, so a source this far under half of its + // nominal length cannot reconstruct the chunk: refused before the nominal buffer is + // inflated and predicted, which padding-only work would make gigabytes. + if (src.len() as u64).saturating_mul(2048) < inflate_len { + return Err(RawError::Corrupt(format!("a deflated chunk of {inflate_len} bytes is packed into only {} bytes", src.len()))); + } let raw = inflate(src, inflate_len as usize)?; + // The stream has to reach the last row inside the image (edge strips may stop there); + // it is laid out in nominal rows, so shorter is a truncated chunk, not a picture. + let last = packed_row_bytes(vw as u64 * cpp as u64, bits, packing, float); + let visible = row_bytes.saturating_mul(vh.saturating_sub(1) as u64).saturating_add(last); + if (raw.len() as u64) < visible { + return Err(RawError::Corrupt(format!( + "a deflated chunk holds {} bytes, fewer than the {visible} bytes its in-image samples span", + raw.len() + ))); + } unpack_chunk(&raw, order, packing, cw, ch, cpp, bits, float, info.predictor) } comp::JPEG_XL => jxl_chunk(src, cw, ch, vw, vh, cpp, bits, float), @@ -228,19 +292,31 @@ fn jxl_chunk_check(src: &[u8], cw: usize, ch: usize, vw: usize, vh: usize, cpp: jxl_chunk(src, cw, ch, vw, vh, cpp, bits, float).map(|_| ()) } +/// The most samples a tiled JPEG chunk or a white-balance preview may decode to: far more than +/// any real lossy tile or preview carries, and small enough that a JPEG header alone cannot size +/// a multi-gigabyte allocation. +pub(crate) const MAX_JPEG_SAMPLES: usize = 1 << 26; + /// Decode one baseline JPEG chunk to 8-bit samples: (samples, width, height, channels). -pub(crate) fn lossy_jpeg(src: &[u8], cpp: usize) -> Result<(Vec, usize, usize, usize)> { +pub(crate) fn lossy_jpeg(src: &[u8], cpp: usize, max_samples: usize) -> Result<(Vec, usize, usize, usize)> { use zune_core::bytestream::ZCursor; use zune_core::colorspace::ColorSpace; use zune_core::options::DecoderOptions; let cs = if cpp == 1 { ColorSpace::Luma } else { ColorSpace::RGB }; let opts = DecoderOptions::default().set_max_width(1 << 16).set_max_height(1 << 16).set_strict_mode(false).jpeg_set_out_colorspace(cs); let mut d = zune_jpeg::JpegDecoder::new_with_options(ZCursor::new(src), opts); - let px = d.decode().map_err(|e| RawError::Corrupt(format!("lossy JPEG tile: {e}")))?; + // The header is read first: the JPEG's own dimensions, not the enclosing tile's, size every + // allocation `decode` makes, and they may claim billions of pixels. + d.decode_headers().map_err(|e| RawError::Corrupt(format!("lossy JPEG tile: {e}")))?; let info = d.info().ok_or_else(|| RawError::Corrupt("lossy JPEG tile without a header".into()))?; let (w, h) = (info.width as usize, info.height as usize); let c = if cpp == 1 { 1 } else { 3 }; - if w == 0 || h == 0 || px.len() < w * h * c { + let out = w as u64 * h as u64 * c as u64; + if w == 0 || h == 0 || out > max_samples as u64 { + return Err(RawError::Corrupt(format!("a lossy JPEG of {w} × {h} × {c} would exceed the {max_samples} output samples allowed"))); + } + let px = d.decode().map_err(|e| RawError::Corrupt(format!("lossy JPEG tile: {e}")))?; + if px.len() < w * h * c { return Err(RawError::Corrupt("lossy JPEG tile: short output".into())); } Ok((px, w, h, c)) @@ -471,4 +547,97 @@ mod lossy_tests { // half of the visible rows are missing assert!(decode(8, 4).is_err()); } + + /// A lossy JPEG whose header alone claims more samples than any real tile or preview (here + /// 40000 × 40000 × 3) is refused from the header, before the decoder allocates its output + /// from the JPEG's own dimensions. + #[test] + fn an_oversized_lossy_jpeg_header_is_refused_before_decoding() { + // A real 8 × 8 baseline JPEG, whose frame header then claims other dimensions: the decoder + // parses the header, and the size check is what refuses it. + let px = vec![128u8; 8 * 8 * 3]; + let mut j = Vec::new(); + jpeg_encoder::Encoder::new(&mut j, 90).encode(&px, 8, 8, jpeg_encoder::ColorType::Rgb).unwrap(); + // SOF0: marker, length, precision, then the height and the width + let sof = j.windows(2).position(|w| w == [0xff, 0xc0]).expect("a baseline frame header"); + let claim = |j: &mut Vec, size: u16| { + j[sof + 5..sof + 7].copy_from_slice(&size.to_be_bytes()); + j[sof + 7..sof + 9].copy_from_slice(&size.to_be_bytes()); + }; + claim(&mut j, 40_000); + let RawError::Corrupt(refused) = lossy_jpeg(&j, 3, MAX_JPEG_SAMPLES).unwrap_err() else { panic!("a corrupt-data error") }; + assert!(refused.contains("would exceed"), "{refused}"); + // a header inside the budget passes the size check (and then fails to decode only for lack of data) + claim(&mut j, 1024); + match lossy_jpeg(&j, 3, MAX_JPEG_SAMPLES) { + Ok(_) => {} + Err(RawError::Corrupt(m)) => assert!(!m.contains("would exceed"), "the smaller header was refused for its size: {m}"), + Err(other) => panic!("unexpected error: {other:?}"), + } + } + + /// A lossy tile whose JPEG decodes far less than the nominal tile is fine: the rows the JPEG + /// covers hold its pixels, the rest of the output stays zero (and the tile's intermediate buffer + /// is sized by the JPEG, not by the nominal tile). + #[test] + fn a_small_lossy_jpeg_in_a_large_nominal_tile_decodes_with_zeros_below() { + let px: Vec = (0..8 * 8).flat_map(|_| [30u8, 90, 150]).collect(); + let mut j = Vec::new(); + jpeg_encoder::Encoder::new(&mut j, 100).encode(&px, 8, 8, jpeg_encoder::ColorType::Rgb).unwrap(); + let info = ImageInfo { + width: 16, + height: 16, + bits_per_sample: vec![8, 8, 8], + samples_per_pixel: 3, + compression: comp::LOSSY_JPEG, + photometric: 34892, + planar: 1, + predictor: 1, + sample_format: 1, + new_subfile_type: 0, + layout: Layout::Tiles { tile_width: 16, tile_height: 16 }, + offsets: vec![0], + byte_counts: vec![j.len() as u64], + }; + let RawData::U16(v) = read_image(&j, &info, ByteOrder::Little, Packing::Msb).unwrap() else { panic!("integer samples") }; + assert_eq!(v.len(), 16 * 16 * 3, "the output is the full nominal image"); + let px = |x: usize, y: usize| &v[(y * 16 + x) * 3..(y * 16 + x) * 3 + 3]; + assert!((px(3, 3)[0] as i32 - 30).abs() <= 3 && (px(3, 3)[2] as i32 - 150).abs() <= 3, "covered rows: {:?}", px(3, 3)); + assert!(px(3, 12).iter().all(|&s| s == 0), "rows below the JPEG stay zero: {:?}", px(3, 12)); + } + + /// A deflated tile is refused when its packed bytes are too few for deflate to have produced + /// the nominal chunk, or when the stream stops short of the rows inside the image; a fully + /// covered flat tile still decodes. + #[test] + fn a_short_or_undercovering_deflated_tile_is_refused() { + let info = |byte_counts: u64| ImageInfo { + width: 512, + height: 512, + bits_per_sample: vec![16], + samples_per_pixel: 1, + compression: comp::ADOBE_DEFLATE, + photometric: 32803, + planar: 1, + predictor: 1, + sample_format: 1, + new_subfile_type: 0, + layout: Layout::Tiles { tile_width: 512, tile_height: 512 }, + offsets: vec![0], + byte_counts: vec![byte_counts], + }; + // a complete flat tile: 512 KiB of zeros compresses to a few hundred bytes (well within + // deflate's packing bound) and inflates to every nominal byte the tile claims + let zlib = miniz_oxide::deflate::compress_to_vec_zlib(&vec![0u8; 512 * 512 * 2], 6); + assert_eq!(read_image(&zlib, &info(zlib.len() as u64), ByteOrder::Little, Packing::Msb), Ok(RawData::U16(vec![0; 512 * 512]))); + // the same 1 KiB packed, but the stream claims a whole tile: a few hundred bytes cannot + // reconstruct 512 KiB from a decoder that compresses no further than about 1:1032 + let zlib = miniz_oxide::deflate::compress_to_vec_zlib(&vec![0u8; 1024], 6); + assert!(matches!(read_image(&zlib, &info(512 * 512 * 2), ByteOrder::Little, Packing::Msb), Err(RawError::Corrupt(_))), "{zlib:?}"); + // 500 KiB of zeros compress to a stream within the packing bound and inflating to more + // bytes than the in-image samples span, but 24 KiB short of the whole nominal tile: + // refused as truncated rather than filled with the predictor's padding + let zlib = miniz_oxide::deflate::compress_to_vec_zlib(&vec![0u8; 500 * 1000], 6); + assert!(matches!(read_image(&zlib, &info(zlib.len() as u64), ByteOrder::Little, Packing::Msb), Err(RawError::Corrupt(_)))); + } } diff --git a/crates/raw/src/vendor/mef.rs b/crates/raw/src/vendor/mef.rs index 207bc8871..d5c5d83b4 100644 --- a/crates/raw/src/vendor/mef.rs +++ b/crates/raw/src/vendor/mef.rs @@ -89,6 +89,13 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { // the white balance is measured from the samples, so the header probe reads them too let chunks = info.chunks(bytes.len() as u64); let src = chunks.first().and_then(|c| chunk_bytes(bytes, c)).ok_or_else(|| RawError::Corrupt("Mef strip outside file".into()))?; + // 12-bit MSB packing takes 3 bytes per 2 samples; a strip that cannot hold the declared + // samples is a truncated file, not a picture — unpacking it would run past the real bytes + // through every output sample (a short strip asks for the whole allocation and more). + let need = n.div_ceil(2).saturating_mul(3); + if src.len() < need { + return Err(RawError::Corrupt(format!("a Mef strip of {} bytes cannot hold the declared {n} samples", src.len()))); + } let mut d = vec![0u16; n]; crate::unpack::unpack_msb(src, 12, &mut d); let white = super::white_from_data(&d, 12); @@ -138,6 +145,10 @@ mod tests { } fn sample(w: u32, h: u32, px: &[u16]) -> Vec { + sample_strip(w, h, &pack(px)) + } + + fn sample_strip(w: u32, h: u32, strip: &[u8]) -> Vec { let mut raw = IfdBuilder::new(); raw.set(t::NEW_SUBFILE_TYPE, Value::Long(vec![0])); raw.set(t::IMAGE_WIDTH, Value::Long(vec![w])); @@ -148,7 +159,7 @@ mod tests { raw.set(t::ORIENTATION, Value::Short(vec![6])); raw.set(t::CFA_REPEAT_PATTERN_DIM, Value::Short(vec![2, 2])); raw.set(t::CFA_PATTERN_EP, Value::Byte(vec![0, 1, 1, 2])); - raw.set_image(ImageData::Strips { rows_per_strip: h, strips: vec![pack(px)] }); + raw.set_image(ImageData::Strips { rows_per_strip: h, strips: vec![strip.to_vec()] }); let mut ifd0 = IfdBuilder::new(); ifd0.set(t::MAKE, Value::Ascii("Mamiya-OP Co.,Ltd.".into())); ifd0.set(t::MODEL, Value::Ascii("MAMIYA ZD".into())); @@ -156,6 +167,52 @@ mod tests { TiffWriter::new(ByteOrder::Big, false).write(&[ifd0]).unwrap() } + /// A strip that cannot hold the declared samples is a truncated file, refused as corrupt + /// instead of unpacked: `unpack_msb` would otherwise run `n` samples through the few real + /// bytes, filling the rest from nothing. + #[test] + fn a_strip_too_short_for_the_declared_samples_is_refused() { + let px: Vec = (0..16 * 6).map(|i| (i * 41 % 4096) as u16).collect(); + let f = sample(16, 6, &px); + assert!(crate::decode(&f).is_ok()); + // A truncated file shaped like a real one: IFD tables first, strip last — only its first + // 20 packed bytes remain, while the IFD still declares the full 1.5 B/sample length. The + // probe accepts the file on the declared length; before the byte gate below, `unpack_msb` + // ran the dimension-sized allocation through those 20 bytes, filling it from nothing. + let u32be = |b: &[u8], a: usize| u32::from_be_bytes([b[a], b[a + 1], b[a + 2], b[a + 3]]); + let full = pack(&px); + let at = f.windows(full.len()).position(|w| w == full).expect("strip bytes in the file"); + let after_strip = at + full.len(); + let moved = after_strip - (at + 20); + let mut g = f[..8].to_vec(); + g.extend(std::iter::repeat_n(0u8, at + 20 - 8)); + g.extend_from_slice(&f[after_strip..]); + // repoint the first-IFD pointer and ifd0's MAKE / MODEL / SubIfds offsets (the tables now + // sit `moved` earlier); the raw sub-IFD's strip offset and byte count are patched below + let poke = |b: &mut [u8], a: usize| { + let v = u32::from_be_bytes([b[a], b[a + 1], b[a + 2], b[a + 3]]); + if v as usize >= after_strip { + b[a..a + 4].copy_from_slice(&(v - moved as u32).to_be_bytes()); + } + }; + let ifd0_at = u32be(&g, 4) as usize - moved; + g[4..8].copy_from_slice(&(ifd0_at as u32).to_be_bytes()); + let n = u16::from_be_bytes([g[ifd0_at], g[ifd0_at + 1]]) as usize; + for i in 0..n { + poke(&mut g, ifd0_at + 2 + i * 12 + 8); + } + // the raw sub-IFD: entries follow its count; tag 273 (StripOffsets) is the seventh, held + // inline — repoint it at the strip's new home, and append the truncated strip for real + let sub_at = u32be(&g, ifd0_at + 2 + 2 * 12 + 8) as usize; + let e_offsets = sub_at + 2 + 6 * 12 + 8; + let strip_at = g.len() as u32; + g[e_offsets..e_offsets + 4].copy_from_slice(&strip_at.to_be_bytes()); + g.extend_from_slice(&full[..20]); + assert_eq!(crate::probe(&g), Some(RawFormat::Mef)); + let r = crate::decode(&g); + assert!(matches!(r, Err(RawError::Corrupt(_))), "{r:?}"); + } + #[test] fn decodes_packed_12_bit() { let px: Vec = (0..16 * 6).map(|i| (i * 41 % 4096) as u16).collect(); diff --git a/crates/raw/src/wb_estimate.rs b/crates/raw/src/wb_estimate.rs index 17a86a2a5..13cb9f8da 100644 --- a/crates/raw/src/wb_estimate.rs +++ b/crates/raw/src/wb_estimate.rs @@ -66,7 +66,7 @@ fn median(v: &mut [f64]) -> Option { fn from_preview(bytes: &[u8], f: &Frame) -> Option<[f32; 3]> { let jpeg = crate::preview::embedded_preview(bytes)?; - let (px, pw, ph, ch) = crate::tiffraw::lossy_jpeg(&jpeg, 3).ok()?; + let (px, pw, ph, ch) = crate::tiffraw::lossy_jpeg(&jpeg, 3, crate::tiffraw::MAX_JPEG_SAMPLES).ok()?; if ch != 3 { return None; } diff --git a/crates/tiff/src/image.rs b/crates/tiff/src/image.rs index ca3ff8b69..16e24f367 100644 --- a/crates/tiff/src/image.rs +++ b/crates/tiff/src/image.rs @@ -118,14 +118,34 @@ impl ImageInfo { } } - /// All chunks that have an offset. Missing byte counts are inferred as "to the next offset / end of data" - /// using `data_len`. - pub fn chunks(&self, data_len: u64) -> Vec { + /// The chunk count the image declares (offsets present, cut to the layout's plane capacity) with the + /// geometry `chunks` needs to place each one. No descriptors are built, so a pathological strip array + /// costs it a length check. + fn chunk_grid(&self) -> (usize, u32, usize) { let (across, down) = self.grid(); let per_plane = across as usize * down as usize; let planes = if self.planar == 2 { self.samples_per_pixel as usize } else { 1 }; + (self.offsets.len().min(per_plane.saturating_mul(planes)), across, per_plane) + } + + fn declared_chunk_count(&self) -> usize { + self.chunk_grid().0 + } + + /// The one chunk when the image has exactly one, else `None` — and no descriptor vector beyond it. Preview + /// extraction only serves single-chunk images, so it asks here before serving a hostile multi-strip layout. + pub fn single_chunk(&self, data_len: u64) -> Option { + if self.declared_chunk_count() != 1 { + return None; + } + self.chunks(data_len).into_iter().next() + } + + /// All chunks that have an offset. Missing byte counts are inferred as "to the next offset / end of data" + /// using `data_len`. + pub fn chunks(&self, data_len: u64) -> Vec { + let (n, across, per_plane) = self.chunk_grid(); let (cw, ch) = self.chunk_size(); - let n = self.offsets.len().min(per_plane.saturating_mul(planes)); let mut out = Vec::with_capacity(n); // the distinct offsets in order, for chunks without a byte count (sorted once, not scanned per chunk) let mut sorted: Vec = Vec::new(); @@ -192,6 +212,18 @@ mod tests { } } + /// One offset is one chunk, and `single_chunk` refuses to build descriptors when the image declares more. + #[test] + fn single_chunk_is_the_only_chunk() { + let one = strips(vec![50], vec![10]); + assert_eq!(one.single_chunk(100).map(|c| (c.index, c.offset, c.len)), one.chunks(100).first().map(|c| (c.index, c.offset, c.len))); + // the inferred length matches too, and never falls back past the data + let inferred = strips(vec![70], vec![]); + assert_eq!(inferred.single_chunk(100).map(|c| c.len), Some(30)); + assert!(strips(vec![50, 10], vec![10, 5]).single_chunk(100).is_none(), "a multi-chunk image has no single chunk"); + assert!(strips(Vec::new(), Vec::new()).single_chunk(100).is_none()); + } + #[test] fn missing_byte_counts_run_to_the_next_larger_offset() { let info = strips(vec![50, 10, 30, 10, 70], vec![0, 0, 5]); diff --git a/crates/tiff/src/makernote.rs b/crates/tiff/src/makernote.rs index 7ed5e1997..288b366f6 100644 --- a/crates/tiff/src/makernote.rs +++ b/crates/tiff/src/makernote.rs @@ -55,7 +55,11 @@ pub fn parse_makernote(data: &[u8], offset: u64, len: u64, order: ByteOrder, mak Some(b"MM") => Some(ByteOrder::Big), _ => None, }; - let opts = ParseOptions { max_ifds: 64, max_depth: 3, ..Default::default() }; + // A fixed value budget: a maker note is at most tens of kilobytes of values in any honest file, and + // computing the budget from the buffer's length multiplies the file length (a hostile entry in a + // sparse header-read buffer, where the buffer holds the file's logical size but only a few + // kilobytes are loaded, would decode allocations no actual data backs). + let opts = ParseOptions { max_ifds: 64, max_depth: 3, value_budget: Some(1 << 20), ..Default::default() }; let try_parse = |kind: MakerNoteKind, ifd_at: u64, base: u64, ord: ByteOrder| -> Option { let (ifd, _) = parse_ifd_at(data, ifd_at, ord, base, false, &opts).ok()?; (!ifd.entries.is_empty()).then_some(MakerNote { kind, order: ord, base, ifd }) @@ -192,6 +196,35 @@ mod tests { assert_eq!(m.ifd.u64s(2).unwrap(), vec![9, 0]); } + /// A maker note's value budget is fixed, not computed from the length of the buffer it sits in: + /// an entry claiming more than the note budget (here 2 MB) is dropped, a fitting one is read. In a + /// sparse header read the buffer holds the file's logical size while only kilobytes were loaded, + /// so a length-scaled budget would let an unrelated entry decode megabytes of zeros (see the focus + /// reader, try_read_exif_with). + #[test] + fn a_note_entry_past_the_fixed_value_budget_is_dropped() { + let order = ByteOrder::Little; + let mut note = b"SONY DSC \0\0\0".to_vec(); + // the Sony IFD directly after the 12-byte header, one fitting BYTE entry and a decoy + // BYTE[2 MB] entry pointing into the file's own (zero-filled) body + order.put_u16(&mut note, 2); + order.put_u16(&mut note, 0x201b); + order.put_u16(&mut note, 1); + order.put_u32(&mut note, 1); + note.extend([3, 0, 0, 0]); + order.put_u16(&mut note, 0x9900); + order.put_u16(&mut note, 1); + order.put_u32(&mut note, 2 << 20); + order.put_u32(&mut note, 200); + order.put_u32(&mut note, 0); + let mut bytes = note.clone(); + bytes.resize(200 + (2 << 20), 0); // the decoy's value range is inside the real bytes + let m = parse_makernote(&bytes, 0, note.len() as u64, order, "SONY").unwrap(); + assert_eq!(m.kind, MakerNoteKind::Sony); + assert!(m.ifd.get(0x201b).is_some(), "the fitting entry is read"); + assert!(m.ifd.get(0x9900).is_none(), "the decoy is dropped by the value budget"); + } + #[test] fn garbage_note_is_none() { let (bytes, off, len) = with_note(ByteOrder::Little, vec![0xff; 40]); diff --git a/crates/tiff/src/reader.rs b/crates/tiff/src/reader.rs index bd728ca9c..70c66c5a7 100644 --- a/crates/tiff/src/reader.rs +++ b/crates/tiff/src/reader.rs @@ -338,6 +338,25 @@ pub fn decode(o: ByteOrder, typ: FieldType, b: &[u8]) -> Value { #[cfg(test)] mod tests { use super::*; + use crate::Value; + use crate::tags as t; + use crate::writer::{IfdBuilder, TiffWriter}; + + /// A value budget the caller sets bounds what is decoded: an entry claiming more value bytes than + /// the budget allows is dropped, entries that fit are read (the sparse header-read path, where the + /// buffer holds the file's logical size but only a few kilobytes were ever loaded, sizes its + /// budget from what was fetched, not from whatever length the file claims). + #[test] + fn the_value_budget_drops_over_budget_entries_and_keeps_the_rest() { + let mut ifd0 = IfdBuilder::new(); + ifd0.set(t::MAKE, Value::Ascii("Keep".into())); + ifd0.set(0x9000, Value::Byte(vec![0; 2 << 20])); + let file = TiffWriter { order: ByteOrder::Little, bigtiff: false }.write(&[ifd0]).unwrap(); + let opts = ParseOptions { value_budget: Some(1 << 20), ..Default::default() }; + let tiff = Tiff::parse_with(&file, &opts).unwrap(); + assert_eq!(tiff.ifds[0].string(t::MAKE), Some("Keep".to_string()), "a fitting entry is read"); + assert!(tiff.ifds[0].get(0x9000).is_none(), "the over-budget entry is dropped"); + } /// A BigTIFF whose first IFD, at 80, declares `declared` entries but holds one, followed by a next-IFD pointer /// `next`; a valid one-entry IFD sits at 16. diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 7bc523653..8e7b77f42 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -45,3 +45,6 @@ lightcraft-merge = { workspace = true } [build-dependencies] serde_json = { workspace = true } toml = "1" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +craft-update = { workspace = true } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index a39fdda5c..e53989fca 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -49,6 +49,7 @@ pub mod sync; pub mod tasks; pub mod text_field; pub mod theme; +pub mod updates_ui; pub mod widgets; pub mod window_place; @@ -410,6 +411,7 @@ pub struct LightcraftApp { pub headless_host: bool, /// Warnings to show one at a time (damaged settings files…, issue #103). pub notices: Vec, + pub(crate) updates_rt: updates_ui::Runtime, /// Quitting was stopped to ask first: changes couldn't be saved, or tasks are running. pub quit_prompt: Option, /// Keyboard/focus/input state for the prompt above, including its one-frame creation barrier. @@ -617,6 +619,7 @@ impl LightcraftApp { text_focus: false, headless_host: false, notices: vec![], + updates_rt: Default::default(), quit_prompt: None, notice_input: Default::default(), quit_confirmed: false, @@ -1968,6 +1971,7 @@ impl LightcraftApp { lightroom_import::tick(self, ctx); recheck::tick(self); tasks::poll(self, ctx); + updates_ui::tick(self, ctx); self.preview_build_status(ctx); self.save_status(ctx); self.ai_mask_detail(ctx, now); @@ -2125,7 +2129,7 @@ impl LightcraftApp { // Recent-library checks and launch-list keys read focus/time/keys: they belong to the UI // phase, not the hidden-capable `logic` tick (plan §4.5 / migration input contract). panels::library_problem::logic(self, ctx); - if self.fonts_ready { + if self.fonts_ready && !updates_ui::blocks_input(self) { shortcuts::handle(self, ctx); } } @@ -2628,6 +2632,7 @@ impl LightcraftApp { panels::notices::hide_background_pointer(&ctx); } panels::dialogs::show(self, &ctx, notice_blocks); + updates_ui::show(self, &ctx); panels::library_problem::show(self, &ctx, notice_blocks); panels::toast(self, &ctx); drop_overlay::show(self, &ctx); @@ -2658,6 +2663,7 @@ impl LightcraftApp { panels::notices::hide_background_pointer(&ctx); } panels::dialogs::show(self, &ctx, notice_blocks); + updates_ui::show(self, &ctx); panels::library_problem::show(self, &ctx, notice_blocks); export_task::poll(self, &ctx); slideshow_export::poll(self, &ctx); diff --git a/crates/ui-egui/src/menubar.rs b/crates/ui-egui/src/menubar.rs index 9b3d88036..ba4a8a7c2 100644 --- a/crates/ui-egui/src/menubar.rs +++ b/crates/ui-egui/src/menubar.rs @@ -322,6 +322,7 @@ const LAYOUT: &[(&str, &[&str])] = &[ "app.systemInfo", "app.openLogFolder", "---", + "app.checkForUpdates", "app.about", ], ), diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 03841d14a..84ec8ae0c 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -263,6 +263,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[ // Settings ▸ Display ("app.displayProfile", "Display Profile", None, ""), ("app.about", "About LightCraft", None, "Help"), + ("app.checkForUpdates", "Check for Updates…", None, "Help"), ("app.systemInfo", "System Info…", None, "Help"), ("app.openLogFolder", "Open Log Folder", None, "Help"), // the frame-rate display (`frame_rate`): a debugging aid, never saved @@ -1587,6 +1588,7 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option crate::updates_ui::check(app), "photo.editInPhotoCraft" => edit_in(app, &ctx, p, true), "photo.editInExternal" => edit_in(app, &ctx, p, false), "photo.openOriginalInExternal" => open_original_in(app, &ctx, p), diff --git a/crates/ui-egui/src/updates_ui.rs b/crates/ui-egui/src/updates_ui.rs new file mode 100644 index 000000000..5e159a0f4 --- /dev/null +++ b/crates/ui-egui/src/updates_ui.rs @@ -0,0 +1,188 @@ +//! Software updates, background check at startup, and startup prompt modal. + +use serde_json::Value; + +use crate::LightcraftApp; + +#[cfg(not(target_arch = "wasm32"))] +use craft_update::updater::{Action, Status, Updater}; + +pub(crate) struct Runtime { + #[cfg(not(target_arch = "wasm32"))] + updater: Updater, + /// The desktop app turns the check at start on; tests and the web build leave it off. + #[cfg(not(target_arch = "wasm32"))] + startup_check: bool, + #[cfg(not(target_arch = "wasm32"))] + check_requested: bool, + /// The context of the last frame, to wake the UI when a check is queued from a menu callback. + #[cfg(not(target_arch = "wasm32"))] + repaint: Option, + pub(crate) manual_window_open: bool, + pub(crate) status_message: Option, +} + +impl Default for Runtime { + fn default() -> Self { + Self { + #[cfg(not(target_arch = "wasm32"))] + updater: Updater::new(&craft_update::suite::LIGHTCRAFT, env!("CARGO_PKG_VERSION")), + #[cfg(not(target_arch = "wasm32"))] + startup_check: false, + #[cfg(not(target_arch = "wasm32"))] + check_requested: false, + #[cfg(not(target_arch = "wasm32"))] + repaint: None, + manual_window_open: false, + status_message: None, + } + } +} + +impl LightcraftApp { + /// Whether the app asks for a newer release when it starts (off until the desktop app turns it on). + #[cfg(not(target_arch = "wasm32"))] + pub fn set_startup_update_check(&mut self, on: bool) { + self.updates_rt.startup_check = on; + } +} + +/// The update prompt owns the keyboard while it is open: shortcuts must not reach the library behind it. +pub(crate) fn blocks_input(app: &LightcraftApp) -> bool { + #[cfg(not(target_arch = "wasm32"))] + { + app.updates_rt.updater.prompt_open() + } + #[cfg(target_arch = "wasm32")] + { + let _ = app; + false + } +} + +pub(crate) fn check(app: &mut LightcraftApp) -> Result { + #[cfg(not(target_arch = "wasm32"))] + { + app.updates_rt.check_requested = true; + if let Some(ctx) = &app.updates_rt.repaint { + ctx.request_repaint(); + } + Ok(Value::Null) + } + #[cfg(target_arch = "wasm32")] + { + app.updates_rt.manual_window_open = true; + app.updates_rt.status_message = Some("Update check is not supported in the web browser.".into()); + Ok(Value::Null) + } +} + +pub(crate) fn tick(app: &mut LightcraftApp, ctx: &egui::Context) { + #[cfg(not(target_arch = "wasm32"))] + { + let rt = &mut app.updates_rt; + rt.repaint = Some(ctx.clone()); + if std::mem::take(&mut rt.check_requested) { + rt.updater.check_now(ctx); + } else if rt.startup_check { + rt.updater.check_at_start(ctx); + } + rt.updater.tick(ctx); + } + #[cfg(target_arch = "wasm32")] + let _ = (app, ctx); +} + +pub(crate) fn show(app: &mut LightcraftApp, ctx: &egui::Context) { + #[cfg(not(target_arch = "wasm32"))] + { + // The restart helper is scheduled before the app closes, so no update starts over unsaved work. + let clean = app.session.unsaved().is_none(); + match app.updates_rt.updater.show_if(ctx, clean) { + Action::OpenUrl(url) => ctx.open_url(egui::OpenUrl::new_tab(url)), + Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close), + Action::None => {} + } + // The window answers Help ▸ Check for Updates, so it follows the updater's status. + let rt = &mut app.updates_rt; + (rt.manual_window_open, rt.status_message) = match rt.updater.status() { + Status::Idle => (false, None), + Status::Checking => (true, Some(crate::i18n::tr("Checking for updates…").to_string())), + Status::UpToDate => ( + true, + Some( + crate::i18n::tr("LightCraft {version} is currently the newest version available.") + .replace("{version}", env!("CARGO_PKG_VERSION")), + ), + ), + Status::Failed(e) => (true, Some(crate::i18n::tr("Could not check for updates:\n{error}").replace("{error}", e))), + }; + } + + if app.updates_rt.manual_window_open { + let mut open = true; + egui::Window::new(crate::i18n::tr("LightCraft Updates")) + .open(&mut open) + .collapsible(false) + .resizable(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) + .show(ctx, |ui| { + if let Some(msg) = &app.updates_rt.status_message { + ui.label(msg); + } + ui.add_space(8.0); + if ui.button(crate::i18n::tr("OK")).clicked() { + app.updates_rt.manual_window_open = false; + } + }); + if !open { + app.updates_rt.manual_window_open = false; + } + } + + // Closing the window dismisses the outcome it showed. + #[cfg(not(target_arch = "wasm32"))] + if !app.updates_rt.manual_window_open { + app.updates_rt.updater.dismiss_status(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use lightcraft_engine::Session; + + #[test] + fn default_runtime_is_idle() { + let rt = Runtime::default(); + assert!(!rt.manual_window_open); + #[cfg(not(target_arch = "wasm32"))] + { + assert!(!rt.startup_check); + assert!(!rt.updater.prompt_open()); + assert_eq!(rt.updater.status(), &Status::Idle); + } + } + + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn startup_prompt_shows_and_closes() { + let mut app = LightcraftApp::new(Session::new(), Default::default()); + let ctx = egui::Context::default(); + let release = craft_update::Release { + tag: "v99.0.0".into(), + version: craft_update::parse_version("99.0.0"), + name: "LightCraft 99.0.0".into(), + html_url: "https://github.com/storytold/lightcraft/releases/tag/v99.0.0".into(), + ..Default::default() + }; + app.updates_rt.updater.offer(release); + + let mut output = ctx.run_ui(egui::RawInput::default(), |ui| { + show(&mut app, ui.ctx()); + }); + output.textures_delta.clear(); + assert!(app.updates_rt.updater.prompt_open()); + } +} diff --git a/docs/showcase/__pycache__/run.cpython-314.pyc b/docs/showcase/__pycache__/run.cpython-314.pyc new file mode 100644 index 000000000..de0f9a97e Binary files /dev/null and b/docs/showcase/__pycache__/run.cpython-314.pyc differ diff --git a/docs/showcase/run.py b/docs/showcase/run.py index 828f8decc..05de581af 100644 --- a/docs/showcase/run.py +++ b/docs/showcase/run.py @@ -14,8 +14,7 @@ def _jsonl_path(arg): if not path.is_file(): sys.exit(f"not a readable file: {arg}") try: - with path.open("rb"): - pass + path.open("rb").close() except OSError: sys.exit(f"not a readable file: {arg}") return path diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 0f1b1f77d..c68c7dca2 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -14,4 +14,4 @@ serde_json = { workspace = true } sha2 = { workspace = true } # precompressed web bundle (`cargo xtask web`); pure Rust flate2 = "1" -brotli = { version = "8", default-features = false, features = ["std"] } +brotli = { version = "9", default-features = false, features = ["std"] }