From ad6e2a06b2fae8cd9c5388825152efec2b5fc0a6 Mon Sep 17 00:00:00 2001 From: newbie-461 <139617217+newbie-461@users.noreply.github.com> Date: Mon, 28 Apr 2025 21:12:31 +0000 Subject: [PATCH] AVIF basic support --- Cargo.lock | 32 +- dom/media/MediaData.cpp | 10 +- gfx/2d/Types.h | 141 +- gfx/layers/ImageContainer.h | 9 + gfx/thebes/gfxUtils.cpp | 41 + gfx/thebes/gfxUtils.h | 5 + gfx/ycbcr/YCbCrUtils.cpp | 231 +- gfx/ycbcr/YCbCrUtils.h | 30 +- gfx/ycbcr/chromium_types.h | 12 +- gfx/ycbcr/moz.build | 2 + gfx/ycbcr/scale_yuv_argb.cpp | 2 + gfx/ycbcr/ycbcr_to_rgb565.cpp | 1 + gfx/ycbcr/yuv_convert.cpp | 211 +- gfx/ycbcr/yuv_convert.h | 35 +- image/DecoderFactory.cpp | 17 +- image/DecoderFactory.h | 1 + image/decoders/moz.build | 7 + image/decoders/nsAVIFDecoder.cpp | 1445 ++++++++ image/decoders/nsAVIFDecoder.h | 77 + image/imgLoader.cpp | 7 + layout/build/components.conf | 1 + .../libyuv/include/libyuv/convert_argb.h | 42 + media/libyuv/libyuv/include/libyuv/row.h | 8 + media/libyuv/libyuv/libyuv.gyp | 6 + media/libyuv/libyuv/libyuv_test.gyp | 6 + media/libyuv/libyuv/source/convert_argb.cc | 69 +- media/libyuv/libyuv/source/row_common.cc | 164 + media/libyuv/moz.build | 2 + media/mp4parse-rust/mp4parse.h | 3 + .../mp4parse/.cargo-checksum.json | 1 + media/mp4parse-rust/mp4parse/Cargo.toml | 27 +- media/mp4parse-rust/mp4parse/LICENSE | 373 ++ media/mp4parse-rust/mp4parse/README.md | 2 + media/mp4parse-rust/mp4parse/src/boxes.rs | 106 +- media/mp4parse-rust/mp4parse/src/lib.rs | 3066 +++++++++++++---- media/mp4parse-rust/mp4parse/src/tests.rs | 231 +- media/mp4parse-rust/mp4parse/src/unstable.rs | 546 +++ .../mp4parse_capi/.cargo-checksum.json | 1 + media/mp4parse-rust/mp4parse_capi/Cargo.toml | 21 +- media/mp4parse-rust/mp4parse_capi/LICENSE | 373 ++ media/mp4parse-rust/mp4parse_capi/README.md | 2 + .../mp4parse-rust/mp4parse_capi/cbindgen.toml | 33 +- media/mp4parse-rust/mp4parse_capi/src/lib.rs | 994 ++---- modules/libpref/init/StaticPrefList.yaml | 26 + netwerk/mime/nsMimeTypes.h | 1 + third_party/rust/ahash/.cargo-checksum.json | 1 + third_party/rust/ahash/Cargo.toml | 91 + third_party/rust/ahash/LICENSE-APACHE | 201 ++ third_party/rust/ahash/LICENSE-MIT | 25 + third_party/rust/ahash/README.md | 245 ++ third_party/rust/ahash/rustfmt.toml | 1 + .../smhasher/0001-Add-support-for-aHash.patch | 135 + .../smhasher/0002-Add-support-for-aHash.patch | 269 ++ .../rust/ahash/smhasher/ahashOutput.txt | 1516 ++++++++ .../rust/ahash/smhasher/clone_smhasher.sh | 1 + .../rust/ahash/smhasher/fallbackOutput.txt | 1467 ++++++++ third_party/rust/ahash/src/aes_hash.rs | 291 ++ third_party/rust/ahash/src/convert.rs | 172 + third_party/rust/ahash/src/fallback_hash.rs | 223 ++ third_party/rust/ahash/src/hash_map.rs | 177 + .../rust/ahash/src/hash_quality_test.rs | 451 +++ third_party/rust/ahash/src/hash_set.rs | 267 ++ third_party/rust/ahash/src/lib.rs | 203 ++ third_party/rust/ahash/src/operations.rs | 277 ++ third_party/rust/ahash/src/random_state.rs | 153 + third_party/rust/ahash/src/specialize.rs | 162 + third_party/rust/ahash/tests/bench.rs | 224 ++ third_party/rust/ahash/tests/map_tests.rs | 204 ++ third_party/rust/ahash/tests/nopanic.rs | 54 + .../fallible_collections/.cargo-checksum.json | 1 + .../rust/fallible_collections/Cargo.toml | 28 + .../rust/fallible_collections/LICENSE-APACHE | 201 ++ .../rust/fallible_collections/LICENSE-MIT | 25 + .../rust/fallible_collections/README.md | 73 + .../rust/fallible_collections/src/arc.rs | 52 + .../rust/fallible_collections/src/boxed.rs | 125 + .../rust/fallible_collections/src/btree.rs | 20 + .../fallible_collections/src/btree/map.rs | 2684 +++++++++++++++ .../fallible_collections/src/btree/node.rs | 1673 +++++++++ .../fallible_collections/src/btree/search.rs | 66 + .../fallible_collections/src/btree/set.rs | 1346 ++++++++ .../rust/fallible_collections/src/format.rs | 46 + .../rust/fallible_collections/src/hashmap.rs | 116 + .../rust/fallible_collections/src/lib.rs | 81 + .../rust/fallible_collections/src/rc.rs | 35 + .../fallible_collections/src/try_clone.rs | 39 + .../rust/fallible_collections/src/vec.rs | 921 +++++ third_party/rust/mime_guess/src/mime_types.rs | 1 + .../mediasniffer/nsMediaSniffer.cpp | 3 +- .../components/mediasniffer/nsMediaSniffer.h | 2 + toolkit/components/telemetry/Histograms.json | 223 ++ toolkit/library/gtest/rust/Cargo.toml | 2 +- toolkit/library/rust/Cargo.toml | 2 +- toolkit/library/rust/gkrust-features.mozbuild | 4 +- toolkit/library/rust/shared/Cargo.toml | 2 +- .../exthandler/nsExternalHelperAppService.cpp | 2 + 96 files changed, 21264 insertions(+), 1739 deletions(-) create mode 100644 image/decoders/nsAVIFDecoder.cpp create mode 100644 image/decoders/nsAVIFDecoder.h create mode 100644 media/mp4parse-rust/mp4parse/.cargo-checksum.json create mode 100644 media/mp4parse-rust/mp4parse/LICENSE create mode 100644 media/mp4parse-rust/mp4parse/README.md create mode 100644 media/mp4parse-rust/mp4parse/src/unstable.rs create mode 100644 media/mp4parse-rust/mp4parse_capi/.cargo-checksum.json create mode 100644 media/mp4parse-rust/mp4parse_capi/LICENSE create mode 100644 media/mp4parse-rust/mp4parse_capi/README.md create mode 100644 third_party/rust/ahash/.cargo-checksum.json create mode 100644 third_party/rust/ahash/Cargo.toml create mode 100644 third_party/rust/ahash/LICENSE-APACHE create mode 100644 third_party/rust/ahash/LICENSE-MIT create mode 100644 third_party/rust/ahash/README.md create mode 100644 third_party/rust/ahash/rustfmt.toml create mode 100644 third_party/rust/ahash/smhasher/0001-Add-support-for-aHash.patch create mode 100644 third_party/rust/ahash/smhasher/0002-Add-support-for-aHash.patch create mode 100644 third_party/rust/ahash/smhasher/ahashOutput.txt create mode 100644 third_party/rust/ahash/smhasher/clone_smhasher.sh create mode 100644 third_party/rust/ahash/smhasher/fallbackOutput.txt create mode 100644 third_party/rust/ahash/src/aes_hash.rs create mode 100644 third_party/rust/ahash/src/convert.rs create mode 100644 third_party/rust/ahash/src/fallback_hash.rs create mode 100644 third_party/rust/ahash/src/hash_map.rs create mode 100644 third_party/rust/ahash/src/hash_quality_test.rs create mode 100644 third_party/rust/ahash/src/hash_set.rs create mode 100644 third_party/rust/ahash/src/lib.rs create mode 100644 third_party/rust/ahash/src/operations.rs create mode 100644 third_party/rust/ahash/src/random_state.rs create mode 100644 third_party/rust/ahash/src/specialize.rs create mode 100644 third_party/rust/ahash/tests/bench.rs create mode 100644 third_party/rust/ahash/tests/map_tests.rs create mode 100644 third_party/rust/ahash/tests/nopanic.rs create mode 100644 third_party/rust/fallible_collections/.cargo-checksum.json create mode 100644 third_party/rust/fallible_collections/Cargo.toml create mode 100644 third_party/rust/fallible_collections/LICENSE-APACHE create mode 100644 third_party/rust/fallible_collections/LICENSE-MIT create mode 100644 third_party/rust/fallible_collections/README.md create mode 100644 third_party/rust/fallible_collections/src/arc.rs create mode 100644 third_party/rust/fallible_collections/src/boxed.rs create mode 100644 third_party/rust/fallible_collections/src/btree.rs create mode 100644 third_party/rust/fallible_collections/src/btree/map.rs create mode 100644 third_party/rust/fallible_collections/src/btree/node.rs create mode 100644 third_party/rust/fallible_collections/src/btree/search.rs create mode 100644 third_party/rust/fallible_collections/src/btree/set.rs create mode 100644 third_party/rust/fallible_collections/src/format.rs create mode 100644 third_party/rust/fallible_collections/src/hashmap.rs create mode 100644 third_party/rust/fallible_collections/src/lib.rs create mode 100644 third_party/rust/fallible_collections/src/rc.rs create mode 100644 third_party/rust/fallible_collections/src/try_clone.rs create mode 100644 third_party/rust/fallible_collections/src/vec.rs diff --git a/Cargo.lock b/Cargo.lock index 46a7276ad4..a05da4332c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + [[package]] name = "aho-corasick" version = "0.7.6" @@ -946,6 +952,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "fallible_collections" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad9169582543d2cfe9961be1e9eaf4fc42f9aa3483f7c485717b8dde36466ea" +dependencies = [ + "hashbrown", +] + [[package]] name = "filetime_win" version = "0.1.0" @@ -1213,6 +1228,9 @@ name = "hashbrown" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] [[package]] name = "hashglobe" @@ -1708,12 +1726,13 @@ dependencies = [ [[package]] name = "mp4parse" -version = "0.11.4" +version = "0.11.5" dependencies = [ "bitreader", "byteorder", + "env_logger", + "fallible_collections", "log", - "mp4parse_fallible", "num-traits", "static_assertions", ] @@ -1724,20 +1743,15 @@ version = "0.1.0" [[package]] name = "mp4parse_capi" -version = "0.11.2" +version = "0.11.5" dependencies = [ "byteorder", + "fallible_collections", "log", "mp4parse", "num-traits", ] -[[package]] -name = "mp4parse_fallible" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704f773471ac3e7110427b6bdf93184932b19319c9b7717688da5424e519b10a" - [[package]] name = "murmurhash3" version = "0.0.5" diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp index 1a264fe333..ec0db7365e 100644 --- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -422,11 +422,11 @@ already_AddRefed VideoData::CreateAndCopyData( // The naming convention for libyuv and associated utils is word-order. // The naming convention in the gfx stack is byte-order. - ConvertYCbCrAToARGB(aBuffer.mPlanes[0].mData, aBuffer.mPlanes[1].mData, - aBuffer.mPlanes[2].mData, aAlphaPlane.mData, - aBuffer.mPlanes[0].mStride, aBuffer.mPlanes[1].mStride, - buffer.data, buffer.stride, buffer.size.width, - buffer.size.height); + ConvertI420AlphaToARGB(aBuffer.mPlanes[0].mData, aBuffer.mPlanes[1].mData, + aBuffer.mPlanes[2].mData, aAlphaPlane.mData, + aBuffer.mPlanes[0].mStride, aBuffer.mPlanes[1].mStride, + buffer.data, buffer.stride, buffer.size.width, + buffer.size.height); return v.forget(); } diff --git a/gfx/2d/Types.h b/gfx/2d/Types.h index 38e26f1c52..ac8508cd8d 100644 --- a/gfx/2d/Types.h +++ b/gfx/2d/Types.h @@ -188,12 +188,149 @@ inline bool IsOpaque(SurfaceFormat aFormat) { } } +// These are standardized Coding-independent Code Points +// See [Rec. ITU-T H.273 +// (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) +// +// We deliberately use an unscoped enum with fixed uint8_t representation since +// all possible values [0, 255] are legal, but it's unwieldy to declare 200+ +// "RESERVED" enumeration values. Having a fixed underlying type avoids any +// potential UB and avoids the need for a cast when passing these values across +// FFI to functions like qcms_profile_create_cicp. +namespace CICP { +enum ColourPrimaries : uint8_t { + CP_RESERVED_MIN = 0, // 0, 3, [13, 21], [23, 255] are all reserved + CP_BT709 = 1, + CP_UNSPECIFIED = 2, + CP_BT470M = 4, + CP_BT470BG = 5, + CP_BT601 = 6, + CP_SMPTE240 = 7, + CP_GENERIC_FILM = 8, + CP_BT2020 = 9, + CP_XYZ = 10, + CP_SMPTE431 = 11, + CP_SMPTE432 = 12, + CP_EBU3213 = 22, +}; + +inline bool IsReserved(ColourPrimaries aIn) { + switch (aIn) { + case CP_BT709: + case CP_UNSPECIFIED: + case CP_BT470M: + case CP_BT470BG: + case CP_BT601: + case CP_SMPTE240: + case CP_GENERIC_FILM: + case CP_BT2020: + case CP_XYZ: + case CP_SMPTE431: + case CP_SMPTE432: + case CP_EBU3213: + return false; + default: + return true; + } +} + +enum TransferCharacteristics : uint8_t { + TC_RESERVED_MIN = 0, // 0, 3, [19, 255] are all reserved + TC_BT709 = 1, + TC_UNSPECIFIED = 2, + TC_BT470M = 4, + TC_BT470BG = 5, + TC_BT601 = 6, + TC_SMPTE240 = 7, + TC_LINEAR = 8, + TC_LOG_100 = 9, + TC_LOG_100_SQRT10 = 10, + TC_IEC61966 = 11, + TC_BT_1361 = 12, + TC_SRGB = 13, + TC_BT2020_10BIT = 14, + TC_BT2020_12BIT = 15, + TC_SMPTE2084 = 16, + TC_SMPTE428 = 17, + TC_HLG = 18, +}; + +inline bool IsReserved(TransferCharacteristics aIn) { + switch (aIn) { + case TC_BT709: + case TC_UNSPECIFIED: + case TC_BT470M: + case TC_BT470BG: + case TC_BT601: + case TC_SMPTE240: + case TC_LINEAR: + case TC_LOG_100: + case TC_LOG_100_SQRT10: + case TC_IEC61966: + case TC_BT_1361: + case TC_SRGB: + case TC_BT2020_10BIT: + case TC_BT2020_12BIT: + case TC_SMPTE2084: + case TC_SMPTE428: + case TC_HLG: + return false; + default: + return true; + } +} + +enum MatrixCoefficients : uint8_t { + MC_IDENTITY = 0, + MC_BT709 = 1, + MC_UNSPECIFIED = 2, + MC_RESERVED_MIN = 3, // 3, [15, 255] are all reserved + MC_FCC = 4, + MC_BT470BG = 5, + MC_BT601 = 6, + MC_SMPTE240 = 7, + MC_YCGCO = 8, + MC_BT2020_NCL = 9, + MC_BT2020_CL = 10, + MC_SMPTE2085 = 11, + MC_CHROMAT_NCL = 12, + MC_CHROMAT_CL = 13, + MC_ICTCP = 14, +}; + +inline bool IsReserved(MatrixCoefficients aIn) { + switch (aIn) { + case MC_IDENTITY: + case MC_BT709: + case MC_UNSPECIFIED: + case MC_RESERVED_MIN: + case MC_FCC: + case MC_BT470BG: + case MC_BT601: + case MC_SMPTE240: + case MC_YCGCO: + case MC_BT2020_NCL: + case MC_BT2020_CL: + case MC_SMPTE2085: + case MC_CHROMAT_NCL: + case MC_CHROMAT_CL: + case MC_ICTCP: + return false; + default: + return true; + } +} +} // namespace CICP + enum class YUVColorSpace : uint8_t { BT601, BT709, BT2020, - // This represents the unknown format and is a valid value. - UNKNOWN, + Identity, // Todo: s/YUVColorSpace/ColorSpace/, s/Identity/SRGB/ + Default = BT709, + _First = BT601, + _Last = Identity, + UNKNOWN = Identity, _NUM_COLORSPACE }; diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h index 5f5e67d72d..405adf2b2b 100644 --- a/gfx/layers/ImageContainer.h +++ b/gfx/layers/ImageContainer.h @@ -648,6 +648,15 @@ struct PlanarYCbCrData { } }; +// This type is currently only used for AVIF and therefore makes some +// AVIF-specific assumptions (e.g., Alpha's bpc and stride is equal to Y's one) +struct PlanarAlphaData { + uint8_t* mChannel = nullptr; + gfx::IntSize mSize = gfx::IntSize(0, 0); + gfx::ColorDepth mDepth = gfx::ColorDepth::COLOR_8; + bool mPremultiplied = false; +}; + /****** Image subtypes for the different formats ******/ /** diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index c7c060eeb0..5afc529f07 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -1176,6 +1176,47 @@ const float kBT2020NarrowYCbCrToRGB_RowMajor[16] = { } } +// Translate from CICP values to the color spaces we support, or return +// Nothing() if there is no appropriate match to let the caller choose +// a default or generate an error. +// +// See Rec. ITU-T H.273 (12/2016) for details on CICP +/* static */ Maybe gfxUtils::CicpToColorSpace( + const CICP::MatrixCoefficients aMatrixCoefficients, + const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) { + switch (aMatrixCoefficients) { + case CICP::MatrixCoefficients::MC_BT2020_NCL: + case CICP::MatrixCoefficients::MC_BT2020_CL: + return Some(gfx::YUVColorSpace::BT2020); + case CICP::MatrixCoefficients::MC_BT601: + return Some(gfx::YUVColorSpace::BT601); + case CICP::MatrixCoefficients::MC_BT709: + return Some(gfx::YUVColorSpace::BT709); + case CICP::MatrixCoefficients::MC_IDENTITY: + return Some(gfx::YUVColorSpace::Identity); + case CICP::MatrixCoefficients::MC_CHROMAT_NCL: + case CICP::MatrixCoefficients::MC_CHROMAT_CL: + case CICP::MatrixCoefficients::MC_UNSPECIFIED: + switch (aColourPrimaries) { + case CICP::ColourPrimaries::CP_BT601: + return Some(gfx::YUVColorSpace::BT601); + case CICP::ColourPrimaries::CP_BT709: + return Some(gfx::YUVColorSpace::BT709); + case CICP::ColourPrimaries::CP_BT2020: + return Some(gfx::YUVColorSpace::BT2020); + default: + MOZ_LOG(aLogger, LogLevel::Debug, + ("Couldn't infer color matrix from primaries: %hhu", + aColourPrimaries)); + return {}; + } + default: + MOZ_LOG(aLogger, LogLevel::Debug, + ("Unsupported color matrix value: %hhu", aMatrixCoefficients)); + return {}; + } +} + /* static */ void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) { WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get()); diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index e8a9afb2dd..7dd28d8ab9 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -178,6 +178,11 @@ class gfxUtils { static const float* YuvToRgbMatrix4x4ColumnMajor( mozilla::gfx::YUVColorSpace aYUVColorSpace); + static mozilla::Maybe CicpToColorSpace( + const mozilla::gfx::CICP::MatrixCoefficients, + const mozilla::gfx::CICP::ColourPrimaries, + mozilla::LazyLogModule& aLogger); + /** * Creates a copy of aSurface, but having the SurfaceFormat aFormat. * diff --git a/gfx/ycbcr/YCbCrUtils.cpp b/gfx/ycbcr/YCbCrUtils.cpp index 9d86634b7d..b07eb019e3 100644 --- a/gfx/ycbcr/YCbCrUtils.cpp +++ b/gfx/ycbcr/YCbCrUtils.cpp @@ -1,4 +1,5 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -9,10 +10,13 @@ #include "YCbCrUtils.h" #include "yuv_convert.h" #include "ycbcr_to_rgb565.h" +#include "libyuv.h" namespace mozilla { namespace gfx { +// clang-format off + void GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData& aData, SurfaceFormat& aSuggestedFormat, @@ -76,31 +80,41 @@ ConvertYCbCr16to8Line(uint8_t* aDst, int aHeight, int aBitDepth) { - uint16_t mask = (1 << aBitDepth) - 1; - - for (int i = 0; i < aHeight; i++) { - for (int j = 0; j < aWidth; j++) { - uint16_t val = (aSrc[j] & mask) >> (aBitDepth - 8); - aDst[j] = val; - } - aDst += aStride; - aSrc += aStride16; + // These values from from the comment on from libyuv's Convert16To8Row_C: + int scale; + switch (aBitDepth) { + case 10: + scale = 16384; + break; + case 12: + scale = 4096; + break; + case 16: + scale = 256; + break; + default: + MOZ_ASSERT_UNREACHABLE("invalid bit depth value"); + return; } + + libyuv::Convert16To8Plane(aSrc, aStride16, aDst, aStride, scale, aWidth, aHeight); } void -ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, - const SurfaceFormat& aDestFormat, - const IntSize& aDestSize, - unsigned char* aDestBuffer, - int32_t aStride) +ConvertYCbCrToRGBInternal(const layers::PlanarYCbCrData& aData, + const SurfaceFormat& aDestFormat, + const IntSize& aDestSize, + unsigned char* aDestBuffer, + int32_t aStride) { // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the - // luma plane is odd sized. - MOZ_ASSERT((aData.mCbCrSize.width == aData.mYSize.width || - aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1) && - (aData.mCbCrSize.height == aData.mYSize.height || - aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1)); + // luma plane is odd sized. Monochrome images have 0-sized CbCr planes + MOZ_ASSERT(aData.mCbCrSize.width == aData.mYSize.width || + aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1 || + aData.mCbCrSize.width == 0); + MOZ_ASSERT(aData.mCbCrSize.height == aData.mYSize.height || + aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1 || + aData.mCbCrSize.height == 0); // Used if converting to 8 bits YUV. UniquePtr yChannel; @@ -123,20 +137,18 @@ ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, dstData.mCbCrStride = (aData.mCbCrSize.width + 31) & ~31; dstData.mYUVColorSpace = aData.mYUVColorSpace; dstData.mColorDepth = ColorDepth::COLOR_8; + dstData.mColorRange = aData.mColorRange; size_t ySize = GetAlignedStride<1>(dstData.mYStride, aData.mYSize.height); size_t cbcrSize = GetAlignedStride<1>(dstData.mCbCrStride, aData.mCbCrSize.height); - if (ySize == 0 || cbcrSize == 0) { + if (ySize == 0) { + MOZ_DIAGNOSTIC_ASSERT(cbcrSize == 0, "CbCr without Y makes no sense"); return; } yChannel = MakeUnique(ySize); - cbChannel = MakeUnique(cbcrSize); - crChannel = MakeUnique(cbcrSize); dstData.mYChannel = yChannel.get(); - dstData.mCbChannel = cbChannel.get(); - dstData.mCrChannel = crChannel.get(); int bitDepth = BitDepthForColorDepth(aData.mColorDepth); @@ -148,21 +160,29 @@ ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, aData.mYSize.height, bitDepth); - ConvertYCbCr16to8Line(dstData.mCbChannel, - dstData.mCbCrStride, - reinterpret_cast(aData.mCbChannel), - aData.mCbCrStride / 2, - aData.mCbCrSize.width, - aData.mCbCrSize.height, - bitDepth); + if (cbcrSize) { + cbChannel = MakeUnique(cbcrSize); + crChannel = MakeUnique(cbcrSize); - ConvertYCbCr16to8Line(dstData.mCrChannel, - dstData.mCbCrStride, - reinterpret_cast(aData.mCrChannel), - aData.mCbCrStride / 2, - aData.mCbCrSize.width, - aData.mCbCrSize.height, - bitDepth); + dstData.mCbChannel = cbChannel.get(); + dstData.mCrChannel = crChannel.get(); + + ConvertYCbCr16to8Line(dstData.mCbChannel, + dstData.mCbCrStride, + reinterpret_cast(aData.mCbChannel), + aData.mCbCrStride / 2, + aData.mCbCrSize.width, + aData.mCbCrSize.height, + bitDepth); + + ConvertYCbCr16to8Line(dstData.mCrChannel, + dstData.mCbCrStride, + reinterpret_cast(aData.mCrChannel), + aData.mCbCrStride / 2, + aData.mCbCrSize.width, + aData.mCbCrSize.height, + bitDepth); + } } YUVType yuvtype = @@ -235,36 +255,129 @@ ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, srcData.mCbCrStride, aStride, yuvtype, - srcData.mYUVColorSpace); + srcData.mYUVColorSpace, + srcData.mColorRange); } +} + +void ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, + const SurfaceFormat& aDestFormat, + const IntSize& aDestSize, unsigned char* aDestBuffer, + int32_t aStride) { + ConvertYCbCrToRGBInternal(aData, aDestFormat, aDestSize, aDestBuffer, + aStride); #if MOZ_BIG_ENDIAN() // libyuv makes endian-correct result, which needs to be swapped to BGRX if (aDestFormat != SurfaceFormat::R5G6B5_UINT16) gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8, aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8, - srcData.mPicSize); + aData.mPicSize); +#endif +} + +void FillAlphaToRGBA(const uint8_t* aAlpha, const int32_t aAlphaStride, + uint8_t* aBuffer, const int32_t aWidth, + const int32_t aHeight, const gfx::SurfaceFormat& aFormat) { + MOZ_ASSERT(aAlphaStride >= aWidth); + MOZ_ASSERT(aFormat == + SurfaceFormat::B8G8R8A8); // required for SurfaceFormatBit::OS_A + + const int bpp = BytesPerPixel(aFormat); + const size_t rgbaStride = aWidth * bpp; + const uint8_t* src = aAlpha; + for (int32_t h = 0; h < aHeight; ++h) { + size_t offset = static_cast(SurfaceFormatBit::OS_A) / 8; + for (int32_t w = 0; w < aWidth; ++w) { + aBuffer[offset] = src[w]; + offset += bpp; + } + src += aAlphaStride; + aBuffer += rgbaStride; + } +} + +void ConvertYCbCrAToARGB(const layers::PlanarYCbCrData& aYCbCr, + const layers::PlanarAlphaData& aAlpha, + const SurfaceFormat& aDestFormat, + const IntSize& aDestSize, unsigned char* aDestBuffer, + int32_t aStride, PremultFunc premultiplyAlphaOp) { + // libyuv makes endian-correct result, so the format needs to be B8G8R8A8. + MOZ_ASSERT(aDestFormat == SurfaceFormat::B8G8R8A8); + MOZ_ASSERT(aAlpha.mSize == aYCbCr.mYSize); + + // libyuv has libyuv::I420AlphaToARGB, but lacks support for 422 and 444. + // Until that's added, we'll rely on our own code to handle this more + // generally, rather than have a special case and more redundant code. + + UniquePtr alphaChannel; + int32_t alphaStride8bpp = 0; + uint8_t* alphaChannel8bpp = nullptr; + + // This function converts non-8-bpc images to 8-bpc. (Bug 1682322) + ConvertYCbCrToRGBInternal(aYCbCr, aDestFormat, aDestSize, aDestBuffer, + aStride); + + if (aYCbCr.mColorDepth != ColorDepth::COLOR_8) { + // These two lines are borrowed from ConvertYCbCrToRGBInternal, since + // there's not a very elegant way of sharing the logic that I can see + alphaStride8bpp = (aAlpha.mSize.width + 31) & ~31; + size_t alphaSize = + GetAlignedStride<1>(alphaStride8bpp, aAlpha.mSize.height); + + alphaChannel = MakeUnique(alphaSize); + + ConvertYCbCr16to8Line(alphaChannel.get(), alphaStride8bpp, + reinterpret_cast(aAlpha.mChannel), + aYCbCr.mYStride / 2, aAlpha.mSize.width, + aAlpha.mSize.height, + BitDepthForColorDepth(aYCbCr.mColorDepth)); + + alphaChannel8bpp = alphaChannel.get(); + } else { + alphaStride8bpp = aYCbCr.mYStride; + alphaChannel8bpp = aAlpha.mChannel; + } + + MOZ_ASSERT(alphaStride8bpp != 0); + MOZ_ASSERT(alphaChannel8bpp); + + FillAlphaToRGBA(alphaChannel8bpp, alphaStride8bpp, aDestBuffer, + aYCbCr.mPicSize.width, aYCbCr.mPicSize.height, aDestFormat); + + if (premultiplyAlphaOp) { + DebugOnly err = + premultiplyAlphaOp(aDestBuffer, aStride, aDestBuffer, aStride, + aYCbCr.mPicSize.width, aYCbCr.mPicSize.height); + MOZ_ASSERT(!err); + } + +#if MOZ_BIG_ENDIAN() + // libyuv makes endian-correct result, which needs to be swapped to BGRA + gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::A8R8G8B8, + aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8A8, + aYCbCr.mPicSize); #endif } void -ConvertYCbCrAToARGB(const uint8_t* aSrcY, - const uint8_t* aSrcU, - const uint8_t* aSrcV, - const uint8_t* aSrcA, - int aSrcStrideYA, int aSrcStrideUV, - uint8_t* aDstARGB, int aDstStrideARGB, - int aWidth, int aHeight) { - - ConvertYCbCrAToARGB32(aSrcY, - aSrcU, - aSrcV, - aSrcA, - aDstARGB, - aWidth, - aHeight, - aSrcStrideYA, - aSrcStrideUV, - aDstStrideARGB); +ConvertI420AlphaToARGB(const uint8_t* aSrcY, + const uint8_t* aSrcU, + const uint8_t* aSrcV, + const uint8_t* aSrcA, + int aSrcStrideYA, int aSrcStrideUV, + uint8_t* aDstARGB, int aDstStrideARGB, + int aWidth, int aHeight) { + + ConvertI420AlphaToARGB32(aSrcY, + aSrcU, + aSrcV, + aSrcA, + aDstARGB, + aWidth, + aHeight, + aSrcStrideYA, + aSrcStrideUV, + aDstStrideARGB); #if MOZ_BIG_ENDIAN() // libyuv makes endian-correct result, which needs to be swapped to BGRA gfx::SwizzleData(aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::A8R8G8B8, diff --git a/gfx/ycbcr/YCbCrUtils.h b/gfx/ycbcr/YCbCrUtils.h index 901db29192..b63e4dabe9 100644 --- a/gfx/ycbcr/YCbCrUtils.h +++ b/gfx/ycbcr/YCbCrUtils.h @@ -1,4 +1,5 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -23,16 +24,25 @@ ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, unsigned char* aDestBuffer, int32_t aStride); -// Currently this function only has support for I420 type. -void -ConvertYCbCrAToARGB(const uint8_t* aSrcY, - const uint8_t* aSrcU, - const uint8_t* aSrcV, - const uint8_t* aSrcA, - int aSrcStrideYA, int aSrcStrideUV, - uint8_t* aDstARGB, int aDstStrideARGB, - int aWidth, int aHeight); +using PremultFunc = int (*)(const uint8_t* src_argb, int src_stride_argb, + uint8_t* dst_argb, int dst_stride_argb, int width, + int height); + +void ConvertYCbCrAToARGB(const layers::PlanarYCbCrData& aYCbCr, + const layers::PlanarAlphaData& aAlpha, + const SurfaceFormat& aDestFormat, + const IntSize& aDestSize, + unsigned char* aDestBuffer, + int32_t aStride, PremultFunc premultiplyAlphaOp); +void +ConvertI420AlphaToARGB(const uint8_t* aSrcY, + const uint8_t* aSrcU, + const uint8_t* aSrcV, + const uint8_t* aSrcA, + int aSrcStrideYA, int aSrcStrideUV, + uint8_t* aDstARGB, int aDstStrideARGB, + int aWidth, int aHeight); } // namespace gfx } // namespace mozilla diff --git a/gfx/ycbcr/chromium_types.h b/gfx/ycbcr/chromium_types.h index 0ef43dc9c1..13f92975b5 100644 --- a/gfx/ycbcr/chromium_types.h +++ b/gfx/ycbcr/chromium_types.h @@ -1,4 +1,5 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef GFX_CHROMIUMTYPES_H @@ -8,15 +9,6 @@ #include "libyuv/basic_types.h" -typedef uint64_t uint64; -typedef int64_t int64; -typedef uint32_t uint32; -typedef int32_t int32; -typedef uint16_t uint16; -typedef int16_t int16; -typedef uint8_t uint8; -typedef int8_t int8; - // From Chromium build_config.h: // Processor architecture detection. For more info on what's defined, see: // http://msdn.microsoft.com/en-us/library/b0084kay.aspx diff --git a/gfx/ycbcr/moz.build b/gfx/ycbcr/moz.build index e24d7c4b8d..aa868e0daa 100644 --- a/gfx/ycbcr/moz.build +++ b/gfx/ycbcr/moz.build @@ -1,3 +1,5 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/gfx/ycbcr/scale_yuv_argb.cpp b/gfx/ycbcr/scale_yuv_argb.cpp index 74bbb5e606..6abe65c182 100644 --- a/gfx/ycbcr/scale_yuv_argb.cpp +++ b/gfx/ycbcr/scale_yuv_argb.cpp @@ -19,6 +19,8 @@ #include "libyuv/scale_row.h" #include "libyuv/video_common.h" +#include "mozilla/gfx/Types.h" + #ifdef __cplusplus namespace libyuv { extern "C" { diff --git a/gfx/ycbcr/ycbcr_to_rgb565.cpp b/gfx/ycbcr/ycbcr_to_rgb565.cpp index 4899c03786..0572e3e094 100644 --- a/gfx/ycbcr/ycbcr_to_rgb565.cpp +++ b/gfx/ycbcr/ycbcr_to_rgb565.cpp @@ -1,3 +1,4 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ diff --git a/gfx/ycbcr/yuv_convert.cpp b/gfx/ycbcr/yuv_convert.cpp index 681d8ce484..32552def6e 100644 --- a/gfx/ycbcr/yuv_convert.cpp +++ b/gfx/ycbcr/yuv_convert.cpp @@ -12,6 +12,7 @@ // YV12 is a full plane of Y and a half height, half width chroma planes // YV16 is a full plane of Y and a full height, half width chroma planes // YV24 is a full plane of Y and a full height, full width chroma planes +// Y8 is a full plane of Y and no chroma planes (i.e., monochrome) // // ARGB pixel format is output, which on little endian is stored as BGRA. // The alpha is set to 255, allowing the application to use RGBA or RGB32. @@ -24,6 +25,7 @@ // Header for low level row functions. #include "yuv_row.h" #include "mozilla/SSE.h" +#include "mozilla/IntegerRange.h" namespace mozilla { @@ -34,6 +36,8 @@ const int kFractionBits = 16; const int kFractionMax = 1 << kFractionBits; const int kFractionMask = ((1 << kFractionBits) - 1); +// clang-format off + YUVType TypeFromSize(int ywidth, int yheight, int cbcrwidth, @@ -45,22 +49,43 @@ YUVType TypeFromSize(int ywidth, else if ((ywidth + 1) / 2 == cbcrwidth && yheight == cbcrheight) { return YV16; } - else { + else if ((ywidth + 1) / 2 == cbcrwidth && (yheight + 1) / 2 == cbcrheight) { return YV12; } + else if (cbcrwidth == 0 && cbcrheight == 0) { + return Y8; + } + else { + MOZ_CRASH("Can't determine YUV type from size"); + } } -libyuv::FourCC FourCCFromYUVType(YUVType aYUVType) -{ - if (aYUVType == YV24) { - return libyuv::FOURCC_I444; - } else if (aYUVType == YV16) { - return libyuv::FOURCC_I422; - } else if (aYUVType == YV12) { - return libyuv::FOURCC_I420; - } else { - return libyuv::FOURCC_ANY; +libyuv::FourCC FourCCFromYUVType(YUVType aYUVType) { + switch (aYUVType) { + case YV24: return libyuv::FOURCC_I444; + case YV16: return libyuv::FOURCC_I422; + case YV12: return libyuv::FOURCC_I420; + case Y8: return libyuv::FOURCC_I400; + default: return libyuv::FOURCC_ANY; + } +} + +int GBRPlanarToARGB(const uint8_t* src_y, int y_pitch, + const uint8_t* src_u, int u_pitch, + const uint8_t* src_v, int v_pitch, + uint8_t* rgb_buf, int rgb_pitch, + int pic_width, int pic_height) { + // libyuv has no native conversion function for this + // fixme: replace with something less awful + for (const auto row : IntegerRange(pic_height)) { + for (const auto col : IntegerRange(pic_width)) { + rgb_buf[rgb_pitch * row + col * 4 + 0] = src_u[u_pitch * row + col]; + rgb_buf[rgb_pitch * row + col * 4 + 1] = src_y[y_pitch * row + col]; + rgb_buf[rgb_pitch * row + col * 4 + 2] = src_v[v_pitch * row + col]; + rgb_buf[rgb_pitch * row + col * 4 + 3] = 255; + } } + return 0; } // Convert a frame of YUV to 32 bit ARGB. @@ -68,7 +93,8 @@ void ConvertYCbCrToRGB32(const uint8* y_buf, const uint8* u_buf, const uint8* v_buf, uint8* rgb_buf, int pic_x, int pic_y, int pic_width, int pic_height, int y_pitch, int uv_pitch, int rgb_pitch, YUVType yuv_type, - YUVColorSpace yuv_color_space) { + YUVColorSpace yuv_color_space, + ColorRange color_range) { // Deprecated function's conversion is accurate. // libyuv converion is a bit inaccurate to get performance. It dynamically // calculates RGB from YUV to use simd. In it, signed byte is used for @@ -79,7 +105,8 @@ void ConvertYCbCrToRGB32(const uint8* y_buf, const uint8* u_buf, // See Bug 1256475. bool use_deprecated = StaticPrefs::gfx_ycbcr_accurate_conversion() || (supports_mmx() && supports_sse() && !supports_sse3() && - yuv_color_space == YUVColorSpace::BT601); + yuv_color_space == YUVColorSpace::BT601 && + color_range == ColorRange::LIMITED); // The deprecated function only support BT601. // See Bug 1210357. if (yuv_color_space != YUVColorSpace::BT601) { @@ -92,73 +119,99 @@ void ConvertYCbCrToRGB32(const uint8* y_buf, const uint8* u_buf, return; } - decltype(libyuv::U444ToARGB)* fConvertYUVToARGB = nullptr; + decltype(libyuv::I420ToARGBMatrix)* fConvertYUVToARGB = nullptr; + const uint8* src_y = nullptr; + const uint8* src_u = nullptr; + const uint8* src_v = nullptr; + const libyuv::YuvConstants* yuv_constant = nullptr; + + switch (yuv_color_space) { + case YUVColorSpace::BT2020: + yuv_constant = color_range == ColorRange::LIMITED + ? &libyuv::kYuv2020Constants + : &libyuv::kYuvV2020Constants; + break; + case YUVColorSpace::BT709: + yuv_constant = color_range == ColorRange::LIMITED + ? &libyuv::kYuvH709Constants + : &libyuv::kYuvF709Constants; + break; + case YUVColorSpace::Identity: + MOZ_ASSERT(yuv_type == YV24, "Identity (aka RGB) with chroma subsampling is unsupported"); + if (yuv_type == YV24) { + break; + } + [[fallthrough]]; // Assuming BT601 for unsupported input is better than crashing + default: + MOZ_FALLTHROUGH_ASSERT("Unsupported YUVColorSpace"); + case YUVColorSpace::BT601: + yuv_constant = color_range == ColorRange::LIMITED + ? &libyuv::kYuvI601Constants + : &libyuv::kYuvJPEGConstants; + break; + } + switch (yuv_type) { case YV24: { - const uint8* src_y = y_buf + y_pitch * pic_y + pic_x; - const uint8* src_u = u_buf + uv_pitch * pic_y + pic_x; - const uint8* src_v = v_buf + uv_pitch * pic_y + pic_x; - switch (yuv_color_space) { - case YUVColorSpace::BT2020: - fConvertYUVToARGB = libyuv::U444ToARGB; - break; - case YUVColorSpace::BT709: - fConvertYUVToARGB = libyuv::H444ToARGB; - break; - default: - fConvertYUVToARGB = libyuv::I444ToARGB; - break; - } - DebugOnly err = - fConvertYUVToARGB(src_y, y_pitch, src_u, uv_pitch, src_v, uv_pitch, + src_y = y_buf + y_pitch * pic_y + pic_x; + src_u = u_buf + uv_pitch * pic_y + pic_x; + src_v = v_buf + uv_pitch * pic_y + pic_x; + + if (yuv_color_space == YUVColorSpace::Identity) { + // Special case for RGB image + DebugOnly err = + GBRPlanarToARGB(src_y, y_pitch, src_u, uv_pitch, src_v, uv_pitch, rgb_buf, rgb_pitch, pic_width, pic_height); - MOZ_ASSERT(!err); + MOZ_ASSERT(!err); + return; + } + + fConvertYUVToARGB = libyuv::I444ToARGBMatrix; break; } case YV16: { - const uint8* src_y = y_buf + y_pitch * pic_y + pic_x; - const uint8* src_u = u_buf + uv_pitch * pic_y + pic_x / 2; - const uint8* src_v = v_buf + uv_pitch * pic_y + pic_x / 2; - switch (yuv_color_space) { - case YUVColorSpace::BT2020: - fConvertYUVToARGB = libyuv::U422ToARGB; - break; - case YUVColorSpace::BT709: - fConvertYUVToARGB = libyuv::H422ToARGB; - break; - default: - fConvertYUVToARGB = libyuv::I422ToARGB; - break; - } - DebugOnly err = - fConvertYUVToARGB(src_y, y_pitch, src_u, uv_pitch, src_v, uv_pitch, - rgb_buf, rgb_pitch, pic_width, pic_height); - MOZ_ASSERT(!err); + src_y = y_buf + y_pitch * pic_y + pic_x; + src_u = u_buf + uv_pitch * pic_y + pic_x / 2; + src_v = v_buf + uv_pitch * pic_y + pic_x / 2; + + fConvertYUVToARGB = libyuv::I422ToARGBMatrix; break; } - default: { - MOZ_ASSERT(yuv_type == YV12); - const uint8* src_y = y_buf + y_pitch * pic_y + pic_x; - const uint8* src_u = u_buf + (uv_pitch * pic_y + pic_x) / 2; - const uint8* src_v = v_buf + (uv_pitch * pic_y + pic_x) / 2; - switch (yuv_color_space) { - case YUVColorSpace::BT2020: - fConvertYUVToARGB = libyuv::U420ToARGB; - break; - case YUVColorSpace::BT709: - fConvertYUVToARGB = libyuv::H420ToARGB; - break; - default: - fConvertYUVToARGB = libyuv::I420ToARGB; - break; - } - DebugOnly err = - fConvertYUVToARGB(src_y, y_pitch, src_u, uv_pitch, src_v, uv_pitch, - rgb_buf, rgb_pitch, pic_width, pic_height); - MOZ_ASSERT(!err); + case YV12: { + src_y = y_buf + y_pitch * pic_y + pic_x; + src_u = u_buf + (uv_pitch * pic_y + pic_x) / 2; + src_v = v_buf + (uv_pitch * pic_y + pic_x) / 2; + + fConvertYUVToARGB = libyuv::I420ToARGBMatrix; break; } + case Y8: { + src_y = y_buf + y_pitch * pic_y + pic_x; + MOZ_ASSERT(u_buf == nullptr); + MOZ_ASSERT(v_buf == nullptr); + + if (color_range == ColorRange::LIMITED) { + DebugOnly err = + libyuv::I400ToARGB(src_y, y_pitch, rgb_buf, rgb_pitch, pic_width, + pic_height); + MOZ_ASSERT(!err); + } else { + DebugOnly err = + libyuv::J400ToARGB(src_y, y_pitch, rgb_buf, rgb_pitch, pic_width, + pic_height); + MOZ_ASSERT(!err); + } + + return; + } + default: + MOZ_ASSERT_UNREACHABLE("Unsupported YUV type"); } + + DebugOnly err = + fConvertYUVToARGB(src_y, y_pitch, src_u, uv_pitch, src_v, uv_pitch, + rgb_buf, rgb_pitch, yuv_constant, pic_width, pic_height); + MOZ_ASSERT(!err); } // Convert a frame of YUV to 32 bit ARGB. @@ -520,16 +573,16 @@ void ScaleYCbCrToRGB32_deprecated(const uint8* y_buf, if (has_mmx) EMMS(); } -void ConvertYCbCrAToARGB32(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* argb_buf, - int pic_width, - int pic_height, - int ya_pitch, - int uv_pitch, - int argb_pitch) { +void ConvertI420AlphaToARGB32(const uint8* y_buf, + const uint8* u_buf, + const uint8* v_buf, + const uint8* a_buf, + uint8* argb_buf, + int pic_width, + int pic_height, + int ya_pitch, + int uv_pitch, + int argb_pitch) { // The downstream graphics stack expects an attenuated input, hence why the // attenuation parameter is set. diff --git a/gfx/ycbcr/yuv_convert.h b/gfx/ycbcr/yuv_convert.h index 3c8a5f1160..5a4ae4bbe0 100644 --- a/gfx/ycbcr/yuv_convert.h +++ b/gfx/ycbcr/yuv_convert.h @@ -2,11 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// clang-format off + #ifndef MEDIA_BASE_YUV_CONVERT_H_ #define MEDIA_BASE_YUV_CONVERT_H_ #include "chromium_types.h" #include "mozilla/gfx/Types.h" +#define int8 int8_t +#define uint8 uint8_t +#define int16 int16_t +#define uint16 uint16_t +#define int32 int32_t +#define uint32 uint32_t +#define int64 int64_t namespace mozilla { @@ -17,7 +26,8 @@ namespace gfx { enum YUVType { YV12 = 0, // YV12 is half width and half height chroma channels. YV16 = 1, // YV16 is half width and full height chroma channels. - YV24 = 2 // YV24 is full width and full height chroma channels. + YV24 = 2, // YV24 is full width and full height chroma channels. + Y8 = 3 // Y8 is monochrome: no chroma channels. }; // Mirror means flip the image horizontally, as in looking in a mirror. @@ -57,7 +67,8 @@ void ConvertYCbCrToRGB32(const uint8* yplane, int uvstride, int rgbstride, YUVType yuv_type, - YUVColorSpace yuv_color_space); + YUVColorSpace yuv_color_space, + ColorRange color_range); void ConvertYCbCrToRGB32_deprecated(const uint8* yplane, const uint8* uplane, @@ -104,16 +115,16 @@ void ScaleYCbCrToRGB32_deprecated(const uint8* yplane, Rotate view_rotate, ScaleFilter filter); -void ConvertYCbCrAToARGB32(const uint8* yplane, - const uint8* uplane, - const uint8* vplane, - const uint8* aplane, - uint8* argbframe, - int pic_width, - int pic_height, - int yastride, - int uvstride, - int argbstride); +void ConvertI420AlphaToARGB32(const uint8* yplane, + const uint8* uplane, + const uint8* vplane, + const uint8* aplane, + uint8* argbframe, + int pic_width, + int pic_height, + int yastride, + int uvstride, + int argbstride); } // namespace gfx } // namespace mozilla diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 2efe781c33..29c592efca 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -19,6 +19,9 @@ #include "nsICODecoder.h" #include "nsIconDecoder.h" #include "nsWebPDecoder.h" +#ifdef MOZ_AV1 +# include "nsAVIFDecoder.h" +#endif namespace mozilla { @@ -75,8 +78,15 @@ DecoderType DecoderFactory::GetDecoderType(const char* aMimeType) { } else if (!strcmp(aMimeType, IMAGE_WEBP) && StaticPrefs::image_webp_enabled()) { type = DecoderType::WEBP; - } + // AVIF + } +#ifdef MOZ_AV1 + else if (!strcmp(aMimeType, IMAGE_AVIF) && + StaticPrefs::image_avif_enabled()) { + type = DecoderType::AVIF; + } +#endif return type; } @@ -114,6 +124,11 @@ already_AddRefed DecoderFactory::GetDecoder(DecoderType aType, case DecoderType::WEBP: decoder = new nsWebPDecoder(aImage); break; +#ifdef MOZ_AV1 + case DecoderType::AVIF: + decoder = new nsAVIFDecoder(aImage); + break; +#endif default: MOZ_ASSERT_UNREACHABLE("Unknown decoder type"); } diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index 714c5666f3..ac5fbac5f6 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -36,6 +36,7 @@ enum class DecoderType { ICO, ICON, WEBP, + AVIF, UNKNOWN }; diff --git a/image/decoders/moz.build b/image/decoders/moz.build index 1f241faaed..97c9b75ca5 100644 --- a/image/decoders/moz.build +++ b/image/decoders/moz.build @@ -29,6 +29,11 @@ UNIFIED_SOURCES += [ 'nsWebPDecoder.cpp', ] +if CONFIG["MOZ_AV1"]: + UNIFIED_SOURCES += [ + "nsAVIFDecoder.cpp", + ] + include('/ipc/chromium/chromium-config.mozbuild') LOCAL_INCLUDES += [ @@ -36,6 +41,8 @@ LOCAL_INCLUDES += [ '/gfx/2d', # Decoders need ImageLib headers. '/image', + # for libyuv::ARGBAttenuate and ::ARGBUnattenuate + "/media/libyuv/libyuv/include", ] LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES'] diff --git a/image/decoders/nsAVIFDecoder.cpp b/image/decoders/nsAVIFDecoder.cpp new file mode 100644 index 0000000000..14728a158d --- /dev/null +++ b/image/decoders/nsAVIFDecoder.cpp @@ -0,0 +1,1445 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" // Must appear first + +#include "nsAVIFDecoder.h" + +#include "aom/aomdx.h" + +//#include "DAV1DDecoder.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/Types.h" +#include "YCbCrUtils.h" +#include "libyuv.h" + +#include "SurfacePipeFactory.h" + +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryComms.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +namespace image { + +using Telemetry::LABELS_AVIF_ALPHA; +using Telemetry::LABELS_AVIF_AOM_DECODE_ERROR; +using Telemetry::LABELS_AVIF_BIT_DEPTH; +using Telemetry::LABELS_AVIF_CICP_CP; +using Telemetry::LABELS_AVIF_CICP_MC; +using Telemetry::LABELS_AVIF_CICP_TC; +using Telemetry::LABELS_AVIF_COLR; +using Telemetry::LABELS_AVIF_DECODE_RESULT; +using Telemetry::LABELS_AVIF_DECODER; +using Telemetry::LABELS_AVIF_ISPE; +using Telemetry::LABELS_AVIF_PIXI; +using Telemetry::LABELS_AVIF_YUV_COLOR_SPACE; + +static LazyLogModule sAVIFLog("AVIFDecoder"); + +static const LABELS_AVIF_BIT_DEPTH gColorDepthLabel[] = { + LABELS_AVIF_BIT_DEPTH::color_8, LABELS_AVIF_BIT_DEPTH::color_10, + LABELS_AVIF_BIT_DEPTH::color_12, LABELS_AVIF_BIT_DEPTH::color_16}; + +static const LABELS_AVIF_YUV_COLOR_SPACE gColorSpaceLabel[] = { + LABELS_AVIF_YUV_COLOR_SPACE::BT601, LABELS_AVIF_YUV_COLOR_SPACE::BT709, + LABELS_AVIF_YUV_COLOR_SPACE::BT2020, LABELS_AVIF_YUV_COLOR_SPACE::identity}; + +static MaybeIntSize GetImageSize(const Mp4parseAvifImage& image) { + // Note this does not take cropping via CleanAperture (clap) into account + const struct Mp4parseImageSpatialExtents* ispe = image.spatial_extents; + // Decoder::PostSize takes int32_t, but ispe contains uint32_t + CheckedInt width = ispe->image_width; + CheckedInt height = ispe->image_height; + + if (width.isValid() && height.isValid()) { + return Some(IntSize{width.value(), height.value()}); + } + + return Nothing(); +} + +// Translate the number of bits per channel into a single ColorDepth. +// Return Nothing if the number of bits per channel is not uniform. +static Maybe BitsPerChannelToBitDepth( + const Mp4parseByteData& bits_per_channel) { + if (bits_per_channel.length == 0) { + return Nothing(); + } + + for (uintptr_t i = 1; i < bits_per_channel.length; ++i) { + if (bits_per_channel.data[i] != bits_per_channel.data[0]) { + // log mismatch + return Nothing(); + } + } + + return Some(bits_per_channel.data[0]); +} + +static void RecordPixiTelemetry(Maybe& pixiBitDepth, + uint8_t aBitstreamBitDepth, + const char* aItemName) { + if (pixiBitDepth.isNothing()) { + AccumulateCategorical(LABELS_AVIF_PIXI::absent); + } else if (pixiBitDepth == Some(aBitstreamBitDepth)) { + AccumulateCategorical(LABELS_AVIF_PIXI::valid); + } else { + MOZ_ASSERT(pixiBitDepth.isSome()); + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("%s item pixi bit depth (%hhu) doesn't match " + "bitstream (%hhu)", + aItemName, *pixiBitDepth, aBitstreamBitDepth)); + AccumulateCategorical(LABELS_AVIF_PIXI::bitstream_mismatch); + } +} + +// Translate the MIAF/HEIF-based orientation transforms (imir, irot) into +// ImageLib's representation. Note that the interpretation of imir was reversed +// Between HEIF (ISO 23008-12:2017) and ISO/IEC 23008-12:2017/DAmd 2. This is +// handled by mp4parse. See mp4parse::read_imir for details. +Orientation GetImageOrientation(const Mp4parseAvifImage& image) { + // Per MIAF (ISO/IEC 23000-22:2019) § 7.3.6.7 + // These properties, if used, shall be indicated to be applied in the + // following order: clean aperture first, then rotation, then mirror. + // The Orientation type does the same order, but opposite rotation direction + + const Mp4parseIrot heifRot = image.image_rotation; + const Mp4parseImir* heifMir = image.image_mirror; + Angle mozRot; + Flip mozFlip; + + if (!heifMir) { // No mirroring + mozFlip = Flip::Unflipped; + + switch (heifRot) { + case MP4PARSE_IROT_D0: + // ⥠ UPWARDS HARPOON WITH BARB LEFT FROM BAR + mozRot = Angle::D0; + break; + case MP4PARSE_IROT_D90: + // ⥞ LEFTWARDS HARPOON WITH BARB DOWN FROM BAR + mozRot = Angle::D270; + break; + case MP4PARSE_IROT_D180: + // ⥝ DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR + mozRot = Angle::D180; + break; + case MP4PARSE_IROT_D270: + // ⥛ RIGHTWARDS HARPOON WITH BARB UP FROM BAR + mozRot = Angle::D90; + break; + default: + MOZ_ASSERT_UNREACHABLE(); + } + } else { + MOZ_ASSERT(heifMir); + mozFlip = Flip::Horizontal; + + enum class HeifFlippedOrientation : uint8_t { + IROT_D0_IMIR_V = (MP4PARSE_IROT_D0 << 1) | MP4PARSE_IMIR_LEFT_RIGHT, + IROT_D0_IMIR_H = (MP4PARSE_IROT_D0 << 1) | MP4PARSE_IMIR_TOP_BOTTOM, + IROT_D90_IMIR_V = (MP4PARSE_IROT_D90 << 1) | MP4PARSE_IMIR_LEFT_RIGHT, + IROT_D90_IMIR_H = (MP4PARSE_IROT_D90 << 1) | MP4PARSE_IMIR_TOP_BOTTOM, + IROT_D180_IMIR_V = (MP4PARSE_IROT_D180 << 1) | MP4PARSE_IMIR_LEFT_RIGHT, + IROT_D180_IMIR_H = (MP4PARSE_IROT_D180 << 1) | MP4PARSE_IMIR_TOP_BOTTOM, + IROT_D270_IMIR_V = (MP4PARSE_IROT_D270 << 1) | MP4PARSE_IMIR_LEFT_RIGHT, + IROT_D270_IMIR_H = (MP4PARSE_IROT_D270 << 1) | MP4PARSE_IMIR_TOP_BOTTOM, + }; + + HeifFlippedOrientation heifO = + HeifFlippedOrientation((heifRot << 1) | *heifMir); + + switch (heifO) { + case HeifFlippedOrientation::IROT_D0_IMIR_V: + case HeifFlippedOrientation::IROT_D180_IMIR_H: + // ⥜ UPWARDS HARPOON WITH BARB RIGHT FROM BAR + mozRot = Angle::D0; + break; + case HeifFlippedOrientation::IROT_D270_IMIR_V: + case HeifFlippedOrientation::IROT_D90_IMIR_H: + // ⥚ LEFTWARDS HARPOON WITH BARB UP FROM BAR + mozRot = Angle::D90; + break; + case HeifFlippedOrientation::IROT_D180_IMIR_V: + case HeifFlippedOrientation::IROT_D0_IMIR_H: + // ⥡ DOWNWARDS HARPOON WITH BARB LEFT FROM BAR + mozRot = Angle::D180; + break; + case HeifFlippedOrientation::IROT_D90_IMIR_V: + case HeifFlippedOrientation::IROT_D270_IMIR_H: + // ⥟ RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR + mozRot = Angle::D270; + break; + default: + MOZ_ASSERT_UNREACHABLE(); + } + } + + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("GetImageOrientation: (rot%d, imir(%s)) -> (Angle%d, " + "Flip%d)", + static_cast(heifRot), + heifMir ? (*heifMir == MP4PARSE_IMIR_LEFT_RIGHT ? "left-right" + : "top-bottom") + : "none", + static_cast(mozRot), static_cast(mozFlip))); + return Orientation{mozRot, mozFlip}; +} + +class AVIFParser { + public: + static Mp4parseStatus Create(const Mp4parseIo* aIo, + UniquePtr& aParserOut) { + MOZ_ASSERT(aIo); + MOZ_ASSERT(!aParserOut); + + UniquePtr p(new AVIFParser(aIo)); + Mp4parseStatus status = p->Init(); + + if (status == MP4PARSE_STATUS_OK) { + MOZ_ASSERT(p->mParser); + aParserOut = std::move(p); + } + + return status; + } + + ~AVIFParser() { + MOZ_LOG(sAVIFLog, LogLevel::Debug, ("Destroy AVIFParser=%p", this)); + } + + Mp4parseAvifImage* GetImage() { + MOZ_ASSERT(mParser); + + if (mAvifImage.isNothing()) { + mAvifImage.emplace(); + Mp4parseStatus status = + mp4parse_avif_get_image(mParser.get(), mAvifImage.ptr()); + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] mp4parse_avif_get_image -> %d; primary_item length: " + "%zu, alpha_item length: %zu", + this, status, mAvifImage->primary_image.coded_data.length, + mAvifImage->alpha_image.coded_data.length)); + if (status != MP4PARSE_STATUS_OK) { + mAvifImage.reset(); + return nullptr; + } + } + return mAvifImage.ptr(); + } + + private: + explicit AVIFParser(const Mp4parseIo* aIo) : mIo(aIo) { + MOZ_ASSERT(mIo); + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("Create AVIFParser=%p, image.avif.compliance_strictness: %d", this, + StaticPrefs::image_avif_compliance_strictness())); + } + + Mp4parseStatus Init() { + MOZ_ASSERT(!mParser); + + Mp4parseAvifParser* parser = nullptr; + Mp4parseStatus status = + mp4parse_avif_new(mIo, + static_cast( + StaticPrefs::image_avif_compliance_strictness()), + &parser); + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] mp4parse_avif_new status: %d", this, status)); + if (status == MP4PARSE_STATUS_OK) { + mParser.reset(parser); + } + return status; + } + + struct FreeAvifParser { + void operator()(Mp4parseAvifParser* aPtr) { mp4parse_avif_free(aPtr); } + }; + + const Mp4parseIo* mIo; + UniquePtr mParser; + Maybe mAvifImage; +}; + +// As well as Maybe, add CICP values (either from the BMFF +// container or the AV1 sequence header) which are used to create the +// colorspace transform. CICP::MatrixCoefficients is only stored for the sake +// of telemetry, since the relevant information for YUV -> RGB conversion is +// stored in mYUVColorSpace. +// +// There are three potential sources of color information for an AVIF: +// 1. ICC profile via a ColourInformationBox (colr) defined in [ISOBMFF] +// § 12.1.5 "Colour information" and [MIAF] § 7.3.6.4 "Colour information +// property" +// 2. NCLX (AKA CICP see [ITU-T H.273]) values in the same ColourInformationBox +// which can have an ICC profile or NCLX values, not both). +// 3. NCLX values in the AV1 bitstream +// +// The 'colr' box is optional, but there are always CICP values in the AV1 +// bitstream, so it is possible to have both. Per ISOBMFF § 12.1.5.1 +// > If colour information is supplied in both this box, and also in the +// > video bitstream, this box takes precedence, and over-rides the +// > information in the bitstream. +// +// If present, the ICC profile takes precedence over CICP values, but only +// specifies the color space, not the matrix coefficients necessary to convert +// YCbCr data (as most AVIF are encoded) to RGB. The matrix coefficients are +// always derived from the CICP values for matrix_coefficients (and potentially +// colour_primaries, but in that case only the CICP values for colour_primaries +// will be used, not anything harvested from the ICC profile). +// +// If there is no ICC profile, the color space transform will be based on the +// CICP values either from the 'colr' box, or if absent/unspecified, the +// decoded AV1 sequence header. +// +// For values that are 2 (meaning unspecified) after trying both, the +// fallback values are: +// - CP: 1 (BT.709/sRGB) +// - TC: 13 (sRGB) +// - MC: 6 (BT.601) +// - Range: Full +// +// Additional details here: +// . Note +// that this contradicts the current version of [MIAF] § 7.3.6.4 which +// specifies MC=1 (BT.709). This is revised in [MIAF DAMD2] and confirmed by +// +// +// The precedence for applying the various values and defaults in the event +// no valid values are found are managed by the following functions. +// +// References: +// [ISOBMFF]: ISO/IEC 14496-12:2020 +// [MIAF]: ISO/IEC 23000-22:2019 +// [MIAF DAMD2]: ISO/IEC 23000-22:2019/FDAmd 2 +// +// [ITU-T H.273]: Rec. ITU-T H.273 (12/2016) +// +struct AVIFDecodedData : layers::PlanarYCbCrData { + Maybe mAlpha = Nothing(); + CICP::ColourPrimaries mColourPrimaries = CICP::CP_UNSPECIFIED; + CICP::TransferCharacteristics mTransferCharacteristics = CICP::TC_UNSPECIFIED; + CICP::MatrixCoefficients mMatrixCoefficients = CICP::MC_UNSPECIFIED; + + void SetCicpValues( + const NclxColourInformation* aNclx, + const CICP::ColourPrimaries aAv1ColourPrimaries, + const CICP::TransferCharacteristics aAv1TransferCharacteristics, + const CICP::MatrixCoefficients aAv1MatrixCoefficients); +}; + +// The gfx::YUVColorSpace value is only used in the conversion from YUV -> RGB. +// Typically this comes directly from the CICP matrix_coefficients value, but +// certain values require additionally considering the colour_primaries value. +// See `gfxUtils::CicpToColorSpace` for details. We return a gfx::YUVColorSpace +// rather than CICP::MatrixCoefficients, since that's what +// `gfx::ConvertYCbCrATo[A]RGB` uses. `aBitstreamColorSpaceFunc` abstracts the +// fact that different decoder libraries require different methods for +// extracting the CICP values from the AV1 bitstream and we don't want to do +// that work unnecessarily because in addition to wasted effort, it would make +// the logging more confusing. +template +static gfx::YUVColorSpace GetAVIFColorSpace(const NclxColourInformation* aNclx, + F&& aBitstreamColorSpaceFunc) { + return ToMaybe(aNclx) + .map([=](const auto& nclx) { + return gfxUtils::CicpToColorSpace( + static_cast(nclx.matrix_coefficients), + static_cast(nclx.colour_primaries), + sAVIFLog); + }) + .valueOrFrom(aBitstreamColorSpaceFunc) + .valueOr(gfx::YUVColorSpace::BT601); +} + +static gfx::ColorRange GetAVIFColorRange(const NclxColourInformation* aNclx, + const gfx::ColorRange av1ColorRange) { + return ToMaybe(aNclx) + .map([=](const auto& nclx) { + return aNclx->full_range_flag ? gfx::ColorRange::FULL + : gfx::ColorRange::LIMITED; + }) + .valueOr(av1ColorRange); +} + +void AVIFDecodedData::SetCicpValues( + const NclxColourInformation* aNclx, + const CICP::ColourPrimaries aAv1ColourPrimaries, + const CICP::TransferCharacteristics aAv1TransferCharacteristics, + const CICP::MatrixCoefficients aAv1MatrixCoefficients) { + auto cp = CICP::ColourPrimaries::CP_UNSPECIFIED; + auto tc = CICP::TransferCharacteristics::TC_UNSPECIFIED; + auto mc = CICP::MatrixCoefficients::MC_UNSPECIFIED; + + if (aNclx) { + cp = static_cast(aNclx->colour_primaries); + tc = static_cast( + aNclx->transfer_characteristics); + mc = static_cast(aNclx->matrix_coefficients); + } + + if (cp == CICP::ColourPrimaries::CP_UNSPECIFIED) { + if (aAv1ColourPrimaries != CICP::ColourPrimaries::CP_UNSPECIFIED) { + cp = aAv1ColourPrimaries; + MOZ_LOG(sAVIFLog, LogLevel::Info, + ("Unspecified colour_primaries value specified in colr box, " + "using AV1 sequence header (%hhu)", + cp)); + } else { + cp = CICP::ColourPrimaries::CP_BT709; + MOZ_LOG(sAVIFLog, LogLevel::Warning, + ("Unspecified colour_primaries value specified in colr box " + "or AV1 sequence header, using fallback value (%hhu)", + cp)); + } + } else if (cp != aAv1ColourPrimaries) { + MOZ_LOG(sAVIFLog, LogLevel::Warning, + ("colour_primaries mismatch: colr box = %hhu, AV1 " + "sequence header = %hhu, using colr box", + cp, aAv1ColourPrimaries)); + } + + if (tc == CICP::TransferCharacteristics::TC_UNSPECIFIED) { + if (aAv1TransferCharacteristics != + CICP::TransferCharacteristics::TC_UNSPECIFIED) { + tc = aAv1TransferCharacteristics; + MOZ_LOG(sAVIFLog, LogLevel::Info, + ("Unspecified transfer_characteristics value specified in " + "colr box, using AV1 sequence header (%hhu)", + tc)); + } else { + tc = CICP::TransferCharacteristics::TC_SRGB; + MOZ_LOG(sAVIFLog, LogLevel::Warning, + ("Unspecified transfer_characteristics value specified in " + "colr box or AV1 sequence header, using fallback value (%hhu)", + tc)); + } + } else if (tc != aAv1TransferCharacteristics) { + MOZ_LOG(sAVIFLog, LogLevel::Warning, + ("transfer_characteristics mismatch: colr box = %hhu, " + "AV1 sequence header = %hhu, using colr box", + tc, aAv1TransferCharacteristics)); + } + + if (mc == CICP::MatrixCoefficients::MC_UNSPECIFIED) { + if (aAv1MatrixCoefficients != CICP::MatrixCoefficients::MC_UNSPECIFIED) { + mc = aAv1MatrixCoefficients; + MOZ_LOG(sAVIFLog, LogLevel::Info, + ("Unspecified matrix_coefficients value specified in " + "colr box, using AV1 sequence header (%hhu)", + mc)); + } else { + mc = CICP::MatrixCoefficients::MC_BT601; + MOZ_LOG(sAVIFLog, LogLevel::Warning, + ("Unspecified matrix_coefficients value specified in " + "colr box or AV1 sequence header, using fallback value (%hhu)", + mc)); + } + } else if (mc != aAv1MatrixCoefficients) { + MOZ_LOG(sAVIFLog, LogLevel::Warning, + ("matrix_coefficients mismatch: colr box = %hhu, " + "AV1 sequence header = %hhu, using colr box", + mc, aAv1TransferCharacteristics)); + } + + mColourPrimaries = cp; + mTransferCharacteristics = tc; + mMatrixCoefficients = mc; +} + +// An interface to do decode and get the decoded data +class AVIFDecoderInterface { + public: + using Dav1dResult = nsAVIFDecoder::Dav1dResult; + using NonAOMCodecError = nsAVIFDecoder::NonAOMCodecError; + using AOMResult = nsAVIFDecoder::AOMResult; + using NonDecoderResult = nsAVIFDecoder::NonDecoderResult; + using DecodeResult = nsAVIFDecoder::DecodeResult; + + virtual ~AVIFDecoderInterface() = default; + + // Set the mDecodedData if Decode() succeeds + virtual DecodeResult Decode(bool aIsMetadataDecode, + const Mp4parseAvifImage& parsedImg) = 0; + // Must be called after Decode() succeeds + AVIFDecodedData& GetDecodedData() { + MOZ_ASSERT(mDecodedData.isSome()); + return mDecodedData.ref(); + } + + protected: + explicit AVIFDecoderInterface(UniquePtr&& aParser) + : mParser(std::move(aParser)) { + MOZ_ASSERT(mParser); + } + + inline static bool IsDecodeSuccess(const DecodeResult& aResult) { + return nsAVIFDecoder::IsDecodeSuccess(aResult); + } + + UniquePtr mParser; + + // The mDecodedData is valid after Decode() succeeds + Maybe mDecodedData; +}; + + +class AOMDecoder final : AVIFDecoderInterface { + public: + ~AOMDecoder() { + MOZ_LOG(sAVIFLog, LogLevel::Verbose, ("Destroy AOMDecoder=%p", this)); + + if (mContext.isSome()) { + aom_codec_err_t r = aom_codec_destroy(mContext.ptr()); + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] aom_codec_destroy -> %d", this, r)); + } + } + + static DecodeResult Create(UniquePtr&& aParser, + UniquePtr& aDecoder) { + UniquePtr d(new AOMDecoder(std::move(aParser))); + aom_codec_err_t e = d->Init(); + if (e == AOM_CODEC_OK) { + MOZ_ASSERT(d->mContext); + aDecoder.reset(d.release()); + } + return AsVariant(AOMResult(e)); + } + + DecodeResult Decode(bool aIsMetadataDecode, + const Mp4parseAvifImage& parsedImg) override { + MOZ_ASSERT(mParser); + MOZ_ASSERT(mContext.isSome()); + MOZ_ASSERT(mDecodedData.isNothing()); + + if (!parsedImg.primary_image.coded_data.data || + !parsedImg.primary_image.coded_data.length) { + return AsVariant(NonDecoderResult::NoPrimaryItem); + } + + aom_image_t* aomImg = nullptr; + DecodeResult r = GetImage(parsedImg.primary_image.coded_data, &aomImg, + aIsMetadataDecode); + if (!IsDecodeSuccess(r)) { + return r; + } + MOZ_ASSERT(aomImg); + + // The aomImg will be released in next GetImage call (aom_codec_decode + // actually). The GetImage could be called again immediately if parsedImg + // contains alpha data. Therefore, we need to copy the image and manage it + // by AOMDecoder itself. + OwnedAOMImage* clonedImg = OwnedAOMImage::CopyFrom(aomImg, false); + if (!clonedImg) { + return AsVariant(NonDecoderResult::OutOfMemory); + } + mOwnedImage.reset(clonedImg); + + if (parsedImg.alpha_image.coded_data.data && + parsedImg.alpha_image.coded_data.length) { + aom_image_t* alphaImg = nullptr; + DecodeResult r = GetImage(parsedImg.alpha_image.coded_data, &alphaImg, + aIsMetadataDecode); + if (!IsDecodeSuccess(r)) { + return r; + } + MOZ_ASSERT(alphaImg); + + OwnedAOMImage* clonedAlphaImg = OwnedAOMImage::CopyFrom(alphaImg, true); + if (!clonedAlphaImg) { + return AsVariant(NonDecoderResult::OutOfMemory); + } + mOwnedAlphaPlane.reset(clonedAlphaImg); + + // Per § 4 of the AVIF spec + // https://aomediacodec.github.io/av1-avif/#auxiliary-images: An AV1 + // Alpha Image Item […] shall be encoded with the same bit depth as the + // associated master AV1 Image Item + MOZ_ASSERT(mOwnedImage->GetImage() && mOwnedAlphaPlane->GetImage()); + if (mOwnedImage->GetImage()->bit_depth != + mOwnedAlphaPlane->GetImage()->bit_depth) { + return AsVariant(NonDecoderResult::AlphaYColorDepthMismatch); + } + } + + MOZ_ASSERT_IF(!mOwnedAlphaPlane, !parsedImg.premultiplied_alpha); + mDecodedData.emplace(AOMImageToToDecodedData( + parsedImg.nclx_colour_information, mOwnedImage->GetImage(), + mOwnedAlphaPlane ? mOwnedAlphaPlane->GetImage() : nullptr, + parsedImg.premultiplied_alpha)); + + return r; + } + + private: + explicit AOMDecoder(UniquePtr&& aParser) + : AVIFDecoderInterface(std::move(aParser)) { + MOZ_LOG(sAVIFLog, LogLevel::Verbose, ("Create AOMDecoder=%p", this)); + } + + aom_codec_err_t Init() { + MOZ_ASSERT(mContext.isNothing()); + + aom_codec_iface_t* iface = aom_codec_av1_dx(); + mContext.emplace(); + aom_codec_err_t r = aom_codec_dec_init( + mContext.ptr(), iface, /* cfg = */ nullptr, /* flags = */ 0); + + MOZ_LOG(sAVIFLog, r == AOM_CODEC_OK ? LogLevel::Verbose : LogLevel::Error, + ("[this=%p] aom_codec_dec_init -> %d, name = %s", this, r, + mContext->name)); + + if (r != AOM_CODEC_OK) { + mContext.reset(); + } + + return r; + } + + DecodeResult GetImage(const Mp4parseByteData& aData, aom_image_t** aImage, + bool aIsMetadataDecode) { + MOZ_ASSERT(mContext.isSome()); + + aom_codec_err_t r = + aom_codec_decode(mContext.ptr(), aData.data, aData.length, nullptr); + + MOZ_LOG(sAVIFLog, r == AOM_CODEC_OK ? LogLevel::Verbose : LogLevel::Error, + ("[this=%p] aom_codec_decode -> %d", this, r)); + + if (aIsMetadataDecode) { + switch (r) { + case AOM_CODEC_OK: + // No need to record any telemetry for the common case + break; + case AOM_CODEC_ERROR: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::error); + break; + case AOM_CODEC_MEM_ERROR: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::mem_error); + break; + case AOM_CODEC_ABI_MISMATCH: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::abi_mismatch); + break; + case AOM_CODEC_INCAPABLE: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::incapable); + break; + case AOM_CODEC_UNSUP_BITSTREAM: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::unsup_bitstream); + break; + case AOM_CODEC_UNSUP_FEATURE: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::unsup_feature); + break; + case AOM_CODEC_CORRUPT_FRAME: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::corrupt_frame); + break; + case AOM_CODEC_INVALID_PARAM: + AccumulateCategorical(LABELS_AVIF_AOM_DECODE_ERROR::invalid_param); + break; + default: + MOZ_ASSERT_UNREACHABLE( + "Unknown aom_codec_err_t value from aom_codec_decode"); + } + } + + if (r != AOM_CODEC_OK) { + return AsVariant(AOMResult(r)); + } + + aom_codec_iter_t iter = nullptr; + aom_image_t* img = aom_codec_get_frame(mContext.ptr(), &iter); + + MOZ_LOG(sAVIFLog, img == nullptr ? LogLevel::Error : LogLevel::Verbose, + ("[this=%p] aom_codec_get_frame -> %p", this, img)); + + if (img == nullptr) { + return AsVariant(AOMResult(NonAOMCodecError::NoFrame)); + } + + const CheckedInt decoded_width = img->d_w; + const CheckedInt decoded_height = img->d_h; + + if (!decoded_height.isValid() || !decoded_width.isValid()) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] image dimensions can't be stored in int: d_w: %u, " + "d_h: %u", + this, img->d_w, img->d_h)); + return AsVariant(AOMResult(NonAOMCodecError::SizeOverflow)); + } + + *aImage = img; + return AsVariant(AOMResult(r)); + } + + class OwnedAOMImage { + public: + ~OwnedAOMImage() { + MOZ_LOG(sAVIFLog, LogLevel::Verbose, ("Destroy OwnedAOMImage=%p", this)); + }; + + static OwnedAOMImage* CopyFrom(aom_image_t* aImage, bool aIsAlpha) { + MOZ_ASSERT(aImage); + UniquePtr img(new OwnedAOMImage()); + if (!img->CloneFrom(aImage, aIsAlpha)) { + return nullptr; + } + return img.release(); + } + + aom_image_t* GetImage() { return mImage.isSome() ? mImage.ptr() : nullptr; } + + private: + OwnedAOMImage() { + MOZ_LOG(sAVIFLog, LogLevel::Verbose, ("Create OwnedAOMImage=%p", this)); + }; + + bool CloneFrom(aom_image_t* aImage, bool aIsAlpha) { + MOZ_ASSERT(aImage); + MOZ_ASSERT(!mImage); + MOZ_ASSERT(!mBuffer); + + uint8_t* srcY = aImage->planes[AOM_PLANE_Y]; + int yStride = aImage->stride[AOM_PLANE_Y]; + int yHeight = aom_img_plane_height(aImage, AOM_PLANE_Y); + size_t yBufSize = yStride * yHeight; + + // If aImage is alpha plane. The data is located in Y channel. + if (aIsAlpha) { + mBuffer = MakeUnique(yBufSize); + if (!mBuffer) { + return false; + } + uint8_t* destY = mBuffer.get(); + memcpy(destY, srcY, yBufSize); + mImage.emplace(*aImage); + mImage->planes[AOM_PLANE_Y] = destY; + + return true; + } + + uint8_t* srcCb = aImage->planes[AOM_PLANE_U]; + int cbStride = aImage->stride[AOM_PLANE_U]; + int cbHeight = aom_img_plane_height(aImage, AOM_PLANE_U); + size_t cbBufSize = cbStride * cbHeight; + + uint8_t* srcCr = aImage->planes[AOM_PLANE_V]; + int crStride = aImage->stride[AOM_PLANE_V]; + int crHeight = aom_img_plane_height(aImage, AOM_PLANE_V); + size_t crBufSize = crStride * crHeight; + + mBuffer = MakeUnique(yBufSize + cbBufSize + crBufSize); + if (!mBuffer) { + return false; + } + + uint8_t* destY = mBuffer.get(); + uint8_t* destCb = destY + yBufSize; + uint8_t* destCr = destCb + cbBufSize; + + memcpy(destY, srcY, yBufSize); + memcpy(destCb, srcCb, cbBufSize); + memcpy(destCr, srcCr, crBufSize); + + mImage.emplace(*aImage); + mImage->planes[AOM_PLANE_Y] = destY; + mImage->planes[AOM_PLANE_U] = destCb; + mImage->planes[AOM_PLANE_V] = destCr; + + return true; + } + + // The mImage's planes are referenced to mBuffer + Maybe mImage; + UniquePtr mBuffer; + }; + + static AVIFDecodedData AOMImageToToDecodedData( + const NclxColourInformation* aNclx, aom_image_t* aImage, + aom_image_t* aAlphaPlane, bool aPremultipliedAlpha); + + Maybe mContext; + UniquePtr mOwnedImage; + UniquePtr mOwnedAlphaPlane; +}; + +/* static */ + +/* static */ +AVIFDecodedData AOMDecoder::AOMImageToToDecodedData( + const NclxColourInformation* aNclx, aom_image_t* aImage, + aom_image_t* aAlphaPlane, bool aPremultipliedAlpha) { + MOZ_ASSERT(aImage); + MOZ_ASSERT(aImage->stride[AOM_PLANE_Y] == aImage->stride[AOM_PLANE_ALPHA]); + MOZ_ASSERT(aImage->stride[AOM_PLANE_Y] >= + aom_img_plane_width(aImage, AOM_PLANE_Y)); + MOZ_ASSERT(aImage->stride[AOM_PLANE_U] == aImage->stride[AOM_PLANE_V]); + MOZ_ASSERT(aImage->stride[AOM_PLANE_U] >= + aom_img_plane_width(aImage, AOM_PLANE_U)); + MOZ_ASSERT(aImage->stride[AOM_PLANE_V] >= + aom_img_plane_width(aImage, AOM_PLANE_V)); + MOZ_ASSERT(aom_img_plane_width(aImage, AOM_PLANE_U) == + aom_img_plane_width(aImage, AOM_PLANE_V)); + MOZ_ASSERT(aom_img_plane_height(aImage, AOM_PLANE_U) == + aom_img_plane_height(aImage, AOM_PLANE_V)); + + AVIFDecodedData data; + + data.mYChannel = aImage->planes[AOM_PLANE_Y]; + data.mYStride = aImage->stride[AOM_PLANE_Y]; + data.mYSize = gfx::IntSize(aom_img_plane_width(aImage, AOM_PLANE_Y), + aom_img_plane_height(aImage, AOM_PLANE_Y)); + data.mYSkip = + aImage->stride[AOM_PLANE_Y] - aom_img_plane_width(aImage, AOM_PLANE_Y); + data.mCbChannel = aImage->planes[AOM_PLANE_U]; + data.mCrChannel = aImage->planes[AOM_PLANE_V]; + data.mCbCrStride = aImage->stride[AOM_PLANE_U]; + data.mCbCrSize = gfx::IntSize(aom_img_plane_width(aImage, AOM_PLANE_U), + aom_img_plane_height(aImage, AOM_PLANE_U)); + data.mCbSkip = + aImage->stride[AOM_PLANE_U] - aom_img_plane_width(aImage, AOM_PLANE_U); + data.mCrSkip = + aImage->stride[AOM_PLANE_V] - aom_img_plane_width(aImage, AOM_PLANE_V); + data.mPicX = 0; + data.mPicY = 0; + data.mPicSize = gfx::IntSize(aImage->d_w, aImage->d_h); + data.mStereoMode = StereoMode::MONO; + data.mColorDepth = ColorDepthForBitDepth(aImage->bit_depth); + + MOZ_ASSERT(aImage->bit_depth == BitDepthForColorDepth(data.mColorDepth)); + + auto av1ColourPrimaries = static_cast(aImage->cp); + auto av1TransferCharacteristics = + static_cast(aImage->tc); + auto av1MatrixCoefficients = + static_cast(aImage->mc); + + data.mYUVColorSpace = GetAVIFColorSpace(aNclx, [=]() { + MOZ_LOG(sAVIFLog, LogLevel::Info, + ("YUVColorSpace cannot be determined from colr box, using AV1 " + "sequence header")); + return gfxUtils::CicpToColorSpace(av1MatrixCoefficients, av1ColourPrimaries, + sAVIFLog); + }); + + gfx::ColorRange av1ColorRange; + if (aImage->range == AOM_CR_STUDIO_RANGE) { + av1ColorRange = gfx::ColorRange::LIMITED; + } else { + MOZ_ASSERT(aImage->range == AOM_CR_FULL_RANGE); + av1ColorRange = gfx::ColorRange::FULL; + } + data.mColorRange = GetAVIFColorRange(aNclx, av1ColorRange); + + data.SetCicpValues(aNclx, av1ColourPrimaries, av1TransferCharacteristics, + av1MatrixCoefficients); + + if (aAlphaPlane) { + MOZ_ASSERT(aAlphaPlane->stride[AOM_PLANE_Y] == data.mYStride); + data.mAlpha.emplace(); + data.mAlpha->mChannel = aAlphaPlane->planes[AOM_PLANE_Y]; + data.mAlpha->mSize = gfx::IntSize(aAlphaPlane->d_w, aAlphaPlane->d_h); + data.mAlpha->mPremultiplied = aPremultipliedAlpha; + } + + return data; +} + +// Wrapper to allow rust to call our read adaptor. +intptr_t nsAVIFDecoder::ReadSource(uint8_t* aDestBuf, uintptr_t aDestBufSize, + void* aUserData) { + MOZ_ASSERT(aDestBuf); + MOZ_ASSERT(aUserData); + + MOZ_LOG(sAVIFLog, LogLevel::Verbose, + ("AVIF ReadSource, aDestBufSize: %zu", aDestBufSize)); + + auto* decoder = reinterpret_cast(aUserData); + + MOZ_ASSERT(decoder->mReadCursor); + + size_t bufferLength = decoder->mBufferedData.end() - decoder->mReadCursor; + size_t n_bytes = std::min(aDestBufSize, bufferLength); + + MOZ_LOG( + sAVIFLog, LogLevel::Verbose, + ("AVIF ReadSource, %zu bytes ready, copying %zu", bufferLength, n_bytes)); + + memcpy(aDestBuf, decoder->mReadCursor, n_bytes); + decoder->mReadCursor += n_bytes; + + return n_bytes; +} + +nsAVIFDecoder::nsAVIFDecoder(RasterImage* aImage) : Decoder(aImage) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] nsAVIFDecoder::nsAVIFDecoder", this)); +} + +nsAVIFDecoder::~nsAVIFDecoder() { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] nsAVIFDecoder::~nsAVIFDecoder", this)); +} + +LexerResult nsAVIFDecoder::DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) { + MOZ_LOG(sAVIFLog, LogLevel::Info, + ("[this=%p] nsAVIFDecoder::DoDecode start", this)); + + DecodeResult result = Decode(aIterator, aOnResume); + + RecordDecodeResultTelemetry(result); + + if (result.is()) { + NonDecoderResult r = result.as(); + if (r == NonDecoderResult::NeedMoreData) { + return LexerResult(Yield::NEED_MORE_DATA); + } + return r == NonDecoderResult::MetadataOk + ? LexerResult(TerminalState::SUCCESS) + : LexerResult(TerminalState::FAILURE); + } + + MOZ_ASSERT(result.is() || result.is() || + result.is()); + // If IsMetadataDecode(), a successful parse should return + // NonDecoderResult::MetadataOk or else continue to the decode stage + MOZ_ASSERT_IF(result.is(), + result.as() != MP4PARSE_STATUS_OK); + auto rv = LexerResult(IsDecodeSuccess(result) ? TerminalState::SUCCESS + : TerminalState::FAILURE); + MOZ_LOG(sAVIFLog, LogLevel::Info, + ("[this=%p] nsAVIFDecoder::DoDecode end", this)); + return rv; +} + +nsAVIFDecoder::DecodeResult nsAVIFDecoder::Decode( + SourceBufferIterator& aIterator, IResumable* aOnResume) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] nsAVIFDecoder::DoDecode", this)); + + // Since the SourceBufferIterator doesn't guarantee a contiguous buffer, + // but the current mp4parse-rust implementation requires it, always buffer + // locally. This keeps the code simpler at the cost of some performance, but + // this implementation is only experimental, so we don't want to spend time + // optimizing it prematurely. + while (!mReadCursor) { + SourceBufferIterator::State state = + aIterator.AdvanceOrScheduleResume(SIZE_MAX, aOnResume); + + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] After advance, iterator state is %d", this, state)); + + switch (state) { + case SourceBufferIterator::WAITING: + return AsVariant(NonDecoderResult::NeedMoreData); + + case SourceBufferIterator::COMPLETE: + mReadCursor = mBufferedData.begin(); + break; + + case SourceBufferIterator::READY: { // copy new data to buffer + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] SourceBufferIterator ready, %zu bytes available", + this, aIterator.Length())); + + bool appendSuccess = + mBufferedData.append(aIterator.Data(), aIterator.Length()); + + if (!appendSuccess) { + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("[this=%p] Failed to append %zu bytes to buffer", this, + aIterator.Length())); + } + + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("unexpected SourceBufferIterator state"); + } + } + + Mp4parseIo io = {nsAVIFDecoder::ReadSource, this}; + UniquePtr parser; + Mp4parseStatus create_parser_status = AVIFParser::Create(&io, parser); + + if (!parser) { + return AsVariant(create_parser_status); + } + + const Mp4parseAvifImage* parsedImagePtr = parser->GetImage(); + if (!parsedImagePtr) { + return AsVariant(NonDecoderResult::NoPrimaryItem); + } + const Mp4parseAvifImage& parsedImg = *parsedImagePtr; + + if (parsedImg.icc_colour_information.data) { + const auto& icc = parsedImg.icc_colour_information; + MOZ_LOG( + sAVIFLog, LogLevel::Debug, + ("[this=%p] colr type ICC: %zu bytes %p", this, icc.length, icc.data)); + } + + if (parsedImg.nclx_colour_information) { + const auto& nclx = *parsedImg.nclx_colour_information; + MOZ_LOG( + sAVIFLog, LogLevel::Debug, + ("[this=%p] colr type CICP: cp/tc/mc/full-range %u/%u/%u/%s", this, + nclx.colour_primaries, nclx.transfer_characteristics, + nclx.matrix_coefficients, nclx.full_range_flag ? "true" : "false")); + } + + if (!parsedImg.icc_colour_information.data && + !parsedImg.nclx_colour_information) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] colr box not present", this)); + } + + if (parsedImg.alpha_image.coded_data.data) { + PostHasTransparency(); + } + + Orientation orientation = StaticPrefs::image_avif_apply_transforms() + ? GetImageOrientation(parsedImg) + : Orientation{}; + MaybeIntSize parsedImageSize = GetImageSize(parsedImg); + Maybe primaryBitDepth = + BitsPerChannelToBitDepth(parsedImg.primary_image.bits_per_channel); + Maybe alphaBitDepth = + BitsPerChannelToBitDepth(parsedImg.alpha_image.bits_per_channel); + + if (parsedImageSize.isSome()) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] Parser returned image size %d x %d (%d/%d bit)", this, + parsedImageSize->width, parsedImageSize->height, + primaryBitDepth.valueOr(0), alphaBitDepth.valueOr(0))); + PostSize(parsedImageSize->width, parsedImageSize->height, orientation); + if (IsMetadataDecode()) { + MOZ_LOG( + sAVIFLog, LogLevel::Debug, + ("[this=%p] Finishing metadata decode without image decode", this)); + return AsVariant(NonDecoderResult::MetadataOk); + } + } else { + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("[this=%p] Parser returned no image size, decoding...", this)); + } + + UniquePtr decoder; + DecodeResult r = AOMDecoder::Create(std::move(parser), decoder); + + if (!IsDecodeSuccess(r)) { + return r; + } + + MOZ_ASSERT(decoder); + r = decoder->Decode(IsMetadataDecode(), parsedImg); + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] Decoder%s->Decode() %s", this, "AOM", + IsDecodeSuccess(r) ? "succeeds" : "fails")); + + if (!IsDecodeSuccess(r)) { + return r; + } + + AVIFDecodedData& decodedData = decoder->GetDecodedData(); + + MOZ_ASSERT(decodedData.mColourPrimaries != + CICP::ColourPrimaries::CP_UNSPECIFIED); + MOZ_ASSERT(decodedData.mTransferCharacteristics != + CICP::TransferCharacteristics::TC_UNSPECIFIED); + MOZ_ASSERT(decodedData.mColorRange <= gfx::ColorRange::_Last); + MOZ_ASSERT(decodedData.mYUVColorSpace <= gfx::YUVColorSpace::_Last); + + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] decodedData.mColorRange: %hhd", this, + static_cast(decodedData.mColorRange))); + + // Technically it's valid but we don't handle it now (Bug 1682318). + if (decodedData.mAlpha && decodedData.mAlpha->mSize != decodedData.mYSize) { + return AsVariant(NonDecoderResult::AlphaYSizeMismatch); + } + + if (parsedImageSize.isNothing()) { + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("[this=%p] Using decoded image size: %d x %d", this, + decodedData.mPicSize.width, decodedData.mPicSize.height)); + PostSize(decodedData.mPicSize.width, decodedData.mPicSize.height, + orientation); + AccumulateCategorical(LABELS_AVIF_ISPE::absent); + } else if (decodedData.mPicSize.width != parsedImageSize->width || + decodedData.mPicSize.height != parsedImageSize->height) { + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("[this=%p] Metadata image size doesn't match decoded image size: " + "(%d x %d) != (%d x %d)", + this, parsedImageSize->width, parsedImageSize->height, + decodedData.mPicSize.width, decodedData.mPicSize.height)); + AccumulateCategorical(LABELS_AVIF_ISPE::bitstream_mismatch); + return AsVariant(NonDecoderResult::MetadataImageSizeMismatch); + } else { + AccumulateCategorical(LABELS_AVIF_ISPE::valid); + } + + const bool hasAlpha = decodedData.mAlpha.isSome(); + if (hasAlpha) { + PostHasTransparency(); + } + + if (IsMetadataDecode()) { + return AsVariant(NonDecoderResult::MetadataOk); + } + + // These data must be recorded after metadata has been decoded + // (IsMetadataDecode()=false) or else they would be double-counted. + AccumulateCategorical( + gColorSpaceLabel[static_cast(decodedData.mYUVColorSpace)]); + AccumulateCategorical( + gColorDepthLabel[static_cast(decodedData.mColorDepth)]); + + RecordPixiTelemetry(primaryBitDepth, + BitDepthForColorDepth(decodedData.mColorDepth), + "primary"); + + if (decodedData.mAlpha) { + AccumulateCategorical(LABELS_AVIF_ALPHA::present); + RecordPixiTelemetry(alphaBitDepth, + BitDepthForColorDepth(decodedData.mAlpha->mDepth), + "alpha"); + } else { + AccumulateCategorical(LABELS_AVIF_ALPHA::absent); + } + + IntSize rgbSize = Size(); + MOZ_ASSERT(rgbSize == decodedData.mPicSize); + + if (parsedImg.nclx_colour_information && + parsedImg.icc_colour_information.data) { + AccumulateCategorical(LABELS_AVIF_COLR::both); + } else if (parsedImg.nclx_colour_information) { + AccumulateCategorical(LABELS_AVIF_COLR::nclx); + } else if (parsedImg.icc_colour_information.data) { + AccumulateCategorical(LABELS_AVIF_COLR::icc); + } else { + AccumulateCategorical(LABELS_AVIF_COLR::absent); + } + + if (CICP::IsReserved(decodedData.mColourPrimaries)) { + AccumulateCategorical(LABELS_AVIF_CICP_CP::RESERVED_REST); + } else { + AccumulateCategorical( + static_cast(decodedData.mColourPrimaries)); + } + + if (CICP::IsReserved(decodedData.mTransferCharacteristics)) { + AccumulateCategorical(LABELS_AVIF_CICP_TC::RESERVED); + } else { + AccumulateCategorical( + static_cast(decodedData.mTransferCharacteristics)); + } + + if (CICP::IsReserved(decodedData.mMatrixCoefficients)) { + AccumulateCategorical(LABELS_AVIF_CICP_MC::RESERVED); + } else { + AccumulateCategorical( + static_cast(decodedData.mMatrixCoefficients)); + } +//__asm("int3"); + // Read color profile + if (0 && mCMSMode != eCMSMode_Off) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] Processing color profile", this)); + + // See comment on AVIFDecodedData + if (parsedImg.icc_colour_information.data) { + const auto& icc = parsedImg.icc_colour_information; + mInProfile = qcms_profile_from_memory(icc.data, icc.length); + } else { + const auto& cp = decodedData.mColourPrimaries; + const auto& tc = decodedData.mTransferCharacteristics; + + if (CICP::IsReserved(cp)) { + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("[this=%p] colour_primaries reserved value (%hhu) is invalid; " + "failing", + this, cp)); + return AsVariant(NonDecoderResult::InvalidCICP); + } + + if (CICP::IsReserved(tc)) { + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("[this=%p] transfer_characteristics reserved value (%hhu) is " + "invalid; failing", + this, tc)); + return AsVariant(NonDecoderResult::InvalidCICP); + } + + MOZ_ASSERT(cp != CICP::ColourPrimaries::CP_UNSPECIFIED && + !CICP::IsReserved(cp)); + MOZ_ASSERT(tc != CICP::TransferCharacteristics::TC_UNSPECIFIED && + !CICP::IsReserved(tc)); + + //mInProfile = qcms_profile_create_cicp(cp, tc); + MOZ_LOG(sAVIFLog, LogLevel::Error, + ("[this=%p] TODO: qcms_profile_create_cicp" + "failing", + this)); + return AsVariant(NonDecoderResult::InvalidCICP); + + } + + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] mInProfile %p", this, mInProfile)); + } else { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] CMSMode::Off, skipping color profile", this)); + } + + if (mInProfile && GetCMSOutputProfile()) { + auto intent = static_cast(gfxPlatform::GetRenderingIntent()); + qcms_data_type inType; + qcms_data_type outType; + + // If we're not mandating an intent, use the one from the image. + if (gfxPlatform::GetRenderingIntent() == -1) { + intent = qcms_profile_get_rendering_intent(mInProfile); + } + + uint32_t profileSpace = qcms_profile_get_color_space(mInProfile); + if (profileSpace != icSigGrayData) { + // If the transform happens with SurfacePipe, it will be in RGBA if we + // have an alpha channel, because the swizzle and premultiplication + // happens after color management. Otherwise it will be in BGRA because + // the swizzle happens at the start. + if (hasAlpha) { + inType = QCMS_DATA_RGBA_8; + outType = QCMS_DATA_RGBA_8; + } else { + inType = gfxPlatform::GetCMSOSRGBAType(); + outType = inType; + } + } else { + if (hasAlpha) { + inType = QCMS_DATA_GRAYA_8; + outType = gfxPlatform::GetCMSOSRGBAType(); + } else { + inType = QCMS_DATA_GRAY_8; + outType = gfxPlatform::GetCMSOSRGBAType(); + } + } + + mTransform = qcms_transform_create(mInProfile, inType, + GetCMSOutputProfile(), outType, intent); + } + + // Get suggested format and size. Note that GetYCbCrToRGBDestFormatAndSize + // force format to be B8G8R8X8 if it's not. + gfx::SurfaceFormat format = SurfaceFormat::OS_RGBX; + gfx::GetYCbCrToRGBDestFormatAndSize(decodedData, format, rgbSize); + if (hasAlpha) { + // We would use libyuv to do the YCbCrA -> ARGB convertion, which only + // works for B8G8R8A8. + format = SurfaceFormat::B8G8R8A8; + } + + const int bytesPerPixel = BytesPerPixel(format); + + const CheckedInt rgbStride = CheckedInt(rgbSize.width) * bytesPerPixel; + const CheckedInt rgbBufLength = rgbStride * rgbSize.height; + + if (!rgbStride.isValid() || !rgbBufLength.isValid()) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] overflow calculating rgbBufLength: rbgSize.width: %d, " + "rgbSize.height: %d, " + "bytesPerPixel: %u", + this, rgbSize.width, rgbSize.height, bytesPerPixel)); + return AsVariant(NonDecoderResult::SizeOverflow); + } + + UniquePtr rgbBuf = MakeUnique(rgbBufLength.value()); + const uint8_t* endOfRgbBuf = {rgbBuf.get() + rgbBufLength.value()}; + + if (!rgbBuf) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] allocation of %u-byte rgbBuf failed", this, + rgbBufLength.value())); + return AsVariant(NonDecoderResult::OutOfMemory); + } + + if (decodedData.mAlpha) { + const auto wantPremultiply = + !bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA); + const bool& hasPremultiply = decodedData.mAlpha->mPremultiplied; + + PremultFunc premultOp = nullptr; + if (wantPremultiply && !hasPremultiply) { + premultOp = libyuv::ARGBAttenuate; + } else if (!wantPremultiply && hasPremultiply) { + premultOp = libyuv::ARGBUnattenuate; + } + + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] calling gfx::ConvertYCbCrAToARGB premultOp: %p", this, + premultOp)); + gfx::ConvertYCbCrAToARGB(decodedData, *decodedData.mAlpha, format, rgbSize, + rgbBuf.get(), rgbStride.value(), premultOp); + } else { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] calling gfx::ConvertYCbCrToRGB", this)); + gfx::ConvertYCbCrToRGB(decodedData, format, rgbSize, rgbBuf.get(), + rgbStride.value()); + } + + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] calling SurfacePipeFactory::CreateSurfacePipe", this)); + Maybe pipe = SurfacePipeFactory::CreateSurfacePipe( + this, rgbSize, OutputSize(), FullFrame(), format, format, Nothing(), + mTransform, SurfacePipeFlags()); + + if (!pipe) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] could not initialize surface pipe", this)); + return AsVariant(NonDecoderResult::PipeInitError); + } + + MOZ_LOG(sAVIFLog, LogLevel::Debug, ("[this=%p] writing to surface", this)); + WriteState writeBufferResult = WriteState::NEED_MORE_DATA; + for (uint8_t* rowPtr = rgbBuf.get(); rowPtr < endOfRgbBuf; + rowPtr += rgbStride.value()) { + writeBufferResult = pipe->WriteBuffer(reinterpret_cast(rowPtr)); + + Maybe invalidRect = pipe->TakeInvalidRect(); + if (invalidRect) { + PostInvalidation(invalidRect->mInputSpaceRect, + Some(invalidRect->mOutputSpaceRect)); + } + + if (writeBufferResult == WriteState::FAILURE) { + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] error writing rowPtr to surface pipe", this)); + + } else if (writeBufferResult == WriteState::FINISHED) { + MOZ_ASSERT(rowPtr + rgbStride.value() == endOfRgbBuf); + } + } + + MOZ_LOG(sAVIFLog, LogLevel::Debug, + ("[this=%p] writing to surface complete", this)); + + if (writeBufferResult == WriteState::FINISHED) { + PostFrameStop(hasAlpha ? Opacity::SOME_TRANSPARENCY + : Opacity::FULLY_OPAQUE); + PostDecodeDone(); + return r; + } + + return AsVariant(NonDecoderResult::WriteBufferError); +} + +/* static */ +bool nsAVIFDecoder::IsDecodeSuccess(const DecodeResult& aResult) { + if (aResult.is()) { + return aResult == DecodeResult(AOMResult(AOM_CODEC_OK)); + } + return false; +} + +void nsAVIFDecoder::RecordDecodeResultTelemetry( + const nsAVIFDecoder::DecodeResult& aResult) { + if (aResult.is()) { + switch (aResult.as()) { + case MP4PARSE_STATUS_OK: + MOZ_ASSERT_UNREACHABLE( + "Expect NonDecoderResult, Dav1dResult or AOMResult"); + break; + case MP4PARSE_STATUS_OOM: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::out_of_memory); + break; + case MP4PARSE_STATUS_UNSUPPORTED_A1LX: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::unsupported_a1lx); + break; + case MP4PARSE_STATUS_UNSUPPORTED_A1OP: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::unsupported_a1op); + break; + case MP4PARSE_STATUS_UNSUPPORTED_CLAP: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::unsupported_clap); + break; + case MP4PARSE_STATUS_UNSUPPORTED_GRID: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::unsupported_grid); + break; + case MP4PARSE_STATUS_UNSUPPORTED_IPRO: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::unsupported_ipro); + break; + case MP4PARSE_STATUS_UNSUPPORTED_LSEL: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::unsupported_lsel); + break; + default: + MOZ_FALLTHROUGH_ASSERT("unexpected Mp4parseStatus value"); + case MP4PARSE_STATUS_BAD_ARG: + case MP4PARSE_STATUS_INVALID: + case MP4PARSE_STATUS_UNSUPPORTED: + case MP4PARSE_STATUS_EOF: + case MP4PARSE_STATUS_IO: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::parse_error); + break; + } + } else if (aResult.is()) { + switch (aResult.as()) { + case NonDecoderResult::NeedMoreData: + break; + case NonDecoderResult::MetadataOk: + break; + case NonDecoderResult::NoPrimaryItem: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::no_primary_item); + break; + case NonDecoderResult::SizeOverflow: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::size_overflow); + break; + case NonDecoderResult::OutOfMemory: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::out_of_memory); + break; + case NonDecoderResult::PipeInitError: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::pipe_init_error); + break; + case NonDecoderResult::WriteBufferError: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::write_buffer_error); + break; + case NonDecoderResult::AlphaYSizeMismatch: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::alpha_y_sz_mismatch); + break; + case NonDecoderResult::AlphaYColorDepthMismatch: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::alpha_y_bpc_mismatch); + break; + case NonDecoderResult::MetadataImageSizeMismatch: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::ispe_mismatch); + break; + case NonDecoderResult::InvalidCICP: + AccumulateCategorical(LABELS_AVIF_DECODE_RESULT::invalid_cicp); + break; + default: + MOZ_ASSERT_UNREACHABLE("unknown NonDecoderResult"); + break; + } + } else { + MOZ_ASSERT(aResult.is() || aResult.is()); + AccumulateCategorical(aResult.is() ? LABELS_AVIF_DECODER::dav1d + : LABELS_AVIF_DECODER::aom); + AccumulateCategorical(IsDecodeSuccess(aResult) + ? LABELS_AVIF_DECODE_RESULT::success + : LABELS_AVIF_DECODE_RESULT::decode_error); + } +} + +Maybe nsAVIFDecoder::SpeedHistogram() const { + //return Some(Telemetry::IMAGE_DECODE_SPEED_AVIF); + return Some(Telemetry::IMAGE_DECODE_SPEED_GIF); +} + +} // namespace image +} // namespace mozilla diff --git a/image/decoders/nsAVIFDecoder.h b/image/decoders/nsAVIFDecoder.h new file mode 100644 index 0000000000..65b578fa7e --- /dev/null +++ b/image/decoders/nsAVIFDecoder.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_nsAVIFDecoder_h +#define mozilla_image_decoders_nsAVIFDecoder_h + +#include "Decoder.h" +#include "mp4parse.h" +#include "SurfacePipe.h" + +#include "aom/aom_decoder.h" +//#include "dav1d/dav1d.h" + +#include "mozilla/Telemetry.h" + +namespace mozilla { +namespace image { +class RasterImage; + +class nsAVIFDecoder final : public Decoder { + public: + virtual ~nsAVIFDecoder(); + + DecoderType GetType() const override { return DecoderType::AVIF; } + + protected: + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + Maybe SpeedHistogram() const override; + + private: + friend class DecoderFactory; + friend class AVIFDecoderInterface; + + // Decoders should only be instantiated via DecoderFactory. + explicit nsAVIFDecoder(RasterImage* aImage); + + static intptr_t ReadSource(uint8_t* aDestBuf, uintptr_t aDestBufSize, + void* aUserData); + + typedef int Dav1dResult; + enum class NonAOMCodecError { NoFrame, SizeOverflow }; + typedef Variant AOMResult; + enum class NonDecoderResult { + NeedMoreData, + MetadataOk, + NoPrimaryItem, + SizeOverflow, + OutOfMemory, + PipeInitError, + WriteBufferError, + AlphaYSizeMismatch, + AlphaYColorDepthMismatch, + MetadataImageSizeMismatch, + InvalidCICP, + }; + using DecodeResult = + Variant; + DecodeResult Decode(SourceBufferIterator& aIterator, IResumable* aOnResume); + + static bool IsDecodeSuccess(const DecodeResult& aResult); + + void RecordDecodeResultTelemetry(const DecodeResult& aResult); + + Vector mBufferedData; + + /// Pointer to the next place to read from mBufferedData + const uint8_t* mReadCursor = nullptr; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_nsAVIFDecoder_h diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index e396551b67..dc9d998d24 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -50,6 +50,7 @@ #include "nsIProgressEventSink.h" #include "nsIProtocolHandler.h" #include "nsImageModule.h" +#include "nsMediaSniffer.h" #include "nsMimeTypes.h" #include "nsNetCID.h" #include "nsNetUtil.h" @@ -2752,6 +2753,8 @@ imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest, nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, uint32_t aLength, nsACString& aContentType) { + nsAutoCString detected; + /* Is it a GIF? */ if (aLength >= 6 && (!strncmp(aContents, "GIF87a", 6) || !strncmp(aContents, "GIF89a", 6))) { @@ -2803,6 +2806,10 @@ nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, !memcmp(aContents + 8, "WEBP", 4)) { aContentType.AssignLiteral(IMAGE_WEBP); + } else if (MatchesMP4(reinterpret_cast(aContents), aLength, + detected) && + detected.Equals(IMAGE_AVIF)) { + aContentType.AssignLiteral(IMAGE_AVIF); } else { /* none of the above? I give up */ return NS_ERROR_NOT_AVAILABLE; diff --git a/layout/build/components.conf b/layout/build/components.conf index 0b8de38664..c70a45a5b3 100644 --- a/layout/build/components.conf +++ b/layout/build/components.conf @@ -46,6 +46,7 @@ content_types = [ 'image/png', 'image/vnd.microsoft.icon', 'image/webp', + 'image/avif', 'image/x-icon', 'image/x-ms-bmp', 'image/x-png', diff --git a/media/libyuv/libyuv/include/libyuv/convert_argb.h b/media/libyuv/libyuv/include/libyuv/convert_argb.h index 7e5bd2efb2..c45194406f 100644 --- a/media/libyuv/libyuv/include/libyuv/convert_argb.h +++ b/media/libyuv/libyuv/include/libyuv/convert_argb.h @@ -693,6 +693,48 @@ int Android420ToABGR(const uint8_t* src_y, int width, int height); +// Convert I420 to ARGB with matrix +LIBYUV_API +int I420ToARGBMatrix(const uint8_t* src_y, + int src_stride_y, + const uint8_t* src_u, + int src_stride_u, + const uint8_t* src_v, + int src_stride_v, + uint8_t* dst_argb, + int dst_stride_argb, + const struct YuvConstants* yuvconstants, + int width, + int height); + +// Convert I422 to ARGB with matrix +LIBYUV_API +int I422ToARGBMatrix(const uint8_t* src_y, + int src_stride_y, + const uint8_t* src_u, + int src_stride_u, + const uint8_t* src_v, + int src_stride_v, + uint8_t* dst_argb, + int dst_stride_argb, + const struct YuvConstants* yuvconstants, + int width, + int height); + +// Convert I444 to ARGB with matrix +LIBYUV_API +int I444ToARGBMatrix(const uint8_t* src_y, + int src_stride_y, + const uint8_t* src_u, + int src_stride_u, + const uint8_t* src_v, + int src_stride_v, + uint8_t* dst_argb, + int dst_stride_argb, + const struct YuvConstants* yuvconstants, + int width, + int height); + // Convert camera sample to ARGB with cropping, rotation and vertical flip. // "sample_size" is needed to parse MJPG. // "dst_stride_argb" number of bytes in a row of the dst_argb plane. diff --git a/media/libyuv/libyuv/include/libyuv/row.h b/media/libyuv/libyuv/include/libyuv/row.h index b1619bfa5b..8441ce8ffe 100644 --- a/media/libyuv/libyuv/include/libyuv/row.h +++ b/media/libyuv/libyuv/include/libyuv/row.h @@ -558,13 +558,21 @@ struct YuvConstants { extern const struct YuvConstants SIMD_ALIGNED(kYuvI601Constants); // BT.601 extern const struct YuvConstants SIMD_ALIGNED(kYuvJPEGConstants); // JPeg extern const struct YuvConstants SIMD_ALIGNED(kYuvH709Constants); // BT.709 +// BT.709 Full +extern const struct YuvConstants SIMD_ALIGNED(kYuvF709Constants); extern const struct YuvConstants SIMD_ALIGNED(kYuv2020Constants); // BT.2020 +// BT.2020 Full +extern const struct YuvConstants SIMD_ALIGNED(kYuvV2020Constants); // Conversion matrix for YVU to BGR extern const struct YuvConstants SIMD_ALIGNED(kYvuI601Constants); // BT.601 extern const struct YuvConstants SIMD_ALIGNED(kYvuJPEGConstants); // JPeg extern const struct YuvConstants SIMD_ALIGNED(kYvuH709Constants); // BT.709 +// BT.709 Full +extern const struct YuvConstants SIMD_ALIGNED(kYvuF709Constants); extern const struct YuvConstants SIMD_ALIGNED(kYvu2020Constants); // BT.2020 +// BT.2020 Full +extern const struct YuvConstants SIMD_ALIGNED(kYvuV2020Constants); #define IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a)-1))) diff --git a/media/libyuv/libyuv/libyuv.gyp b/media/libyuv/libyuv/libyuv.gyp index ee399758a5..776510b32a 100644 --- a/media/libyuv/libyuv/libyuv.gyp +++ b/media/libyuv/libyuv/libyuv.gyp @@ -186,3 +186,9 @@ }, ], # targets. } + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/media/libyuv/libyuv/libyuv_test.gyp b/media/libyuv/libyuv/libyuv_test.gyp index f268ee0af5..5fe154c610 100644 --- a/media/libyuv/libyuv/libyuv_test.gyp +++ b/media/libyuv/libyuv/libyuv_test.gyp @@ -195,3 +195,9 @@ }], ], } + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/media/libyuv/libyuv/source/convert_argb.cc b/media/libyuv/libyuv/source/convert_argb.cc index 967f3d1cbd..d1b8f922af 100644 --- a/media/libyuv/libyuv/source/convert_argb.cc +++ b/media/libyuv/libyuv/source/convert_argb.cc @@ -48,17 +48,18 @@ int ARGBCopy(const uint8_t* src_argb, } // Convert I420 to ARGB with matrix -static int I420ToARGBMatrix(const uint8_t* src_y, - int src_stride_y, - const uint8_t* src_u, - int src_stride_u, - const uint8_t* src_v, - int src_stride_v, - uint8_t* dst_argb, - int dst_stride_argb, - const struct YuvConstants* yuvconstants, - int width, - int height) { +LIBYUV_API +int I420ToARGBMatrix(const uint8_t* src_y, + int src_stride_y, + const uint8_t* src_u, + int src_stride_u, + const uint8_t* src_v, + int src_stride_v, + uint8_t* dst_argb, + int dst_stride_argb, + const struct YuvConstants* yuvconstants, + int width, + int height) { int y; void (*I422ToARGBRow)(const uint8_t* y_buf, const uint8_t* u_buf, const uint8_t* v_buf, uint8_t* rgb_buf, @@ -244,17 +245,18 @@ int U420ToARGB(const uint8_t* src_y, } // Convert I422 to ARGB with matrix -static int I422ToARGBMatrix(const uint8_t* src_y, - int src_stride_y, - const uint8_t* src_u, - int src_stride_u, - const uint8_t* src_v, - int src_stride_v, - uint8_t* dst_argb, - int dst_stride_argb, - const struct YuvConstants* yuvconstants, - int width, - int height) { +LIBYUV_API +int I422ToARGBMatrix(const uint8_t* src_y, + int src_stride_y, + const uint8_t* src_u, + int src_stride_u, + const uint8_t* src_v, + int src_stride_v, + uint8_t* dst_argb, + int dst_stride_argb, + const struct YuvConstants* yuvconstants, + int width, + int height) { int y; void (*I422ToARGBRow)(const uint8_t* y_buf, const uint8_t* u_buf, const uint8_t* v_buf, uint8_t* rgb_buf, @@ -695,17 +697,18 @@ int U422ToARGB(const uint8* src_y, } // Convert I444 to ARGB with matrix -static int I444ToARGBMatrix(const uint8_t* src_y, - int src_stride_y, - const uint8_t* src_u, - int src_stride_u, - const uint8_t* src_v, - int src_stride_v, - uint8_t* dst_argb, - int dst_stride_argb, - const struct YuvConstants* yuvconstants, - int width, - int height) { +LIBYUV_API +int I444ToARGBMatrix(const uint8_t* src_y, + int src_stride_y, + const uint8_t* src_u, + int src_stride_u, + const uint8_t* src_v, + int src_stride_v, + uint8_t* dst_argb, + int dst_stride_argb, + const struct YuvConstants* yuvconstants, + int width, + int height) { int y; void (*I444ToARGBRow)(const uint8_t* y_buf, const uint8_t* u_buf, const uint8_t* v_buf, uint8_t* rgb_buf, diff --git a/media/libyuv/libyuv/source/row_common.cc b/media/libyuv/libyuv/source/row_common.cc index 04b5caa275..6f89da997b 100644 --- a/media/libyuv/libyuv/source/row_common.cc +++ b/media/libyuv/libyuv/source/row_common.cc @@ -1306,6 +1306,88 @@ const struct YuvConstants SIMD_ALIGNED(kYvuH709Constants) = { #undef VR #undef YG +// BT.709 full range YUV to RGB reference +// R = Y + V * 1.5748 +// G = Y - U * 0.18732 - V * 0.46812 +// B = Y + U * 1.8556 +// KR = 0.2126, KB = 0.0722 + +// Y contribution to R,G,B. Scale and bias. +#define YG 16320 /* round(1 * 64 * 256 * 256 / 257) */ +#define YGB 32 /* 64 / 2 */ + +// U and V contributions to R,G,B. +#define UB -119 /* round(-1.8556 * 64) */ +#define UG 12 /* round(0.18732 * 64) */ +#define VG 30 /* round(0.46812 * 64) */ +#define VR -101 /* round(-1.5748 * 64) */ + +// Bias values to round, and subtract 128 from U and V. +#define BB (UB * 128 + YGB) +#define BG (UG * 128 + VG * 128 + YGB) +#define BR (VR * 128 + YGB) + +#if defined(__aarch64__) +const struct YuvConstants SIMD_ALIGNED(kYuvF709Constants) = { + {-UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR}, + {-UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR}, + {UG, VG, UG, VG, UG, VG, UG, VG}, + {UG, VG, UG, VG, UG, VG, UG, VG}, + {BB, BG, BR, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +const struct YuvConstants SIMD_ALIGNED(kYvuF709Constants) = { + {-VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB}, + {-VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB}, + {VG, UG, VG, UG, VG, UG, VG, UG}, + {VG, UG, VG, UG, VG, UG, VG, UG}, + {BR, BG, BB, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +#elif defined(__arm__) +const struct YuvConstants SIMD_ALIGNED(kYuvF709Constants) = { + {-UB, -UB, -UB, -UB, -VR, -VR, -VR, -VR, 0, 0, 0, 0, 0, 0, 0, 0}, + {UG, UG, UG, UG, VG, VG, VG, VG, 0, 0, 0, 0, 0, 0, 0, 0}, + {BB, BG, BR, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +const struct YuvConstants SIMD_ALIGNED(kYvuF709Constants) = { + {-VR, -VR, -VR, -VR, -UB, -UB, -UB, -UB, 0, 0, 0, 0, 0, 0, 0, 0}, + {VG, VG, VG, VG, UG, UG, UG, UG, 0, 0, 0, 0, 0, 0, 0, 0}, + {BR, BG, BB, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +#else +const struct YuvConstants SIMD_ALIGNED(kYuvF709Constants) = { + {UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, + UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0}, + {UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, + UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG}, + {0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, + 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR}, + {BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB}, + {BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG}, + {BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR}, + {YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG}}; +const struct YuvConstants SIMD_ALIGNED(kYvuF709Constants) = { + {VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, + VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0}, + {VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, + VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG}, + {0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, + 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB}, + {BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR}, + {BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG}, + {BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB}, + {YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG}}; +#endif + +#undef BB +#undef BG +#undef BR +#undef YGB +#undef UB +#undef UG +#undef VG +#undef VR +#undef YG + // BT.2020 YUV to RGB reference // R = (Y - 16) * 1.164384 - V * -1.67867 // G = (Y - 16) * 1.164384 - U * 0.187326 - V * -0.65042 @@ -1386,6 +1468,88 @@ const struct YuvConstants SIMD_ALIGNED(kYvu2020Constants) = { #undef VR #undef YG +// BT.2020 full range YUV to RGB reference +// R = Y + V * 1.474600 +// G = Y - U * 0.164553 - V * 0.571353 +// B = Y + U * 1.881400 +// KR = 0.2627; KB = 0.0593 + +// Y contribution to R,G,B. Scale and bias. +#define YG 16320 /* round(1 * 64 * 256 * 256 / 257) */ +#define YGB 32 /* 64 / 2 */ + +// U and V contributions to R,G,B. +#define UB -120 /* round(-1.881400 * 64) */ +#define UG 11 /* round(0.164553 * 64) */ +#define VG 37 /* round(0.571353 * 64) */ +#define VR -94 /* round(-1.474600 * 64) */ + +// Bias values to round, and subtract 128 from U and V. +#define BB (UB * 128 + YGB) +#define BG (UG * 128 + VG * 128 + YGB) +#define BR (VR * 128 + YGB) + +#if defined(__aarch64__) +const struct YuvConstants SIMD_ALIGNED(kYuvV2020Constants) = { + {-UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR}, + {-UB, -VR, -UB, -VR, -UB, -VR, -UB, -VR}, + {UG, VG, UG, VG, UG, VG, UG, VG}, + {UG, VG, UG, VG, UG, VG, UG, VG}, + {BB, BG, BR, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +const struct YuvConstants SIMD_ALIGNED(kYvuV2020Constants) = { + {-VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB}, + {-VR, -UB, -VR, -UB, -VR, -UB, -VR, -UB}, + {VG, UG, VG, UG, VG, UG, VG, UG}, + {VG, UG, VG, UG, VG, UG, VG, UG}, + {BR, BG, BB, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +#elif defined(__arm__) +const struct YuvConstants SIMD_ALIGNED(kYuvV2020Constants) = { + {-UB, -UB, -UB, -UB, -VR, -VR, -VR, -VR, 0, 0, 0, 0, 0, 0, 0, 0}, + {UG, UG, UG, UG, VG, VG, VG, VG, 0, 0, 0, 0, 0, 0, 0, 0}, + {BB, BG, BR, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +const struct YuvConstants SIMD_ALIGNED(kYvuV2020Constants) = { + {-VR, -VR, -VR, -VR, -UB, -UB, -UB, -UB, 0, 0, 0, 0, 0, 0, 0, 0}, + {VG, VG, VG, VG, UG, UG, UG, UG, 0, 0, 0, 0, 0, 0, 0, 0}, + {BR, BG, BB, 0, 0, 0, 0, 0}, + {0x0101 * YG, 0, 0, 0}}; +#else +const struct YuvConstants SIMD_ALIGNED(kYuvV2020Constants) = { + {UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, + UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0}, + {UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, + UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG}, + {0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, + 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR}, + {BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB}, + {BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG}, + {BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR}, + {YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG}}; +const struct YuvConstants SIMD_ALIGNED(kYvuV2020Constants) = { + {VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, + VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0, VR, 0}, + {VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, + VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG, VG, UG}, + {0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, + 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB, 0, UB}, + {BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR, BR}, + {BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG, BG}, + {BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB}, + {YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG, YG}}; +#endif + +#undef BB +#undef BG +#undef BR +#undef YGB +#undef UB +#undef UG +#undef VG +#undef VR +#undef YG + // C reference code that mimics the YUV assembly. // Reads 8 bit YUV and leaves result as 16 bit. diff --git a/media/libyuv/moz.build b/media/libyuv/moz.build index 9e11512c4d..3121978937 100644 --- a/media/libyuv/moz.build +++ b/media/libyuv/moz.build @@ -1,3 +1,5 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/media/mp4parse-rust/mp4parse.h b/media/mp4parse-rust/mp4parse.h index 86e1aadbaf..b14d5d70fd 100644 --- a/media/mp4parse-rust/mp4parse.h +++ b/media/mp4parse-rust/mp4parse.h @@ -5,6 +5,9 @@ #ifndef mozilla_media_mp4parse_rust_mp4parse_h #define mozilla_media_mp4parse_rust_mp4parse_h +#define mp4parse_rust_mp4parse_h +#define MP4PARSE_UNSTABLE_API 1 + #include "mp4parse_ffi_generated.h" // prepend mozilla/media when we namespace this // Add any non-generated support code here diff --git a/media/mp4parse-rust/mp4parse/.cargo-checksum.json b/media/mp4parse-rust/mp4parse/.cargo-checksum.json new file mode 100644 index 0000000000..ec2fe7a4a1 --- /dev/null +++ b/media/mp4parse-rust/mp4parse/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"3969c05e1a5ba7f937776863571b7af940d8a2915ba5a7853c5760a2d71b14b1","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"86cb40854b93f988e3a63ce6fe39d2ce95367f8ca301a5ba50676ff98a0ad791","benches/avif_benchmark.rs":"cd99c0dde025ab40d2cd860f53dc697a1587a48c164c3e5c8adfd40add29d772","src/boxes.rs":"496fd2961c55e798134680a9549ad8e8ba73f22f0330f68cfa6a414d49362f46","src/lib.rs":"a6376b0bdbf9e23cced5aa87fe12d954768c1927320ad6ce2a155237ddca7d92","src/macros.rs":"76c840f9299797527fe71aa5b378ffb01312767372b45cc62deddb19775400ae","src/tests.rs":"c48ff81f52925462eb283ff1706a7ac1cd6bbb7f2103a1354f6f84cfd3a6cf99","src/unstable.rs":"c2cef9a3b2b08a4da66fa0305fce7a117bd99c9b8d57b0a044e1d99dbda6faf5","tests/1x1-black-alpha-50pct-premultiplied.avif":"31a8c235bf2cf601a593a7bc33f7f2779f2d5b2e0cd145897b931fce94b0c0b8","tests/amr_nb_1f.3gp":"d1423e3414ad06b69f8b58d5c916ec353ba2d0402d99dec9f1c88acc33b6a127","tests/amr_wb_1f.3gp":"be635b24097e8757b0c04d70ab28e00417ca113e86108b6c269b79b64b89bcd5","tests/av1C-missing-essential.avif":"a1501254c4071847b2269fe40b81409c389ff14e91cf7c0005a47e6ea97a6803","tests/bad-ipma-flags.avif":"ecde7997b97db1910b9dcc7ca8e3c8957da0e83681ea9008c66dc9f12b78ad19","tests/bad-ipma-version.avif":"7f9a1a0b4ebbf8d800d22eaae5ff78970cc6b811317db6c1467c6883952b7c9b","tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp":"03e5b1264d0a188d77b9e676ba3ce23a801b17aaa11c0343dfd851d6ea4e3a40","tests/clap-basic-1_3x3-to-1x1.avif":"83af9c8196fa93b2475163585a23d0eb5a8f8015d0db8da7a5d6de61adfb1876","tests/clusterfuzz-testcase-minimized-mp4-6093954524250112":"af7044a470732d4e7e34ac7ab5ff038c58b66f09702cbcd774931d7766bbfd35","tests/corrupt/bug-1655846.avif":"e0a5a06225800fadf05f5352503a4cec11af73eef705c43b4acab5f4a99dea50","tests/corrupt/bug-1661347.avif":"31c26561e1d9eafb60f7c5968b82a0859d203d73f17f26b29276256acee12966","tests/corrupt/hdlr-not-first.avif":"2c29308af077209b9c984921b7e36f8fb7ca7cf379cf8eba4c7a91f65bc7a304","tests/corrupt/hdlr-not-pict.avif":"9fe37619606645a95725300a9e34fada9190d1e0b3919881db84353941ca9291","tests/corrupt/imir-before-clap.avif":"22d6b5dacf0ef0be59053beba7564b08037fed859ada2885e3476e0ff0d19c95","tests/corrupt/invalid-avif-colr-multiple-nclx.avif":"7990a995855120dc4f724a6098816595becc35077fcd9e0de8c68300b49c4f1f","tests/corrupt/invalid-avif-colr-multiple-prof.avif":"b077a6b58e3a13ad743ee3f19fbae53b521eab8727606e0dba9bf06384f3121c","tests/corrupt/invalid-avif-colr-multiple-rICC.avif":"88b24d4d588744b9f2cdc03944f28283e9315eb3de7d7d57773a0541137f6529","tests/corrupt/invalid-avif-colr-multiple.zip":"9abddcbc47fde6da20263a29b770c6a9e76c8ab8dc785ef8512f35d9cb3206ed","tests/corrupt/ipma-duplicate-item_id.avif":"ca8c5275b0b8b79c1068489a52d0a5c8f0b4453463971e72b694189f11c10745","tests/corrupt/ipma-duplicate-version-and-flags.avif":"cf8e15ec4b210235f3d68332a1adeb64e35c41b8d8e1e7586ae38b6d9cd8926c","tests/corrupt/ipma-invalid-property-index.avif":"2480e773fa716d22883032d05fd4cf2c6b00fba8796cf4ff286a5d1ba26adff6","tests/corrupt/no-alpha-av1C.avif":"ad3d34d6331db7d9bea0c5f37efb88923520e33e08e7c636a5df435a4575eae7","tests/corrupt/no-av1C.avif":"eeb4fc50930c91465999f787c4a2a3b12de20556da0857be72da5a1a9eaa3f01","tests/corrupt/no-hdlr.avif":"91a1eb70c7b6adf2104e471d7deeeb98084a591d64ce09ba106c27edfbc3a409","tests/corrupt/no-ispe.avif":"4b6edfd8c9b40c25dc40305a6057e32b5e65f40da4a9d810c58dbff53254113f","tests/corrupt/no-pixi-for-alpha.avif":"f8adc3573c79ee25bf6d4dd2693c61661469b28f86a5c7b1d9e41b0e8d2d53bb","tests/corrupt/no-pixi.avif":"4b1776def440dc8b913c170e4479772ee6bbb299b8679f7c564704bd03c9597e","tests/hdlr-nonzero-reserved.avif":"b872dcd7b4f49c6808d6da109cf4fedc26a237c42e8529c5aa8f7130abaf40a9","tests/imir-missing-essential.avif":"b1226e4b1358528befbd3f1126b5caf0c5051b4354777b87e71f6001f3829f87","tests/irot-missing-essential.avif":"b7da1fc1d1b45bb1b7ca3494476e052f711d794a6d010df6870872ed8b9da10e","tests/multiple-extents.avif":"b5549ac68793e155a726d754e565cea0da03fa17833d3545f45c79e13f4c9360","tests/no-mif1.avif":"1442aa6ffaeb9512724287768bfd1850d3aa29a651ef05abb33e5dec2b3ee5c2","tests/overflow.rs":"16b591d8def1a155b3b997622f6ea255536870d99c3d8f97c51755b77a50de3c","tests/public.rs":"5f18c3cd6a6ecf867489e95f9b5ba94335589b7c1c125e2171c1c0d1333a0328","tests/valid-alpha.avif":"9d417a35b9b62ad3ff66ffbc55f16552aacf821a092aa5ef4adff7e746bd4c2f","tests/valid-avif-colr-nclx-and-prof-and-rICC.avif":"ab6f5e786d26f8bcade5993f8b9cca3cd004a3d7fcec76e829f5d0f98cb18e7b","tests/valid-avif-colr-nclx-and-prof.avif":"0e982818de61869fcb85a2a4c2b7b8aeecb3053cbfdc6276987f91204998eefb","tests/valid-avif-colr-nclx-and-rICC.avif":"8530ef1305ff956a0c2912d0b3d1e0fc3a68cf3103e70b04cc2574530389b030","tests/valid-avif-colr-nclx.avif":"345ab58b7b1cb48aba2e21eb8dc5ab0a751a78a752ce1896c59b4bf361992f38","tests/valid-avif-colr-prof-and-rICC.avif":"1f0f085141106885bda78b0879c768818420d8196b39440a36578456a7d50a6c","tests/valid-avif-colr-prof.avif":"5d7aaefb5204ebe1cc296456866b8e46e023748b921a38ee56fd6c776a9733ff","tests/valid-avif-colr-rICC.avif":"e1c7b49bfad5904b484bd5118e6b33b78e2dc708a31a10fcbb0e4a373ed8dbb7","tests/valid.avif":"f0b33e09bf01232e0877df325f47986c0bee7764f2a81c9c908ae109e7dc63c4"},"package":null} \ No newline at end of file diff --git a/media/mp4parse-rust/mp4parse/Cargo.toml b/media/mp4parse-rust/mp4parse/Cargo.toml index b38c6459f3..dc7ffadeee 100644 --- a/media/mp4parse-rust/mp4parse/Cargo.toml +++ b/media/mp4parse-rust/mp4parse/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "mp4parse" -version = "0.11.4" +version = "0.11.5" authors = [ "Ralph Giles ", "Matthew Gregan ", "Alfredo Yang ", "Jon Bauman ", + "Bryce Seager van Dyk ", ] description = "Parser for ISO base media file format (mp4)" @@ -18,6 +19,7 @@ repository = "https://github.com/mozilla/mp4parse-rust" # Avoid complaints about trying to package test files. exclude = [ "*.mp4", + "av1-avif/*" ] [badges] @@ -26,11 +28,28 @@ travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } [dependencies] byteorder = "1.2.1" bitreader = { version = "0.3.2" } -num-traits = "0.2.0" -mp4parse_fallible = { version = "0.0.3", optional = true } +env_logger = "0.6.2" +fallible_collections = { version = "0.4", features = ["std_io"] } +num-traits = "0.2.10" log = "0.4" static_assertions = "1.1.0" [dev-dependencies] test-assembler = "0.1.2" -env_logger = "0.7.1" +walkdir = "2.3.1" +criterion = "0.3" + +[features] +missing-pixi-permitted = [] +3gpp = [] +meta-xml = [] +unstable-api = [] +mp4v = [] + +[[bench]] +name = "avif_benchmark" +harness = false + +# See https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options +[lib] +bench = false diff --git a/media/mp4parse-rust/mp4parse/LICENSE b/media/mp4parse-rust/mp4parse/LICENSE new file mode 100644 index 0000000000..14e2f777f6 --- /dev/null +++ b/media/mp4parse-rust/mp4parse/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/media/mp4parse-rust/mp4parse/README.md b/media/mp4parse-rust/mp4parse/README.md new file mode 100644 index 0000000000..c9e65a4388 --- /dev/null +++ b/media/mp4parse-rust/mp4parse/README.md @@ -0,0 +1,2 @@ +`mp4parse` is a parser for ISO base media file format (mp4) written in rust. +See [the README in the mp4parse-rust repo](https://github.com/mozilla/mp4parse-rust/blob/master/README.md) for more details. \ No newline at end of file diff --git a/media/mp4parse-rust/mp4parse/src/boxes.rs b/media/mp4parse-rust/mp4parse/src/boxes.rs index 6efd83d4e0..d80798c15e 100644 --- a/media/mp4parse-rust/mp4parse/src/boxes.rs +++ b/media/mp4parse-rust/mp4parse/src/boxes.rs @@ -3,11 +3,21 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::fmt; +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; + macro_rules! box_database { - ($($boxenum:ident $boxtype:expr),*,) => { + ($($(#[$attr:meta])* $boxenum:ident $boxtype:expr),*,) => { #[derive(Clone, Copy, PartialEq)] pub enum BoxType { - $($boxenum),*, + $($(#[$attr])* $boxenum),*, UnknownBox(u32), } @@ -15,51 +25,42 @@ macro_rules! box_database { fn from(t: u32) -> BoxType { use self::BoxType::*; match t { - $($boxtype => $boxenum),*, + $($(#[$attr])* $boxtype => $boxenum),*, _ => UnknownBox(t), } } } - impl Into for BoxType { - fn into(self) -> u32 { + impl From for u32 { + fn from(b: BoxType) -> u32 { use self::BoxType::*; - match self { - $($boxenum => $boxtype),*, + match b { + $($(#[$attr])* $boxenum => $boxtype),*, UnknownBox(t) => t, } } } - impl fmt::Debug for BoxType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let fourcc: FourCC = From::from(self.clone()); - write!(f, "{}", fourcc) - } - } } } -#[derive(Default, PartialEq, Clone)] +impl fmt::Debug for BoxType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let fourcc: FourCC = From::from(*self); + fourcc.fmt(f) + } +} + +#[derive(Default, Eq, Hash, PartialEq, Clone)] pub struct FourCC { - pub value: String, + pub value: [u8; 4], } impl From for FourCC { fn from(number: u32) -> FourCC { - let mut box_chars = Vec::new(); - for x in 0..4 { - let c = (number >> (x * 8) & 0x0000_00FF) as u8; - box_chars.push(c); + FourCC { + value: number.to_be_bytes(), } - box_chars.reverse(); - - let box_string = match String::from_utf8(box_chars) { - Ok(t) => t, - _ => String::from("null"), // error to retrieve fourcc - }; - - FourCC { value: box_string } } } @@ -70,23 +71,30 @@ impl From for FourCC { } } -impl<'a> From<&'a str> for FourCC { - fn from(v: &'a str) -> FourCC { - FourCC { - value: v.to_owned(), - } +impl From<[u8; 4]> for FourCC { + fn from(v: [u8; 4]) -> FourCC { + FourCC { value: v } } } impl fmt::Debug for FourCC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.value) + match std::str::from_utf8(&self.value) { + Ok(s) => f.write_str(s), + Err(_) => self.value.fmt(f), + } } } impl fmt::Display for FourCC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.value) + f.write_str(std::str::from_utf8(&self.value).unwrap_or("null")) + } +} + +impl PartialEq<&[u8; 4]> for FourCC { + fn eq(&self, other: &&[u8; 4]) -> bool { + self.value.eq(*other) } } @@ -107,6 +115,20 @@ box_database!( MediaHeaderBox 0x6d64_6864, // "mdhd" HandlerBox 0x6864_6c72, // "hdlr" MediaInformationBox 0x6d69_6e66, // "minf" + ItemReferenceBox 0x6972_6566, // "iref" + ItemPropertiesBox 0x6970_7270, // "iprp" + ItemPropertyContainerBox 0x6970_636f, // "ipco" + ItemPropertyAssociationBox 0x6970_6d61, // "ipma" + ColourInformationBox 0x636f_6c72, // "colr" + ImageSpatialExtentsProperty 0x6973_7065, // "ispe" + PixelInformationBox 0x7069_7869, // "pixi" + AuxiliaryTypeProperty 0x6175_7843, // "auxC" + CleanApertureBox 0x636c_6170, // "clap" + ImageRotation 0x6972_6f74, // "irot" + ImageMirror 0x696d_6972, // "imir" + OperatingPointSelectorProperty 0x6131_6f70, // "a1op" + AV1LayeredImageIndexingProperty 0x6131_6c78, // "a1lx" + LayerSelectorProperty 0x6c73_656c, // "lsel" SampleTableBox 0x7374_626c, // "stbl" SampleDescriptionBox 0x7374_7364, // "stsd" TimeToSampleBox 0x7374_7473, // "stts" @@ -118,8 +140,16 @@ box_database!( AVCSampleEntry 0x6176_6331, // "avc1" AVC3SampleEntry 0x6176_6333, // "avc3" - Need to check official name in spec. AVCConfigurationBox 0x6176_6343, // "avcC" + H263SampleEntry 0x7332_3633, // "s263" + H263SpecificBox 0x6432_3633, // "d263" MP4AudioSampleEntry 0x6d70_3461, // "mp4a" MP4VideoSampleEntry 0x6d70_3476, // "mp4v" + #[cfg(feature = "3gpp")] + AMRNBSampleEntry 0x7361_6d72, // "samr" - AMR narrow-band + #[cfg(feature = "3gpp")] + AMRWBSampleEntry 0x7361_7762, // "sawb" - AMR wide-band + #[cfg(feature = "3gpp")] + AMRSpecificBox 0x6461_6d72, // "damr" ESDBox 0x6573_6473, // "esds" VP8SampleEntry 0x7670_3038, // "vp08" VP9SampleEntry 0x7670_3039, // "vp09" @@ -143,8 +173,8 @@ box_database!( SchemeTypeBox 0x7363_686d, // "schm" MP3AudioSampleEntry 0x2e6d_7033, // ".mp3" - from F4V. CompositionOffsetBox 0x6374_7473, // "ctts" - LPCMAudioSampleEntry 0x6C70_636D, // "lpcm" - quicktime atom - ALACSpecificBox 0x616C_6163, // "alac" - Also used by ALACSampleEntry + LPCMAudioSampleEntry 0x6c70_636d, // "lpcm" - quicktime atom + ALACSpecificBox 0x616c_6163, // "alac" - Also used by ALACSampleEntry UuidBox 0x7575_6964, // "uuid" MetadataBox 0x6d65_7461, // "meta" MetadataHeaderBox 0x6d68_6472, // "mhdr" @@ -152,6 +182,10 @@ box_database!( MetadataItemListEntry 0x696c_7374, // "ilst" MetadataItemDataEntry 0x6461_7461, // "data" MetadataItemNameBox 0x6e61_6d65, // "name" + #[cfg(feature = "meta-xml")] + MetadataXMLBox 0x786d_6c20, // "xml " + #[cfg(feature = "meta-xml")] + MetadataBXMLBox 0x6278_6d6c, // "bxml" UserdataBox 0x7564_7461, // "udta" AlbumEntry 0xa961_6c62, // "©alb" ArtistEntry 0xa941_5254, // "©ART" diff --git a/media/mp4parse-rust/mp4parse/src/lib.rs b/media/mp4parse-rust/mp4parse/src/lib.rs index b4af3d8a87..8d3525d35f 100644 --- a/media/mp4parse-rust/mp4parse/src/lib.rs +++ b/media/mp4parse-rust/mp4parse/src/lib.rs @@ -4,25 +4,32 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +// `clippy::upper_case_acronyms` is a nightly-only lint as of 2021-03-15, so we +// allow `clippy::unknown_clippy_lints` to ignore it on stable - but +// `clippy::unknown_clippy_lints` has been renamed in nightly, so we need to +// allow `renamed_and_removed_lints` to ignore a warning for that. +#![allow(renamed_and_removed_lints)] +#![allow(clippy::unknown_clippy_lints)] +#![allow(clippy::upper_case_acronyms)] + #[macro_use] extern crate log; extern crate bitreader; extern crate byteorder; +extern crate fallible_collections; extern crate num_traits; use bitreader::{BitReader, ReadInto}; use byteorder::{ReadBytesExt, WriteBytesExt}; + +use fallible_collections::TryRead; +use fallible_collections::TryReserveError; + use num_traits::Num; use std::convert::{TryFrom, TryInto as _}; +use std::fmt; use std::io::Cursor; use std::io::{Read, Take}; -use std::ops::{Range, RangeFrom}; - -#[cfg(feature = "mp4parse_fallible")] -extern crate mp4parse_fallible; - -#[cfg(feature = "mp4parse_fallible")] -use mp4parse_fallible::FallibleVec; #[macro_use] mod macros; @@ -34,16 +41,18 @@ use boxes::{BoxType, FourCC}; #[cfg(test)] mod tests; -// Arbitrary buffer size limit used for raw read_bufs on a box. -const BUF_SIZE_LIMIT: u64 = 10 * 1024 * 1024; +#[cfg(feature = "unstable-api")] +pub mod unstable; -// Max table length. Calculating in worst case for one week long video, one -// frame per table entry in 30 fps. -const TABLE_SIZE_LIMIT: u32 = 30 * 60 * 60 * 24 * 7; +/// The 'mif1' brand indicates structural requirements on files +/// See HEIF (ISO 23008-12:2017) § 10.2.1 +const MIF1_BRAND: FourCC = FourCC { value: *b"mif1" }; /// A trait to indicate a type can be infallibly converted to `u64`. /// This should only be implemented for infallible conversions, so only unsigned types are valid. trait ToU64 { + // Remove when https://github.com/rust-lang/rust-clippy/issues/6727 is resolved + #[allow(clippy::wrong_self_convention)] fn to_u64(self) -> u64; } @@ -61,7 +70,7 @@ impl ToU64 for usize { /// A trait to indicate a type can be infallibly converted to `usize`. /// This should only be implemented for infallible conversions, so only unsigned types are valid. -trait ToUsize { +pub trait ToUsize { fn to_usize(self) -> usize; } @@ -122,70 +131,99 @@ impl<'a, T: Read> Read for OffsetReader<'a, T> { } } -// TODO: vec_push() needs to be replaced when Rust supports fallible memory -// allocation in raw_vec. -#[allow(unreachable_code)] -pub fn vec_push(vec: &mut Vec, val: T) -> std::result::Result<(), ()> { - #[cfg(feature = "mp4parse_fallible")] - { - return FallibleVec::try_push(vec, val); - } +pub type TryVec = fallible_collections::TryVec; +pub type TryString = fallible_collections::TryVec; +pub type TryHashMap = fallible_collections::TryHashMap; +pub type TryBox = fallible_collections::TryBox; + +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; + +/// The return value to the C API +/// Any detail that needs to be communicated to the caller must be encoded here +/// since the [`Error`] type's associated data is part of the FFI. +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum Status { + Ok = 0, + BadArg = 1, + Invalid = 2, + Unsupported = 3, + Eof = 4, + Io = 5, + Oom = 6, + UnsupportedA1lx, + UnsupportedA1op, + UnsupportedClap, + UnsupportedGrid, + UnsupportedIpro, + UnsupportedLsel, +} + +/// For convenience of creating an error for an unsupported feature which we +/// want to communicate the specific feature back to the C API caller +impl From for Error { + fn from(parse_status: Status) -> Self { + let msg = match parse_status { + Status::Ok + | Status::BadArg + | Status::Invalid + | Status::Unsupported + | Status::Eof + | Status::Io + | Status::Oom => { + panic!("Status -> Error is only for Status:UnsupportedXXX errors") + } - vec.push(val); - Ok(()) + Status::UnsupportedA1lx => "AV1 layered image indexing (a1lx) is unsupported", + Status::UnsupportedA1op => "Operating point selection (a1op) is unsupported", + Status::UnsupportedClap => "Clean aperture (clap) transform is unsupported", + Status::UnsupportedGrid => "Grid-based images are unsupported", + Status::UnsupportedIpro => "Item protection (ipro) is unsupported", + Status::UnsupportedLsel => "Layer selection (lsel) is unsupported", + }; + Self::UnsupportedDetail(parse_status, msg) + } } -fn vec_with_capacity(capacity: usize) -> std::result::Result, ()> { - #[cfg(feature = "mp4parse_fallible")] - { - let mut v = Vec::new(); - FallibleVec::try_reserve(&mut v, capacity)?; - Ok(v) - } - #[cfg(not(feature = "mp4parse_fallible"))] - { - Ok(Vec::with_capacity(capacity)) +impl From for Status { + fn from(error: Error) -> Self { + match error { + Error::NoMoov | Error::InvalidData(_) => Self::Invalid, + Error::Unsupported(_) => Self::Unsupported, + Error::UnsupportedDetail(parse_status, _msg) => parse_status, + Error::UnexpectedEOF => Self::Eof, + Error::Io(_) => { + // Getting std::io::ErrorKind::UnexpectedEof is normal + // but our From trait implementation should have converted + // those to our Error::UnexpectedEOF variant. + Self::Io + } + Error::OutOfMemory => Self::Oom, + } } } -pub fn extend_from_slice(vec: &mut Vec, other: &[T]) -> std::result::Result<(), ()> { - #[cfg(feature = "mp4parse_fallible")] - { - FallibleVec::try_extend_from_slice(vec, other) - } - #[cfg(not(feature = "mp4parse_fallible"))] - { - vec.extend_from_slice(other); - Ok(()) +impl From> for Status { + fn from(result: Result<(), Status>) -> Self { + match result { + Ok(()) => Status::Ok, + Err(Status::Ok) => unreachable!(), + Err(e) => e, + } } } -/// With the `mp4parse_fallible` feature enabled, this function reserves the -/// upper limit of what `src` can generate before reading all bytes until EOF -/// in this source, placing them into buf. If the allocation is unsuccessful, -/// or reading from the source generates an error before reaching EOF, this -/// will return an error. Otherwise, it will return the number of bytes read. -/// -/// Since `src.limit()` may return a value greater than the number of bytes -/// which can be read from the source, it's possible this function may fail -/// in the allocation phase even though allocating the number of bytes available -/// to read would have succeeded. In general, it is assumed that the callers -/// have accurate knowledge of the number of bytes of interest and have created -/// `src` accordingly. -/// -/// With the `mp4parse_fallible` feature disabled, this is wrapper around -/// `std::io::Read::read_to_end()`. -fn read_to_end(src: &mut Take, buf: &mut Vec) -> std::result::Result { - #[cfg(feature = "mp4parse_fallible")] - { - let limit: usize = src.limit().try_into().map_err(|_| ())?; - FallibleVec::try_reserve(buf, limit)?; - let bytes_read = src.read_to_end(buf).map_err(|_| ())?; - Ok(bytes_read) - } - #[cfg(not(feature = "mp4parse_fallible"))] - { - src.read_to_end(buf).map_err(|_| ()) +impl From for Status { + fn from(_: fallible_collections::TryReserveError) -> Self { + Status::Oom } } @@ -199,6 +237,10 @@ pub enum Error { InvalidData(&'static str), /// Parse error caused by limited parser support rather than invalid data. Unsupported(&'static str), + /// Similar to [`Self::Unsupported`], but for errors that have a specific + /// [`Status`] variant for communicating the detail across FFI. + /// See the helper [`From for Error`](enum.Error.html#impl-From) + UnsupportedDetail(Status, &'static str), /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data. UnexpectedEOF, /// Propagate underlying errors from `std::io`. @@ -209,6 +251,14 @@ pub enum Error { OutOfMemory, } +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for Error {} + impl From for Error { fn from(_: bitreader::BitReaderError) -> Error { Error::InvalidData("invalid data") @@ -230,20 +280,38 @@ impl From for Error { } } +impl From for Error { + fn from(_: std::str::Utf8Error) -> Error { + Error::InvalidData("invalid utf8") + } +} + impl From for Error { fn from(_: std::num::TryFromIntError) -> Error { Error::Unsupported("integer conversion failed") } } -impl From<()> for Error { - fn from(_: ()) -> Error { +impl From for std::io::Error { + fn from(err: Error) -> Self { + let kind = match err { + Error::InvalidData(_) => std::io::ErrorKind::InvalidData, + Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof, + Error::Io(io_err) => return io_err, + _ => std::io::ErrorKind::Other, + }; + Self::new(kind, err) + } +} + +impl From for Error { + fn from(_: TryReserveError) -> Error { Error::OutOfMemory } } /// Result shorthand using our Error enum. -pub type Result = std::result::Result; +pub type Result = std::result::Result; /// Basic ISO box structure. /// @@ -251,6 +319,8 @@ pub type Result = std::result::Result; /// begins with a header describing the length of the box's data and a /// four-byte box type which identifies the type of the box. Together these /// are enough to interpret the contents of that section of the file. +/// +/// See ISOBMFF (ISO 14496-12:2015) § 4.2 #[derive(Debug, Clone, Copy)] struct BoxHeader { /// Box type. @@ -260,21 +330,30 @@ struct BoxHeader { /// Offset to the start of the contained data (or header size). offset: u64, /// Uuid for extended type. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 uuid: Option<[u8; 16]>, } +impl BoxHeader { + const MIN_SIZE: u64 = 8; // 4-byte size + 4-byte type + const MIN_LARGE_SIZE: u64 = 16; // 4-byte size + 4-byte type + 16-byte size +} + /// File type box 'ftyp'. #[derive(Debug)] struct FileTypeBox { + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 major_brand: FourCC, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 minor_version: u32, - compatible_brands: Vec, + compatible_brands: TryVec, } /// Movie header box 'mvhd'. #[derive(Debug)] struct MovieHeaderBox { pub timescale: u32, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 duration: u64, } @@ -305,14 +384,16 @@ pub struct TrackHeaderBox { /// Edit list box 'elst' #[derive(Debug)] struct EditListBox { - edits: Vec, + edits: TryVec, } #[derive(Debug)] struct Edit { segment_duration: u64, media_time: i64, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 media_rate_integer: i16, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 media_rate_fraction: i16, } @@ -326,19 +407,19 @@ struct MediaHeaderBox { // Chunk offset box 'stco' or 'co64' #[derive(Debug)] pub struct ChunkOffsetBox { - pub offsets: Vec, + pub offsets: TryVec, } // Sync sample box 'stss' #[derive(Debug)] pub struct SyncSampleBox { - pub samples: Vec, + pub samples: TryVec, } // Sample to chunk box 'stsc' #[derive(Debug)] pub struct SampleToChunkBox { - pub samples: Vec, + pub samples: TryVec, } #[derive(Debug)] @@ -352,13 +433,13 @@ pub struct SampleToChunk { #[derive(Debug)] pub struct SampleSizeBox { pub sample_size: u32, - pub sample_sizes: Vec, + pub sample_sizes: TryVec, } // Time to sample box 'stts' #[derive(Debug)] pub struct TimeToSampleBox { - pub samples: Vec, + pub samples: TryVec, } #[repr(C)] @@ -382,7 +463,7 @@ pub struct TimeOffset { #[derive(Debug)] pub struct CompositionOffsetBox { - pub samples: Vec, + pub samples: TryVec, } // Handler reference box 'hdlr' @@ -394,30 +475,34 @@ struct HandlerBox { // Sample description box 'stsd' #[derive(Debug)] pub struct SampleDescriptionBox { - pub descriptions: Vec, + pub descriptions: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum SampleEntry { Audio(AudioSampleEntry), Video(VideoSampleEntry), Unknown, } +/// An Elementary Stream Descriptor +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 #[allow(non_camel_case_types)] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct ES_Descriptor { pub audio_codec: CodecType, pub audio_object_type: Option, pub extended_audio_object_type: Option, pub audio_sample_rate: Option, pub audio_channel_count: Option, - pub codec_esds: Vec, - pub decoder_specific_data: Vec, // Data in DECODER_SPECIFIC_TAG + #[cfg(feature = "mp4v")] + pub video_codec: CodecType, + pub codec_esds: TryVec, + pub decoder_specific_data: TryVec, // Data in DECODER_SPECIFIC_TAG } #[allow(non_camel_case_types)] -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum AudioCodecSpecific { ES_Descriptor(ES_Descriptor), FLACSpecificBox(FLACSpecificBox), @@ -425,66 +510,77 @@ pub enum AudioCodecSpecific { ALACSpecificBox(ALACSpecificBox), MP3, LPCM, + #[cfg(feature = "3gpp")] + AMRSpecificBox(TryVec), } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct AudioSampleEntry { pub codec_type: CodecType, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 data_reference_index: u16, pub channelcount: u32, pub samplesize: u16, pub samplerate: f64, pub codec_specific: AudioCodecSpecific, - pub protection_info: Vec, + pub protection_info: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum VideoCodecSpecific { - AVCConfig(Vec), + AVCConfig(TryVec), VPxConfig(VPxConfigBox), AV1Config(AV1ConfigBox), - ESDSConfig(Vec), + ESDSConfig(TryVec), + H263Config(TryVec), } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct VideoSampleEntry { pub codec_type: CodecType, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 data_reference_index: u16, pub width: u16, pub height: u16, pub codec_specific: VideoCodecSpecific, - pub protection_info: Vec, + pub protection_info: TryVec, } /// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9). The meaning of each /// field is covered in detail in "VP Codec ISO Media File Format Binding". -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct VPxConfigBox { /// An integer that specifies the VP codec profile. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 profile: u8, /// An integer that specifies a VP codec level all samples conform to the following table. /// For a description of the various levels, please refer to the VP9 Bitstream Specification. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 level: u8, /// An integer that specifies the bit depth of the luma and color components. Valid values /// are 8, 10, and 12. pub bit_depth: u8, - /// Really an enum defined by the "Colour primaries" section of ISO/IEC 23001-8:2016. + /// Really an enum defined by the "Colour primaries" section of ISO 23091-2:2019 § 8.1. pub colour_primaries: u8, /// Really an enum defined by "VP Codec ISO Media File Format Binding". pub chroma_subsampling: u8, - /// Really an enum defined by the "Transfer characteristics" section of ISO/IEC 23001-8:2016. + /// Really an enum defined by the "Transfer characteristics" section of ISO 23091-2:2019 § 8.2. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 transfer_characteristics: u8, - /// Really an enum defined by the "Matrix coefficients" section of ISO/IEC 23001-8:2016. + /// Really an enum defined by the "Matrix coefficients" section of ISO 23091-2:2019 § 8.3. /// Available in 'VP Codec ISO Media File Format' version 1 only. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 matrix_coefficients: Option, /// Indicates the black level and range of the luma and chroma signals. 0 = legal range /// (e.g. 16-235 for 8 bit sample depth); 1 = full range (e.g. 0-255 for 8-bit sample depth). + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 video_full_range_flag: bool, /// This is not used for VP8 and VP9 . Intended for binary codec initialization data. - pub codec_init: Vec, + pub codec_init: TryVec, } -#[derive(Debug, Clone)] +/// See [AV1-ISOBMFF § 2.3.3](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax) +#[derive(Debug)] pub struct AV1ConfigBox { pub profile: u8, pub level: u8, @@ -496,31 +592,42 @@ pub struct AV1ConfigBox { pub chroma_sample_position: u8, pub initial_presentation_delay_present: bool, pub initial_presentation_delay_minus_one: u8, - pub config_obus: Vec, + // The raw config contained in the av1c box. Because some decoders accept this data as a binary + // blob, rather than as structured data, we store the blob here for convenience. + pub raw_config: TryVec, } -#[derive(Debug, Clone)] +impl AV1ConfigBox { + const CONFIG_OBUS_OFFSET: usize = 4; + + pub fn config_obus(&self) -> &[u8] { + &self.raw_config[Self::CONFIG_OBUS_OFFSET..] + } +} + +#[derive(Debug)] pub struct FLACMetadataBlock { pub block_type: u8, - pub data: Vec, + pub data: TryVec, } /// Represents a FLACSpecificBox 'dfLa' -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct FLACSpecificBox { + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 version: u8, - pub blocks: Vec, + pub blocks: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] struct ChannelMappingTable { stream_count: u8, coupled_count: u8, - channel_mapping: Vec, + channel_mapping: TryVec, } /// Represent an OpusSpecificBox 'dOps' -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct OpusSpecificBox { pub version: u8, output_channel_count: u8, @@ -532,10 +639,11 @@ pub struct OpusSpecificBox { } /// Represent an ALACSpecificBox 'alac' -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ALACSpecificBox { + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 version: u8, - pub data: Vec, + pub data: TryVec, } #[derive(Debug)] @@ -543,12 +651,12 @@ pub struct MovieExtendsBox { pub fragment_duration: Option, } -pub type ByteData = Vec; +pub type ByteData = TryVec; #[derive(Debug, Default)] pub struct ProtectionSystemSpecificHeaderBox { pub system_id: ByteData, - pub kid: Vec, + pub kid: TryVec, pub data: ByteData, // The entire pssh box (include header) required by Gecko. @@ -561,21 +669,21 @@ pub struct SchemeTypeBox { pub scheme_version: u32, } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct TrackEncryptionBox { pub is_encrypted: u8, pub iv_size: u8, - pub kid: Vec, + pub kid: TryVec, // Members for pattern encryption schemes pub crypt_byte_block_count: Option, pub skip_byte_block_count: Option, - pub constant_iv: Option>, + pub constant_iv: Option>, // End pattern encryption scheme members } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct ProtectionSchemeInfoBox { - pub code_name: String, + pub original_format: FourCC, pub scheme_type: Option, pub tenc: Option, } @@ -583,7 +691,7 @@ pub struct ProtectionSchemeInfoBox { /// Represents a userdata box 'udta'. /// Currently, only the metadata atom 'meta' /// is parsed. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct UserdataBox { pub meta: Option, } @@ -593,12 +701,12 @@ pub struct UserdataBox { /// 'udta.meta.ilst' may only have either a /// standard genre box 'gnre' or a custom /// genre box '©gen', but never both at once. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, PartialEq)] pub enum Genre { /// A standard ID3v1 numbered genre. StandardGenre(u8), /// Any custom genre string. - CustomGenre(String), + CustomGenre(TryString), } /// Represents the contents of a 'stik' @@ -641,24 +749,24 @@ pub enum AdvisoryRating { /// Represents the contents of 'ilst' atoms within /// a metadata box 'meta', parsed as iTunes metadata using /// the conventional tags. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct MetadataBox { /// The album name, '©alb' - pub album: Option, + pub album: Option, /// The artist name '©art' or '©ART' - pub artist: Option, + pub artist: Option, /// The album artist 'aART' - pub album_artist: Option, + pub album_artist: Option, /// Track comments '©cmt' - pub comment: Option, + pub comment: Option, /// The date or year field '©day' /// /// This is stored as an arbitrary string, /// and may not necessarily be in a valid date /// format. - pub year: Option, + pub year: Option, /// The track title '©nam' - pub title: Option, + pub title: Option, /// The track genre '©gen' or 'gnre'. pub genre: Option, /// The track number 'trkn'. @@ -672,15 +780,15 @@ pub struct MetadataBox { /// stored in 'disk' pub total_discs: Option, /// The composer of the track '©wrt' - pub composer: Option, + pub composer: Option, /// The encoder used to create this track '©too' - pub encoder: Option, + pub encoder: Option, /// The encoded-by settingo this track '©enc' - pub encoded_by: Option, + pub encoded_by: Option, /// The tempo or BPM of the track 'tmpo' pub beats_per_minute: Option, /// Copyright information of the track 'cprt' - pub copyright: Option, + pub copyright: Option, /// Whether or not this track is part of a compilation 'cpil' pub compilation: Option, /// The advisory rating of this track 'rtng' @@ -690,45 +798,45 @@ pub struct MetadataBox { /// This is stored in the box as string data, but /// the format is an integer percentage from 0 - 100, /// where 100 is displayed as 5 stars out of 5. - pub rating: Option, + pub rating: Option, /// The grouping this track belongs to '©grp' - pub grouping: Option, + pub grouping: Option, /// The media type of this track 'stik' pub media_type: Option, // stik /// Whether or not this track is a podcast 'pcst' pub podcast: Option, /// The category of ths track 'catg' - pub category: Option, + pub category: Option, /// The podcast keyword 'keyw' - pub keyword: Option, + pub keyword: Option, /// The podcast url 'purl' - pub podcast_url: Option, + pub podcast_url: Option, /// The podcast episode GUID 'egid' - pub podcast_guid: Option, + pub podcast_guid: Option, /// The description of the track 'desc' - pub description: Option, + pub description: Option, /// The long description of the track 'ldes'. /// /// Unlike other string fields, the long description field /// can be longer than 256 characters. - pub long_description: Option, + pub long_description: Option, /// The lyrics of the track '©lyr'. /// /// Unlike other string fields, the lyrics field /// can be longer than 256 characters. - pub lyrics: Option, + pub lyrics: Option, /// The name of the TV network this track aired on 'tvnn'. - pub tv_network_name: Option, + pub tv_network_name: Option, /// The name of the TV Show for this track 'tvsh'. - pub tv_show_name: Option, + pub tv_show_name: Option, /// The name of the TV Episode for this track 'tven'. - pub tv_episode_name: Option, + pub tv_episode_name: Option, /// The number of the TV Episode for this track 'tves'. pub tv_episode_number: Option, /// The season of the TV Episode of this track 'tvsn'. pub tv_season: Option, /// The date this track was purchased 'purd'. - pub purchase_date: Option, + pub purchase_date: Option, /// Whether or not this track supports gapless playback 'pgap' pub gapless_playback: Option, /// Any cover artwork attached to this track 'covr' @@ -736,21 +844,34 @@ pub struct MetadataBox { /// 'covr' is unique in that it may contain multiple 'data' sub-entries, /// each an image file. Here, each subentry's raw binary data is exposed, /// which may contain image data in JPEG or PNG format. - pub cover_art: Option>>, + pub cover_art: Option>>, /// The owner of the track 'ownr' - pub owner: Option, + pub owner: Option, /// Whether or not this track is HD Video 'hdvd' pub hd_video: Option, /// The name of the track to sort by 'sonm' - pub sort_name: Option, + pub sort_name: Option, /// The name of the album to sort by 'soal' - pub sort_album: Option, + pub sort_album: Option, /// The name of the artist to sort by 'soar' - pub sort_artist: Option, + pub sort_artist: Option, /// The name of the album artist to sort by 'soaa' - pub sort_album_artist: Option, + pub sort_album_artist: Option, /// The name of the composer to sort by 'soco' - pub sort_composer: Option, + pub sort_composer: Option, + /// Metadata + #[cfg(feature = "meta-xml")] + pub xml: Option, +} + +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.2.1 +#[cfg(feature = "meta-xml")] +#[derive(Debug)] +pub enum XmlBox { + /// XML metadata + StringXmlBox(TryString), + /// Binary XML metadata + BinaryXmlBox(TryVec), } /// Internal data structures. @@ -758,106 +879,427 @@ pub struct MetadataBox { pub struct MediaContext { pub timescale: Option, /// Tracks found in the file. - pub tracks: Vec, + pub tracks: TryVec, pub mvex: Option, - pub psshs: Vec, + pub psshs: TryVec, pub userdata: Option>, + #[cfg(feature = "meta-xml")] + pub metadata: Option>, +} + +/// An ISOBMFF item as described by an iloc box. For the sake of avoiding copies, +/// this can either be represented by the `Location` variant, which indicates +/// where the data exists within a `MediaDataBox` stored separately, or the +/// `Data` variant which owns the data. Unfortunately, it's not simple to +/// represent this as a [`std::borrow::Cow`], or other reference-based type, because +/// multiple instances may references different parts of the same [`MediaDataBox`] +/// and we want to avoid the copy that splitting the storage would entail. +#[derive(Debug)] +enum IsobmffItem { + Location(Extent), + Data(TryVec), } -impl MediaContext { - pub fn new() -> MediaContext { - Default::default() +#[derive(Debug)] +struct AvifItem { + /// The `item_ID` from ISOBMFF (ISO 14496-12:2015) + /// + /// See [`read_iloc`] + id: ItemId, + + /// AV1 Image Item per + image_data: IsobmffItem, +} + +impl AvifItem { + fn with_data_location(id: ItemId, extent: Extent) -> Self { + Self { + id, + image_data: IsobmffItem::Location(extent), + } + } + + fn with_inline_data(id: ItemId) -> Self { + Self { + id, + image_data: IsobmffItem::Data(TryVec::new()), + } } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct AvifContext { - /// The collected data indicated by the `pitm` box, See ISO 14496-12:2015 § 8.11.4 - pub primary_item: Vec, + /// Level of deviation from the specification before failing the parse + strictness: ParseStrictness, + /// Referred to by the `Location` variants of the `AvifItem`s in this struct + item_storage: TryVec, + /// The item indicated by the `pitm` box, See ISOBMFF (ISO 14496-12:2015) § 8.11.4 + primary_item: AvifItem, + /// Associated alpha channel for the primary item, if any + alpha_item: Option, + /// If true, divide RGB values by the alpha value. + /// See `prem` in MIAF (ISO 23000-22:2019) § 7.3.5.2 + pub premultiplied_alpha: bool, + /// All properties associated with `primary_item` or `alpha_item` + item_properties: ItemPropertiesBox, } impl AvifContext { - pub fn new() -> Self { - Default::default() + pub fn primary_item_coded_data(&self) -> &[u8] { + self.item_as_slice(&self.primary_item) + } + + pub fn primary_item_bits_per_channel(&self) -> Result<&[u8]> { + self.image_bits_per_channel(self.primary_item.id) + } + + pub fn alpha_item_coded_data(&self) -> &[u8] { + self.alpha_item + .as_ref() + .map_or(&[], |item| self.item_as_slice(item)) + } + + pub fn alpha_item_bits_per_channel(&self) -> Result<&[u8]> { + self.alpha_item + .as_ref() + .map_or(Ok(&[]), |item| self.image_bits_per_channel(item.id)) + } + + fn image_bits_per_channel(&self, item_id: ItemId) -> Result<&[u8]> { + match self + .item_properties + .get(item_id, BoxType::PixelInformationBox)? + { + Some(ItemProperty::Channels(pixi)) => Ok(pixi.bits_per_channel.as_slice()), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => Ok(&[]), + } + } + + pub fn spatial_extents_ptr(&self) -> Result<*const ImageSpatialExtentsProperty> { + match self + .item_properties + .get(self.primary_item.id, BoxType::ImageSpatialExtentsProperty)? + { + Some(ItemProperty::ImageSpatialExtents(ispe)) => Ok(ispe), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => { + fail_if( + self.strictness == ParseStrictness::Permissive, + "ispe is a mandatory property", + )?; + Ok(std::ptr::null()) + } + } + } + + pub fn nclx_colour_information_ptr(&self) -> Result<*const NclxColourInformation> { + let nclx_colr_boxes = self + .item_properties + .get_multiple(self.primary_item.id, |prop| { + matches!(prop, ItemProperty::Colour(ColourInformation::Nclx(_))) + })?; + + match *nclx_colr_boxes.as_slice() { + [] => Ok(std::ptr::null()), + [ItemProperty::Colour(ColourInformation::Nclx(nclx)), ..] => { + if nclx_colr_boxes.len() > 1 { + warn!("Multiple nclx colr boxes, using first"); + } + Ok(nclx) + } + _ => unreachable!("Expect only ColourInformation::Nclx(_) matches"), + } + } + + pub fn icc_colour_information(&self) -> Result<&[u8]> { + let icc_colr_boxes = self + .item_properties + .get_multiple(self.primary_item.id, |prop| { + matches!(prop, ItemProperty::Colour(ColourInformation::Icc(_, _))) + })?; + + match *icc_colr_boxes.as_slice() { + [] => Ok(&[]), + [ItemProperty::Colour(ColourInformation::Icc(icc, _)), ..] => { + if icc_colr_boxes.len() > 1 { + warn!("Multiple ICC profiles in colr boxes, using first"); + } + Ok(icc.bytes.as_slice()) + } + _ => unreachable!("Expect only ColourInformation::Icc(_) matches"), + } + } + + pub fn image_rotation(&self) -> Result { + match self + .item_properties + .get(self.primary_item.id, BoxType::ImageRotation)? + { + Some(ItemProperty::Rotation(irot)) => Ok(*irot), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => Ok(ImageRotation::D0), + } + } + + pub fn image_mirror_ptr(&self) -> Result<*const ImageMirror> { + match self + .item_properties + .get(self.primary_item.id, BoxType::ImageMirror)? + { + Some(ItemProperty::Mirroring(imir)) => Ok(imir), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => Ok(std::ptr::null()), + } + } + + /// A helper for the various `AvifItem`s to expose a reference to the + /// underlying data while avoiding copies. + fn item_as_slice<'a>(&'a self, item: &'a AvifItem) -> &'a [u8] { + match &item.image_data { + IsobmffItem::Location(extent) => { + for mdat in &self.item_storage { + if let Some(slice) = mdat.get(extent) { + return slice; + } + } + unreachable!( + "IsobmffItem::Location requires the location exists in AvifContext::item_storage" + ); + } + IsobmffItem::Data(data) => data.as_slice(), + } } } +struct AvifMeta { + item_references: TryVec, + item_properties: ItemPropertiesBox, + primary_item_id: ItemId, + iloc_items: TryHashMap, +} + /// A Media Data Box -/// See ISO 14496-12:2015 § 8.1.1 +/// See ISOBMFF (ISO 14496-12:2015) § 8.1.1 struct MediaDataBox { - /// Offset of `data` from the beginning of the file. See ConstructionMethod::File - offset: u64, - data: Vec, + /// Offset of `data` from the beginning of the "file". See ConstructionMethod::File. + /// Note: the file may not be an actual file, read_avif supports any `&mut impl Read` + /// source for input. However we try to match the terminology used in the spec. + file_offset: u64, + data: TryVec, +} + +impl fmt::Debug for MediaDataBox { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("MediaDataBox") + .field("file_offset", &self.file_offset) + .field("data", &format_args!("{} bytes", self.data.len())) + .finish() + } } impl MediaDataBox { - /// Check whether the beginning of `extent` is within the bounds of the `MediaDataBox`. - /// We assume extents to not cross box boundaries. If so, this will cause an error - /// in `read_extent`. - fn contains_extent(&self, extent: &ExtentRange) -> bool { - if self.offset <= extent.start() { - let start_offset = extent.start() - self.offset; - start_offset < self.data.len().to_u64() - } else { - false + /// Convert an absolute offset to an offset relative to the beginning of the + /// `self.data` field. Returns None if the offset would be negative. + /// + /// # Panics + /// + /// Panics if the offset would overflow a `usize`. + fn file_offset_to_data_offset(&self, offset: u64) -> Option { + let start = offset + .checked_sub(self.file_offset)? + .try_into() + .expect("usize overflow"); + Some(start) + } + + /// Return a slice from the MediaDataBox specified by the provided `extent`. + /// Returns `None` if the extent isn't fully contained by the MediaDataBox. + /// + /// # Panics + /// + /// Panics if either the offset or length (if the extent is bounded) of the + /// slice would overflow a `usize`. + pub fn get<'a>(&'a self, extent: &'a Extent) -> Option<&'a [u8]> { + match extent { + Extent::WithLength { offset, len } => { + let start = self.file_offset_to_data_offset(*offset)?; + let end = start.checked_add(*len).expect("usize overflow"); + self.data.get(start..end) + } + Extent::ToEnd { offset } => { + let start = self.file_offset_to_data_offset(*offset)?; + self.data.get(start..) + } } } +} - /// Check whether `extent` covers the `MediaDataBox` exactly. - fn matches_extent(&self, extent: &ExtentRange) -> bool { - if self.offset == extent.start() { - match extent { - ExtentRange::WithLength(range) => { - if let Some(end) = self.offset.checked_add(self.data.len().to_u64()) { - end == range.end - } else { - false - } - } - ExtentRange::ToEnd(_) => true, +#[cfg(test)] +mod media_data_box_tests { + use super::*; + + impl MediaDataBox { + fn at_offset(file_offset: u64, data: std::vec::Vec) -> Self { + MediaDataBox { + file_offset, + data: data.into(), } - } else { - false - } - } - - /// Copy the range specified by `extent` to the end of `buf` or return an error if the range - /// is not fully contained within `MediaDataBox`. - fn read_extent(&mut self, extent: &ExtentRange, buf: &mut Vec) -> Result<()> { - let start_offset = extent - .start() - .checked_sub(self.offset) - .expect("mdat does not contain extent"); - let slice = match extent { - ExtentRange::WithLength(range) => { - let range_len = range - .end - .checked_sub(range.start) - .expect("range start > end"); - let end = start_offset - .checked_add(range_len) - .expect("extent end overflow"); - self.data.get(start_offset.try_into()?..end.try_into()?) - } - ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..), + } + } + + #[test] + fn extent_with_length_before_mdat_returns_none() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { offset: 0, len: 2 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_to_end_before_mdat_returns_none() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 0 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_crossing_front_mdat_boundary_returns_none() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { offset: 99, len: 3 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_which_is_subset_of_mdat() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 101, + len: 2, }; - let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?; - extend_from_slice(buf, slice)?; - Ok(()) + + assert_eq!(mdat.get(&extent), Some(&[1, 1][..])); + } + + #[test] + fn extent_to_end_which_is_subset_of_mdat() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 101 }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); + } + + #[test] + fn extent_with_length_which_is_all_of_mdat() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 100, + len: 5, + }; + + assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); + } + + #[test] + fn extent_to_end_which_is_all_of_mdat() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 100 }; + + assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); + } + + #[test] + fn extent_with_length_crossing_back_mdat_boundary_returns_none() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 103, + len: 3, + }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_after_mdat_returns_none() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 200, + len: 2, + }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_to_end_after_mdat_returns_none() { + let mdat = MediaDataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 200 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + #[should_panic(expected = "usize overflow")] + fn extent_with_length_which_overflows_usize_panics() { + let mdat = MediaDataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); + let extent = Extent::WithLength { + offset: std::u64::MAX, + len: std::usize::MAX, + }; + + mdat.get(&extent); + } + + // The end of the range would overflow `usize` if it were calculated, but + // because the range end is unbounded, we don't calculate it. + #[test] + fn extent_to_end_which_overflows_usize() { + let mdat = MediaDataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); + let extent = Extent::ToEnd { + offset: std::u64::MAX, + }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +struct PropertyIndex(u16); +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +struct ItemId(u32); + +impl ItemId { + fn read(src: &mut impl ReadBytesExt, version: u8) -> Result { + Ok(ItemId(if version == 0 { + be_u16(src)?.into() + } else { + be_u32(src)? + })) } } /// Used for 'infe' boxes within 'iinf' boxes -/// See ISO 14496-12:2015 § 8.11.6 +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.6 /// Only versions {2, 3} are supported #[derive(Debug)] struct ItemInfoEntry { - item_id: u32, + item_id: ItemId, item_type: u32, } +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.12 +#[derive(Debug)] +struct SingleItemTypeReferenceBox { + item_type: FourCC, + from_item_id: ItemId, + to_item_id: ItemId, +} + /// Potential sizes (in bytes) of variable-sized fields of the 'iloc' box -/// See ISO 14496-12:2015 § 8.11.3 +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.3 +#[derive(Debug, Clone, Copy, PartialEq)] enum IlocFieldSize { Zero, Four, @@ -865,7 +1307,7 @@ enum IlocFieldSize { } impl IlocFieldSize { - fn to_bits(&self) -> u8 { + fn as_bits(&self) -> u8 { match self { IlocFieldSize::Zero => 0, IlocFieldSize::Four => 32, @@ -908,45 +1350,42 @@ impl TryFrom for IlocVersion { } /// Used for 'iloc' boxes -/// See ISO 14496-12:2015 § 8.11.3 +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.3 /// `base_offset` is omitted since it is integrated into the ranges in `extents` /// `data_reference_index` is omitted, since only 0 (i.e., this file) is supported -#[derive(Clone, Debug)] +#[derive(Debug)] struct ItemLocationBoxItem { - item_id: u32, construction_method: ConstructionMethod, /// Unused for ConstructionMethod::Idat - extents: Vec, + extents: TryVec, } -#[derive(Clone, Copy, Debug)] +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.3 +/// +/// Note: per MIAF (ISO 23000-22:2019) § 7.2.1.7:
+/// > MIAF image items are constrained as follows:
+/// > — `construction_method` shall be equal to 0 for MIAF image items that are coded image items.
+/// > — `construction_method` shall be equal to 0 or 1 for MIAF image items that are derived image items. +#[derive(Clone, Copy, Debug, PartialEq)] enum ConstructionMethod { - File, - Idat, + File = 0, + Idat = 1, #[allow(dead_code)] // TODO: see https://github.com/mozilla/mp4parse-rust/issues/196 - Item, + Item = 2, } +/// Describes a region where a item specified by an `ItemLocationBoxItem` is stored. +/// The offset is `u64` since that's the maximum possible size and since the relative +/// nature of `MediaDataBox` means this can still possibly succeed even in the case +/// that the raw value exceeds std::usize::MAX on platforms where that type is smaller +/// than u64. However, `len` is stored as a `usize` since no value larger than +/// `std::usize::MAX` can be used in a successful indexing operation in rust. /// `extent_index` is omitted since it's only used for ConstructionMethod::Item which /// is currently not implemented. #[derive(Clone, Debug)] -struct ItemLocationBoxExtent { - extent_range: ExtentRange, -} - -#[derive(Clone, Debug)] -enum ExtentRange { - WithLength(Range), - ToEnd(RangeFrom), -} - -impl ExtentRange { - fn start(&self) -> u64 { - match self { - Self::WithLength(r) => r.start, - Self::ToEnd(r) => r.start, - } - } +enum Extent { + WithLength { offset: u64, len: usize }, + ToEnd { offset: u64 }, } #[derive(Debug, PartialEq)] @@ -963,6 +1402,25 @@ impl Default for TrackType { } } +// This type is used by mp4parse_capi since it needs to be passed from FFI consumers +// The C-visible struct is renamed via mp4parse_capi/cbindgen.toml to match naming conventions +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ParseStrictness { + Permissive, // Error only on ambiguous inputs + Normal, // Error on "shall" directives, log warnings for "should" + Strict, // Error on "should" directives +} + +fn fail_if(violation: bool, message: &'static str) -> Result<()> { + if violation { + Err(Error::InvalidData(message)) + } else { + warn!("{}", message); + Ok(()) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum CodecType { Unknown, @@ -979,6 +1437,11 @@ pub enum CodecType { EncryptedAudio, LPCM, // QT ALAC, + H263, + #[cfg(feature = "3gpp")] + AMRNB, + #[cfg(feature = "3gpp")] + AMRWB, } impl Default for CodecType { @@ -1003,16 +1466,16 @@ pub struct TrackTimeScale(pub T, pub usize); /// A time to be scaled by the track's local (mdhd) timescale. /// Members are time in scale units and the track id. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct TrackScaledTime(pub T, pub usize); +pub struct TrackScaledTime(pub T, pub usize); impl std::ops::Add for TrackScaledTime where - T: Num, + T: num_traits::CheckedAdd, { - type Output = TrackScaledTime; + type Output = Option; - fn add(self, other: TrackScaledTime) -> TrackScaledTime { - TrackScaledTime::(self.0 + other.0, self.1) + fn add(self, other: TrackScaledTime) -> Self::Output { + self.0.checked_add(&other.0).map(|sum| Self(sum, self.1)) } } @@ -1044,6 +1507,7 @@ impl Track { } } +/// See ISOBMFF (ISO 14496-12:2015) § 4.2 struct BMFFBox<'a, T: 'a> { head: BoxHeader, content: Take<&'a mut T>, @@ -1077,6 +1541,12 @@ impl<'a, T: Read> Read for BMFFBox<'a, T> { } } +impl<'a, T: Read> TryRead for BMFFBox<'a, T> { + fn try_read_to_end(&mut self, buf: &mut TryVec) -> std::io::Result { + fallible_collections::try_read_up_to(self, self.bytes_left(), buf) + } +} + impl<'a, T: Offset> Offset for BMFFBox<'a, T> { fn offset(&self) -> u64 { self.content.get_ref().offset() @@ -1097,46 +1567,6 @@ impl<'a, T: Read> BMFFBox<'a, T> { } } -impl<'a, T: Read + Offset> BMFFBox<'a, T> { - /// Check whether the beginning of `extent` is within the bounds of the `BMFFBox`. - /// We assume extents to not cross box boundaries. If so, this will cause an error - /// in `read_extent`. - fn contains_extent(&self, extent: &ExtentRange) -> bool { - if self.offset() <= extent.start() { - let start_offset = extent.start() - self.offset(); - start_offset < self.bytes_left() - } else { - false - } - } - - /// Read the range specified by `extent` into `buf` or return an error if the range is not - /// fully contained within the `BMFFBox`. - fn read_extent(&mut self, extent: &ExtentRange, buf: &mut Vec) -> Result<()> { - let start_offset = extent - .start() - .checked_sub(self.offset()) - .expect("box does not contain extent"); - skip(self, start_offset)?; - match extent { - ExtentRange::WithLength(range) => { - let len = range - .end - .checked_sub(range.start) - .expect("range start > end"); - if len > self.bytes_left() { - return Err(Error::InvalidData("extent crosses box boundary")); - } - read_to_end(&mut self.take(len), buf)?; - } - ExtentRange::ToEnd(_) => { - read_to_end(&mut self.take(self.bytes_left()), buf)?; - } - } - Ok(()) - } -} - impl<'a, T> Drop for BMFFBox<'a, T> { fn drop(&mut self) { if self.content.limit() > 0 { @@ -1152,6 +1582,8 @@ impl<'a, T> Drop for BMFFBox<'a, T> { /// and its length. Used internally for dispatching to specific /// parsers for the internal content, or to get the length to /// skip unknown or uninteresting boxes. +/// +/// See ISOBMFF (ISO 14496-12:2015) § 4.2 fn read_box_header(src: &mut T) -> Result { let size32 = be_u32(src)?; let name = BoxType::from(be_u32(src)?); @@ -1160,17 +1592,21 @@ fn read_box_header(src: &mut T) -> Result { 0 => return Err(Error::Unsupported("unknown sized box")), 1 => { let size64 = be_u64(src)?; - if size64 < 16 { + if size64 < BoxHeader::MIN_LARGE_SIZE { return Err(Error::InvalidData("malformed wide size")); } size64 } - 2..=7 => return Err(Error::InvalidData("malformed size")), - _ => u64::from(size32), + _ => { + if u64::from(size32) < BoxHeader::MIN_SIZE { + return Err(Error::InvalidData("malformed size")); + } + u64::from(size32) + } }; let mut offset = match size32 { - 1 => 4 + 4 + 8, - _ => 4 + 4, + 1 => BoxHeader::MIN_LARGE_SIZE, + _ => BoxHeader::MIN_SIZE, }; let uuid = if name == BoxType::UuidBox { if size >= offset + 16 { @@ -1249,277 +1685,1368 @@ fn skip_box_remain(src: &mut BMFFBox) -> Result<()> { } /// Read the contents of an AVIF file -/// -/// Metadata is accumulated in the passed-through `AvifContext` struct, -/// which can be examined later. -pub fn read_avif(f: &mut T, context: &mut AvifContext) -> Result<()> { +pub fn read_avif(f: &mut T, strictness: ParseStrictness) -> Result { + let _ = env_logger::try_init(); + + debug!("read_avif(strictness: {:?})", strictness); + let mut f = OffsetReader::new(f); let mut iter = BoxIter::new(&mut f); - // 'ftyp' box must occur first; see ISO 14496-12:2015 § 4.3.1 + // 'ftyp' box must occur first; see ISOBMFF (ISO 14496-12:2015) § 4.3.1 if let Some(mut b) = iter.next_box()? { if b.head.name == BoxType::FileTypeBox { let ftyp = read_ftyp(&mut b)?; - if !ftyp.compatible_brands.contains(&FourCC::from("mif1")) { - return Err(Error::InvalidData("compatible_brands must contain 'mif1'")); + if !ftyp.compatible_brands.contains(&MIF1_BRAND) { + // This mandatory inclusion of this brand is in the process of being changed + // to optional. In anticipation of that, only give an error in strict mode + // See https://github.com/MPEGGroup/MIAF/issues/5 + // and https://github.com/MPEGGroup/FileFormat/issues/23 + fail_if( + strictness == ParseStrictness::Strict, + "The FileTypeBox should contain 'mif1' in the compatible_brands list \ + per MIAF (ISO 23000-22:2019) § 7.2.1.2", + )?; } } else { return Err(Error::InvalidData("'ftyp' box must occur first")); } } - let mut read_meta = false; - let mut mdats = vec![]; - let mut primary_item_extents = None; - let mut primary_item_extents_data: Vec> = vec![]; + let mut meta = None; + let mut item_storage = TryVec::new(); while let Some(mut b) = iter.next_box()? { + trace!("read_avif parsing {:?} box", b.head.name); match b.head.name { BoxType::MetadataBox => { - if read_meta { + if meta.is_some() { return Err(Error::InvalidData( - "There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1", + "There should be zero or one meta boxes per ISOBMFF (ISO 14496-12:2015) § 8.11.1.1", )); } - read_meta = true; - let primary_item_loc = read_avif_meta(&mut b)?; - match primary_item_loc.construction_method { - ConstructionMethod::File => { - primary_item_extents = Some(primary_item_loc.extents); - primary_item_extents_data = - primary_item_extents.iter().map(|_| vec![]).collect(); - } - _ => return Err(Error::Unsupported("unsupported construction_method")), - } + meta = Some(read_avif_meta(&mut b, strictness)?); } BoxType::MediaDataBox => { - // See ISO 14496-12:2015 § 8.1.1 - // If we know our primary item location by this point, try to read it out of this - // mdat directly and avoid a copy - if let Some(extents) = &primary_item_extents { - for (extent, data) in extents.iter().zip(primary_item_extents_data.iter_mut()) { - if b.contains_extent(&extent.extent_range) { - b.read_extent(&extent.extent_range, data)?; - } - } + if b.bytes_left() > 0 { + let file_offset = b.offset(); + let data = b.read_into_try_vec()?; + item_storage.push(MediaDataBox { file_offset, data })?; } + } + _ => skip_box_content(&mut b)?, + } - // Store any remaining data for potential later extraction - if b.bytes_left() > 0 { - let offset = b.offset(); - let mut data = vec_with_capacity(b.bytes_left().try_into()?)?; - b.read_to_end(&mut data)?; - vec_push(&mut mdats, MediaDataBox { offset, data })?; + check_parser_state!(b.content); + } + + let AvifMeta { + item_references, + item_properties, + primary_item_id, + iloc_items, + } = meta.ok_or(Error::InvalidData("missing meta"))?; + + let mut alpha_item_ids = item_references + .iter() + // Auxiliary image for the primary image + .filter(|iref| { + iref.to_item_id == primary_item_id + && iref.from_item_id != primary_item_id + && iref.item_type == b"auxl" + }) + .map(|iref| iref.from_item_id) + // which has the alpha property + .filter(|&item_id| item_properties.is_alpha(item_id)); + let alpha_item_id = alpha_item_ids.next(); + if alpha_item_ids.next().is_some() { + return Err(Error::InvalidData("multiple alpha planes")); + } + + let premultiplied_alpha = alpha_item_id.map_or(false, |alpha_item_id| { + item_references.iter().any(|iref| { + iref.from_item_id == primary_item_id + && iref.to_item_id == alpha_item_id + && iref.item_type == b"prem" + }) + }); + + let mut primary_item = None; + let mut alpha_item = None; + + // store data or record location of relevant items + for (item_id, loc) in iloc_items { + let item = if item_id == primary_item_id { + &mut primary_item + } else if Some(item_id) == alpha_item_id { + &mut alpha_item + } else { + continue; + }; + + if loc.construction_method != ConstructionMethod::File { + return Err(Error::Unsupported("unsupported construction_method")); + } + + assert!(item.is_none()); + + // If our item is spread over multiple extents, we'll need to copy it + // into a contiguous buffer. Otherwise, we can just store the extent + // and return a pointer into the mdat later to avoid the copy. + if loc.extents.len() > 1 { + *item = Some(AvifItem::with_inline_data(item_id)) + } + + for extent in loc.extents { + let mut found = false; + // try to find an mdat which contains the extent + for mdat in item_storage.iter_mut() { + if let Some(extent_slice) = mdat.get(&extent) { + match item { + None => { + trace!("Using IsobmffItem::Location"); + *item = Some(AvifItem::with_data_location(item_id, extent)); + } + Some(AvifItem { + image_data: IsobmffItem::Data(item_data), + .. + }) => { + trace!("Using IsobmffItem::Data"); + // We could potentially optimize memory usage by trying to avoid reading + // or storing mdat boxes which aren't used by our API, but for now it seems + // like unnecessary complexity + item_data.extend_from_slice(extent_slice)?; + } + _ => unreachable!(), + } + found = true; + break; } } + + if !found { + return Err(Error::InvalidData( + "iloc contains an extent that is not in any mdat", + )); + } + } + + assert!(item.is_some()); + } + + let primary_item = primary_item.ok_or(Error::InvalidData( + "Missing 'pitm' box, required per HEIF (ISO/IEC 23008-12:2017) § 10.2.1", + ))?; + + let has_pixi = |item_id| { + item_properties + .get(item_id, BoxType::PixelInformationBox) + .map_or(false, |opt| opt.is_some()) + }; + if !has_pixi(primary_item_id) || !alpha_item_id.map_or(true, has_pixi) { + // The requirement to include pixi is in the process of being changed + // to allowing its omission to imply a default value. In anticipation + // of that, only give an error in strict mode + // See https://github.com/MPEGGroup/MIAF/issues/9 + fail_if( + if cfg!(feature = "missing-pixi-permitted") { + strictness == ParseStrictness::Strict + } else { + strictness != ParseStrictness::Permissive + }, + "The pixel information property shall be associated with every image \ + that is displayable (not hidden) \ + per MIAF (ISO/IEC 23000-22:2019) specification § 7.3.6.6", + )?; + } + + let has_av1c = |item_id| { + item_properties + .get(item_id, BoxType::AV1CodecConfigurationBox) + .map_or(false, |opt| opt.is_some()) + }; + if !has_av1c(primary_item_id) || !alpha_item_id.map_or(true, has_av1c) { + fail_if( + strictness != ParseStrictness::Permissive, + "One AV1 Item Configuration Property (av1C) is mandatory for an \ + image item of type 'av01' \ + per AVIF specification § 2.2.1", + )?; + } + + if item_properties.get_ispe(primary_item_id)?.is_none() { + fail_if( + strictness != ParseStrictness::Permissive, + "Missing 'ispe' property for primary item, required \ + per HEIF (ISO/IEC 23008-12:2017) § 6.5.3.1", + )?; + } + + Ok(AvifContext { + strictness, + item_storage, + primary_item, + alpha_item, + premultiplied_alpha, + item_properties, + }) +} + +/// Parse a metadata box in the context of an AVIF +/// Currently requires the primary item to be an av01 item type and generates +/// an error otherwise. +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.1 +fn read_avif_meta( + src: &mut BMFFBox, + strictness: ParseStrictness, +) -> Result { + let version = read_fullbox_version_no_flags(src)?; + + if version != 0 { + return Err(Error::Unsupported("unsupported meta version")); + } + + let mut read_handler_box = false; + let mut primary_item_id = None; + let mut item_infos = None; + let mut iloc_items = None; + let mut item_references = None; + let mut item_properties = None; + + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + trace!("read_avif_meta parsing {:?} box", b.head.name); + + if !read_handler_box && b.head.name != BoxType::HandlerBox { + fail_if( + strictness != ParseStrictness::Permissive, + "The HandlerBox shall be the first contained box within the MetaBox \ + per MIAF (ISO 23000-22:2019) § 7.2.1.5", + )?; + } + + match b.head.name { + BoxType::HandlerBox => { + if read_handler_box { + return Err(Error::InvalidData( + "There shall be exactly one hdlr box per ISOBMFF (ISO 14496-12:2015) § 8.4.3.1", + )); + } + let HandlerBox { handler_type } = read_hdlr(&mut b, strictness)?; + if handler_type != b"pict" { + fail_if( + strictness != ParseStrictness::Permissive, + "The HandlerBox handler_type must be 'pict' \ + per MIAF (ISO 23000-22:2019) § 7.2.1.5", + )?; + } + read_handler_box = true; + } + BoxType::ItemInfoBox => { + if item_infos.is_some() { + return Err(Error::InvalidData( + "There shall be zero or one iinf boxes per ISOBMFF (ISO 14496-12:2015) § 8.11.6.1", + )); + } + item_infos = Some(read_iinf(&mut b, strictness)?); + } + BoxType::ItemLocationBox => { + if iloc_items.is_some() { + return Err(Error::InvalidData( + "There shall be zero or one iloc boxes per ISOBMFF (ISO 14496-12:2015) § 8.11.3.1", + )); + } + iloc_items = Some(read_iloc(&mut b)?); + } + BoxType::PrimaryItemBox => { + if primary_item_id.is_some() { + return Err(Error::InvalidData( + "There shall be zero or one pitm boxes per ISOBMFF (ISO 14496-12:2015) § 8.11.4.1", + )); + } + primary_item_id = Some(read_pitm(&mut b)?); + } + BoxType::ItemReferenceBox => { + if item_references.is_some() { + return Err(Error::InvalidData("There shall be zero or one iref boxes per ISOBMFF (ISO 14496-12:2015) § 8.11.12.1")); + } + item_references = Some(read_iref(&mut b)?); + } + BoxType::ItemPropertiesBox => { + if item_properties.is_some() { + return Err(Error::InvalidData("There shall be zero or one iprp boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1")); + } + item_properties = Some(read_iprp(&mut b, MIF1_BRAND, strictness)?); + } _ => skip_box_content(&mut b)?, } check_parser_state!(b.content); } - // If the `mdat` box came before the `meta` box, we need to fill in our primary item data - let primary_item_extents = - primary_item_extents.ok_or(Error::InvalidData("primary item extents missing"))?; - for (extent, data) in primary_item_extents - .iter() - .zip(primary_item_extents_data.iter_mut()) - { - if data.is_empty() { - // try to find an overlapping mdat - for mdat in &mut mdats { - if mdat.matches_extent(&extent.extent_range) { - data.append(&mut mdat.data) - } else if mdat.contains_extent(&extent.extent_range) { - mdat.read_extent(&extent.extent_range, data)?; + let primary_item_id = primary_item_id.ok_or(Error::InvalidData( + "Required pitm box not present in meta box", + ))?; + + let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?; + + if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) { + debug!("primary_item_id type: {}", U32BE(item_info.item_type)); + match &item_info.item_type.to_be_bytes() { + b"av01" => {} + b"grid" => return Err(Error::from(Status::UnsupportedGrid)), + _ => { + return Err(Error::InvalidData( + "primary_item_id type is neither 'av01' nor 'grid'", + )) + } + } + } else { + return Err(Error::InvalidData( + "primary_item_id not present in iinf box", + )); + } + + Ok(AvifMeta { + item_properties: item_properties.unwrap_or_default(), + item_references: item_references.unwrap_or_default(), + primary_item_id, + iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?, + }) +} + +/// Parse a Primary Item Box +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.4 +fn read_pitm(src: &mut BMFFBox) -> Result { + let version = read_fullbox_version_no_flags(src)?; + + let item_id = ItemId(match version { + 0 => be_u16(src)?.into(), + 1 => be_u32(src)?, + _ => return Err(Error::Unsupported("unsupported pitm version")), + }); + + Ok(item_id) +} + +/// Parse an Item Information Box +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.6 +fn read_iinf( + src: &mut BMFFBox, + strictness: ParseStrictness, +) -> Result> { + let version = read_fullbox_version_no_flags(src)?; + + match version { + 0 | 1 => (), + _ => return Err(Error::Unsupported("unsupported iinf version")), + } + + let entry_count = if version == 0 { + be_u16(src)?.to_usize() + } else { + be_u32(src)?.to_usize() + }; + let mut item_infos = TryVec::with_capacity(entry_count)?; + + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + if b.head.name != BoxType::ItemInfoEntry { + return Err(Error::InvalidData( + "iinf box shall contain only infe boxes per ISOBMFF (ISO 14496-12:2015) § 8.11.6.2", + )); + } + + item_infos.push(read_infe(&mut b, strictness)?)?; + + check_parser_state!(b.content); + } + + Ok(item_infos) +} + +/// A simple wrapper to interpret a u32 as a 4-byte string in big-endian +/// order without requiring any allocation. +struct U32BE(u32); + +impl std::fmt::Display for U32BE { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match std::str::from_utf8(&self.0.to_be_bytes()) { + Ok(s) => f.write_str(s), + Err(_) => write!(f, "{:x?}", self.0), + } + } +} + +/// Parse an Item Info Entry +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.6.2 +fn read_infe(src: &mut BMFFBox, strictness: ParseStrictness) -> Result { + let (version, flags) = read_fullbox_extra(src)?; + + // According to the standard, it seems the flags field shall be 0, but at + // least one sample AVIF image has a nonzero value. + // See https://github.com/AOMediaCodec/av1-avif/issues/146 + if flags != 0 { + fail_if( + strictness == ParseStrictness::Strict, + "'infe' flags field shall be 0 \ + per ISOBMFF (ISO 14496-12:2015) § 8.11.6.2", + )?; + } + + // mif1 brand (see HEIF (ISO 23008-12:2017) § 10.2.1) only requires v2 and 3 + let item_id = ItemId(match version { + 2 => be_u16(src)?.into(), + 3 => be_u32(src)?, + _ => return Err(Error::Unsupported("unsupported version in 'infe' box")), + }); + + let item_protection_index = be_u16(src)?; + + if item_protection_index != 0 { + return Err(Error::from(Status::UnsupportedIpro)); + } + + let item_type = be_u32(src)?; + debug!("infe {:?} item_type: {}", item_id, U32BE(item_type)); + + // There are some additional fields here, but they're not of interest to us + skip_box_remain(src)?; + + Ok(ItemInfoEntry { item_id, item_type }) +} + +/// Parse an Item Reference Box +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.12 +fn read_iref(src: &mut BMFFBox) -> Result> { + let mut item_references = TryVec::new(); + let version = read_fullbox_version_no_flags(src)?; + if version > 1 { + return Err(Error::Unsupported("iref version")); + } + + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + trace!("read_iref parsing {:?} referenceType", b.head.name); + let from_item_id = ItemId::read(&mut b, version)?; + let reference_count = be_u16(&mut b)?; + item_references.reserve(reference_count.to_usize())?; + for _ in 0..reference_count { + let to_item_id = ItemId::read(&mut b, version)?; + if from_item_id == to_item_id { + return Err(Error::InvalidData( + "from_item_id and to_item_id must be different", + )); + } + item_references.push(SingleItemTypeReferenceBox { + item_type: b.head.name.into(), + from_item_id, + to_item_id, + })?; + } + check_parser_state!(b.content); + } + Ok(item_references) +} + +/// Parse an Item Properties Box +/// +/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14) +/// +/// Note: HEIF (ISO 23008-12:2017) § 9.3.1 also defines the `iprp` box and +/// related types, but lacks additional requirements specified in 14496-12:2020. +/// +/// Note: Currently HEIF (ISO 23008-12:2017) § 6.5.5.1 specifies "At most one" +/// `colr` box per item, but this is being amended in [DIS 23008-12](https://www.iso.org/standard/83650.html). +/// The new text is likely to be "At most one for a given value of `colour_type`", +/// so this implementation adheres to that language for forward compatibility. +fn read_iprp( + src: &mut BMFFBox, + brand: FourCC, + strictness: ParseStrictness, +) -> Result { + let mut iter = src.box_iter(); + + let properties = match iter.next_box()? { + Some(mut b) if b.head.name == BoxType::ItemPropertyContainerBox => { + read_ipco(&mut b, strictness) + } + Some(_) => Err(Error::InvalidData("unexpected iprp child")), + None => Err(Error::UnexpectedEOF), + }?; + + let mut ipma_version_and_flag_values_seen = TryVec::with_capacity(1)?; + let mut association_entries = TryVec::::new(); + + while let Some(mut b) = iter.next_box()? { + if b.head.name != BoxType::ItemPropertyAssociationBox { + return Err(Error::InvalidData("unexpected iprp child")); + } + + let (version, flags) = read_fullbox_extra(&mut b)?; + if ipma_version_and_flag_values_seen.contains(&(version, flags)) { + fail_if( + strictness != ParseStrictness::Permissive, + "There shall be at most one ItemPropertyAssociationbox with a given pair of \ + values of version and flags \ + per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1", + )?; + } + if flags != 0 && properties.len() <= 127 { + fail_if( + strictness == ParseStrictness::Strict, + "Unless there are more than 127 properties in the ItemPropertyContainerBox, \ + flags should be equal to 0 \ + per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1", + )?; + } + ipma_version_and_flag_values_seen.push((version, flags))?; + for association_entry in read_ipma(&mut b, strictness, version, flags)? { + if let Some(previous_entry) = association_entries + .iter() + .find(|e| association_entry.item_id == e.item_id) + { + error!( + "Duplicate ipma entries for item_id\n1: {:?}\n2: {:?}", + previous_entry, association_entry + ); + // It's technically possible to make sense of this situation by merging ipma + // boxes, but this is a "shall" requirement, so we'd only do it in + // ParseStrictness::Permissive mode, and this hasn't shown up in the wild + return Err(Error::InvalidData( + "There shall be at most one occurrence of a given item_ID, \ + in the set of ItemPropertyAssociationBox boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1", + )); + } + + const TRANSFORM_ORDER_ERROR: &str = + "These properties, if used, shall be indicated to be applied \ + in the following order: clean aperture first, then rotation, \ + then mirror. \ + per MIAF (ISO/IEC 23000-22:2019) § 7.3.6.7"; + const TRANSFORM_ORDER: &[BoxType] = &[ + BoxType::CleanApertureBox, + BoxType::ImageRotation, + BoxType::ImageMirror, + ]; + let mut prev_transform_index = None; + // Realistically, there should only ever be 1 nclx and 1 icc + let mut colour_type_indexes: TryHashMap = + TryHashMap::with_capacity(2)?; + + for a in &association_entry.associations { + if a.property_index == PropertyIndex(0) { + if a.essential { + fail_if( + strictness != ParseStrictness::Permissive, + "the essential indicator shall be 0 for property index 0 \ + per ISOBMFF (ISO 14496-12:2020 § 8.11.14.3", + )?; + } + continue; + } + + if let Some(property) = properties.get(&a.property_index) { + assert!(brand == MIF1_BRAND); + match property { + ItemProperty::AV1Config(_) + | ItemProperty::Mirroring(_) + | ItemProperty::Rotation(_) => { + if !a.essential { + warn!("{:?} is invalid", property); + // This is a "shall", but it is likely to change, so only + // fail if using strict parsing. + // See https://github.com/mozilla/mp4parse-rust/issues/284 + fail_if( + strictness == ParseStrictness::Strict, + "All transformative properties associated with coded and \ + derived images required or conditionally required by this \ + document shall be marked as essential \ + per MIAF (ISO 23000-22:2019) § 7.3.9", + )?; + } + } + // XXX this is contrary to the published specification; see doc comment + // at the beginning of this function for more details + ItemProperty::Colour(colr) => { + let colour_type = colr.colour_type(); + if let Some(prev_colr_index) = colour_type_indexes.get(&colour_type) { + warn!( + "Multiple '{}' type colr associations with {:?}: {:?} and {:?}", + colour_type, + association_entry.item_id, + a.property_index, + prev_colr_index + ); + fail_if( + strictness != ParseStrictness::Permissive, + "Each item shall have at most one property association with a + ColourInformationBox (colr) for a given value of colour_type \ + per HEIF (ISO/IEC DIS 23008-12) § 6.5.5.1", + )?; + } else { + colour_type_indexes.insert(colour_type, a.property_index)?; + } + } + _ => {} + } + + if let Some(transform_index) = TRANSFORM_ORDER + .iter() + .position(|t| *t == property.box_type()) + { + if let Some(prev) = prev_transform_index { + if prev >= transform_index { + error!( + "{:?} after {:?}", + TRANSFORM_ORDER[transform_index], TRANSFORM_ORDER[prev] + ); + return Err(Error::InvalidData(TRANSFORM_ORDER_ERROR)); + } + } + prev_transform_index = Some(transform_index); + } + } else { + error!( + "Missing property at {:?} for {:?}", + a.property_index, association_entry.item_id + ); + fail_if( + strictness != ParseStrictness::Permissive, + "Invalid property index in ipma", + )?; + } + } + association_entries.push(association_entry)? + } + + check_parser_state!(b.content); + } + + let iprp = ItemPropertiesBox { + properties, + association_entries, + }; + trace!("read_iprp -> {:#?}", iprp); + Ok(iprp) +} + +/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 +#[derive(Debug)] +pub enum ItemProperty { + AuxiliaryType(AuxiliaryTypeProperty), + AV1Config(AV1ConfigBox), + Channels(PixelInformation), + Colour(ColourInformation), + ImageSpatialExtents(ImageSpatialExtentsProperty), + Mirroring(ImageMirror), + Rotation(ImageRotation), + /// Necessary to validate property indices in read_iprp + Unsupported(BoxType), +} + +impl ItemProperty { + fn box_type(&self) -> BoxType { + match self { + ItemProperty::AuxiliaryType(_) => BoxType::AuxiliaryTypeProperty, + ItemProperty::AV1Config(_) => BoxType::AV1CodecConfigurationBox, + ItemProperty::Colour(_) => BoxType::ColourInformationBox, + ItemProperty::Mirroring(_) => BoxType::ImageMirror, + ItemProperty::Rotation(_) => BoxType::ImageRotation, + ItemProperty::ImageSpatialExtents(_) => BoxType::ImageSpatialExtentsProperty, + ItemProperty::Channels(_) => BoxType::PixelInformationBox, + ItemProperty::Unsupported(box_type) => *box_type, + } + } +} + +#[derive(Debug)] +struct ItemPropertyAssociationEntry { + item_id: ItemId, + associations: TryVec, +} + +/// For storing ItemPropertyAssociation data +/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 +#[derive(Debug)] +struct Association { + essential: bool, + property_index: PropertyIndex, +} + +/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 +/// +/// The properties themselves are stored in `properties`, but the items they're +/// associated with are stored in `association_entries`. It's necessary to +/// maintain this indirection because multiple items can reference the same +/// property. For example, both the primary item and alpha item can share the +/// same [`ImageSpatialExtentsProperty`]. +#[derive(Debug, Default)] +pub struct ItemPropertiesBox { + /// `ItemPropertyContainerBox property_container` in the spec + properties: TryHashMap, + /// `ItemPropertyAssociationBox association[]` in the spec + association_entries: TryVec, +} + +impl ItemPropertiesBox { + /// For displayable images `av1C`, `pixi` and `ispe` are mandatory, `colr` + /// is typically included too, so we might as well use an even power of 2. + const MIN_PROPERTIES: usize = 4; + + fn is_alpha(&self, item_id: ItemId) -> bool { + match self.get(item_id, BoxType::AuxiliaryTypeProperty) { + Ok(Some(ItemProperty::AuxiliaryType(urn))) => { + urn.aux_type.as_slice() == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha".as_bytes() + } + Ok(Some(other_property)) => panic!("property key mismatch: {:?}", other_property), + Ok(None) => false, + Err(e) => { + error!( + "is_alpha: Error checking AuxiliaryTypeProperty ({}), returning false", + e + ); + false + } + } + } + + fn get_ispe(&self, item_id: ItemId) -> Result> { + if let Some(ItemProperty::ImageSpatialExtents(ispe)) = + self.get(item_id, BoxType::ImageSpatialExtentsProperty)? + { + Ok(Some(ispe)) + } else { + Ok(None) + } + } + + fn get(&self, item_id: ItemId, property_type: BoxType) -> Result> { + match self + .get_multiple(item_id, |prop| prop.box_type() == property_type)? + .as_slice() + { + &[] => Ok(None), + &[single_value] => Ok(Some(single_value)), + multiple_values => { + error!( + "Multiple values for {:?}: {:?}", + property_type, multiple_values + ); + // TODO: add test + Err(Error::InvalidData("conflicting item property values")) + } + } + } + + fn get_multiple( + &self, + item_id: ItemId, + filter: impl Fn(&ItemProperty) -> bool, + ) -> Result> { + let mut values = TryVec::new(); + for entry in &self.association_entries { + for a in &entry.associations { + if entry.item_id == item_id { + match self.properties.get(&a.property_index) { + Some(ItemProperty::Unsupported(_)) => {} + Some(property) if filter(property) => values.push(property)?, + _ => {} + } } } } + + Ok(values) + } +} + +/// An upper bound which can be used to check overflow at compile time +trait UpperBounded { + const MAX: u64; +} + +/// Implement type $name as a newtype wrapper around an unsigned int which +/// implements the UpperBounded trait. +macro_rules! impl_bounded { + ( $name:ident, $inner:ty ) => { + #[derive(Clone, Copy)] + pub struct $name($inner); + + impl $name { + pub const fn new(n: $inner) -> Self { + Self(n) + } + + #[allow(dead_code)] + pub fn get(self) -> $inner { + self.0 + } + } + + impl UpperBounded for $name { + const MAX: u64 = <$inner>::MAX as u64; + } + }; +} + +/// Implement type $name as a type representing the product of two unsigned ints +/// which implements the UpperBounded trait. +macro_rules! impl_bounded_product { + ( $name:ident, $multiplier:ty, $multiplicand:ty, $inner:ty) => { + #[derive(Clone, Copy)] + pub struct $name($inner); + + impl $name { + pub fn new(value: $inner) -> Self { + assert!(value <= Self::MAX); + Self(value) + } + + pub fn get(self) -> $inner { + self.0 + } + } + + impl UpperBounded for $name { + const MAX: u64 = <$multiplier>::MAX * <$multiplicand>::MAX; + } + }; +} + +mod bounded_uints { + use UpperBounded; + + impl_bounded!(U8, u8); + impl_bounded!(U16, u16); + impl_bounded!(U32, u32); + impl_bounded!(U64, u64); + + impl_bounded_product!(U32MulU8, U32, U8, u64); + impl_bounded_product!(U32MulU16, U32, U16, u64); + + impl UpperBounded for std::num::NonZeroU8 { + const MAX: u64 = u8::MAX as u64; + } +} + +use bounded_uints::*; + +/// Implement the multiplication operator for $lhs * $rhs giving $output, which +/// is internally represented as $inner. The operation is statically checked +/// to ensure the product won't overflow $inner, nor exceed <$output>::MAX. +macro_rules! impl_mul { + ( ($lhs:ty , $rhs:ty) => ($output:ty, $inner:ty) ) => { + impl std::ops::Mul<$rhs> for $lhs { + type Output = $output; + + fn mul(self, rhs: $rhs) -> Self::Output { + static_assertions::const_assert!(<$output>::MAX <= <$inner>::MAX as u64); + static_assertions::const_assert!(<$lhs>::MAX * <$rhs>::MAX <= <$output>::MAX); + + let lhs: $inner = self.get().into(); + let rhs: $inner = rhs.get().into(); + Self::Output::new(lhs.checked_mul(rhs).expect("infallible")) + } + } + }; +} + +impl_mul!((U8, std::num::NonZeroU8) => (U16, u16)); +impl_mul!((U32, std::num::NonZeroU8) => (U32MulU8, u64)); +impl_mul!((U32, U16) => (U32MulU16, u64)); + +impl std::ops::Add for U32MulU8 { + type Output = U64; + + fn add(self, rhs: U32MulU16) -> Self::Output { + static_assertions::const_assert!(U32MulU8::MAX + U32MulU16::MAX < U64::MAX); + let lhs: u64 = self.get(); + let rhs: u64 = rhs.get(); + Self::Output::new(lhs.checked_add(rhs).expect("infallible")) + } +} + +const MAX_IPMA_ASSOCIATION_COUNT: U8 = U8::new(u8::MAX); + +/// After reading only the `entry_count` field of an ipma box, we can check its +/// basic validity and calculate (assuming validity) the number of associations +/// which will be contained (allowing preallocation of the storage). +/// All the arithmetic is compile-time verified to not overflow via supporting +/// types implementing the UpperBounded trait. Types are declared explicitly to +/// show there isn't any accidental inference to primitive types. +/// +/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 +fn calculate_ipma_total_associations( + version: u8, + bytes_left: u64, + entry_count: U32, + num_association_bytes: std::num::NonZeroU8, +) -> Result { + let min_entry_bytes = + std::num::NonZeroU8::new(1 /* association_count */ + if version == 0 { 2 } else { 4 }) + .unwrap(); + + let total_non_association_bytes: U32MulU8 = entry_count * min_entry_bytes; + let total_association_bytes: u64; + + if let Some(difference) = bytes_left.checked_sub(total_non_association_bytes.get()) { + // All the storage for the `essential` and `property_index` parts (assuming a valid ipma box size) + total_association_bytes = difference; + } else { + return Err(Error::InvalidData( + "ipma box below minimum size for entry_count", + )); } - context.primary_item = primary_item_extents_data.concat(); + let max_association_bytes_per_entry: U16 = MAX_IPMA_ASSOCIATION_COUNT * num_association_bytes; + let max_total_association_bytes: U32MulU16 = entry_count * max_association_bytes_per_entry; + let max_bytes_left: U64 = total_non_association_bytes + max_total_association_bytes; + + if bytes_left > max_bytes_left.get() { + return Err(Error::InvalidData( + "ipma box exceeds maximum size for entry_count", + )); + } + + let total_associations: u64 = total_association_bytes / u64::from(num_association_bytes.get()); + + Ok(total_associations.try_into()?) +} + +/// Parse an ItemPropertyAssociation box +/// +/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 +fn read_ipma( + src: &mut BMFFBox, + strictness: ParseStrictness, + version: u8, + flags: u32, +) -> Result> { + let entry_count = be_u32(src)?; + let num_association_bytes = + std::num::NonZeroU8::new(if flags & 1 == 1 { 2 } else { 1 }).unwrap(); + + let total_associations = calculate_ipma_total_associations( + version, + src.bytes_left(), + U32::new(entry_count), + num_association_bytes, + )?; + // Assuming most items will have at least `MIN_PROPERTIES` and knowing the + // total number of item -> property associations (`total_associations`), + // we can provide a good estimate for how many elements we'll need in this + // vector, even though we don't know precisely how many items there will be + // properties for. + let mut entries = TryVec::::with_capacity( + total_associations / ItemPropertiesBox::MIN_PROPERTIES, + )?; + + for _ in 0..entry_count { + let item_id = ItemId::read(src, version)?; + + if let Some(previous_association) = entries.last() { + #[allow(clippy::comparison_chain)] + if previous_association.item_id > item_id { + return Err(Error::InvalidData( + "Each ItemPropertyAssociation box shall be ordered by increasing item_ID", + )); + } else if previous_association.item_id == item_id { + return Err(Error::InvalidData("There shall be at most one association box for each item_ID, in any ItemPropertyAssociation box")); + } + } + + let association_count = src.read_u8()?; + let mut associations = TryVec::with_capacity(association_count.to_usize())?; + for _ in 0..association_count { + let association = src + .take(num_association_bytes.get().into()) + .read_into_try_vec()?; + let mut association = BitReader::new(association.as_slice()); + let essential = association.read_bool()?; + let property_index = + PropertyIndex(association.read_u16(association.remaining().try_into()?)?); + associations.push(Association { + essential, + property_index, + })?; + } - Ok(()) -} + entries.push(ItemPropertyAssociationEntry { + item_id, + associations, + })?; + } -/// Parse a metadata box in the context of an AVIF -/// Currently requires the primary item to be an av01 item type and generates -/// an error otherwise. -/// See ISO 14496-12:2015 § 8.11.1 -fn read_avif_meta(src: &mut BMFFBox) -> Result { - let version = read_fullbox_version_no_flags(src)?; + check_parser_state!(src.content); if version != 0 { - return Err(Error::Unsupported("unsupported meta version")); + if let Some(ItemPropertyAssociationEntry { + item_id: max_item_id, + .. + }) = entries.last() + { + if *max_item_id <= ItemId(u16::MAX.into()) { + fail_if( + strictness == ParseStrictness::Strict, + "The ipma version 0 should be used unless 32-bit item_ID values are needed \ + per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1", + )?; + } + } } - let mut primary_item_id = None; - let mut item_infos = None; - let mut iloc_items = None; + trace!("read_ipma -> {:#?}", entries); + + Ok(entries) +} +/// Parse an ItemPropertyContainerBox +/// +/// For unsupported properties that we know about, return specific +/// [`Status`] UnsupportedXXXX variants. Unless running in +/// [`ParseStrictness::Permissive`] mode, in which case, unsupported properties +/// will be ignored. +/// +/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 +fn read_ipco( + src: &mut BMFFBox, + strictness: ParseStrictness, +) -> Result> { + let mut properties = TryHashMap::with_capacity(ItemPropertiesBox::MIN_PROPERTIES)?; + + let mut index = PropertyIndex(1); // ipma uses 1-based indexing let mut iter = src.box_iter(); while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::ItemInfoBox => { - if item_infos.is_some() { - return Err(Error::InvalidData( - "There should be zero or one iinf boxes per ISO 14496-12:2015 § 8.11.6.1", - )); - } - item_infos = Some(read_iinf(&mut b)?); + if let Some(property) = match b.head.name { + BoxType::AuxiliaryTypeProperty => Some(ItemProperty::AuxiliaryType(read_auxc(&mut b)?)), + BoxType::AV1CodecConfigurationBox => Some(ItemProperty::AV1Config(read_av1c(&mut b)?)), + BoxType::AV1LayeredImageIndexingProperty + if strictness != ParseStrictness::Permissive => + { + return Err(Error::from(Status::UnsupportedA1lx)) } - BoxType::ItemLocationBox => { - if iloc_items.is_some() { - return Err(Error::InvalidData( - "There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.3.1", - )); - } - iloc_items = Some(read_iloc(&mut b)?); + BoxType::CleanApertureBox if strictness != ParseStrictness::Permissive => { + return Err(Error::from(Status::UnsupportedClap)) } - BoxType::PrimaryItemBox => { - if primary_item_id.is_some() { - return Err(Error::InvalidData( - "There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.4.1", - )); - } - primary_item_id = Some(read_pitm(&mut b)?); + BoxType::ColourInformationBox => { + Some(ItemProperty::Colour(read_colr(&mut b, strictness)?)) } - _ => skip_box_content(&mut b)?, + BoxType::ImageMirror => Some(ItemProperty::Mirroring(read_imir(&mut b)?)), + BoxType::ImageRotation => Some(ItemProperty::Rotation(read_irot(&mut b)?)), + BoxType::ImageSpatialExtentsProperty => { + Some(ItemProperty::ImageSpatialExtents(read_ispe(&mut b)?)) + } + BoxType::LayerSelectorProperty if strictness != ParseStrictness::Permissive => { + return Err(Error::from(Status::UnsupportedLsel)) + } + BoxType::OperatingPointSelectorProperty + if strictness != ParseStrictness::Permissive => + { + return Err(Error::from(Status::UnsupportedA1op)) + } + BoxType::PixelInformationBox => Some(ItemProperty::Channels(read_pixi(&mut b)?)), + other_box_type => { + // Though we don't do anything with other property types, we still store + // a record at the index to identify invalid indices in ipma boxes + skip_box_remain(&mut b)?; + let item_property = ItemProperty::Unsupported(other_box_type); + debug!("Storing empty record {:?}", item_property); + Some(item_property) + } + } { + properties.insert(index, property)?; } + index = PropertyIndex( + index + .0 + .checked_add(1) // must include ignored properties to have correct indexes + .ok_or(Error::InvalidData("ipco index overflow"))?, + ); + check_parser_state!(b.content); } - let primary_item_id = primary_item_id.ok_or(Error::InvalidData( - "Required pitm box not present in meta box", - ))?; + Ok(properties) +} - if let Some(item_info) = item_infos - .iter() - .flatten() - .find(|x| x.item_id == primary_item_id) - { - if &item_info.item_type.to_be_bytes() != b"av01" { - warn!( - "primary_item_id type: {}", - be_u32_to_string(item_info.item_type) - ); - return Err(Error::InvalidData("primary_item_id type is not av01")); - } - } else { - return Err(Error::InvalidData( - "primary_item_id not present in iinf box", - )); - } +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ImageSpatialExtentsProperty { + image_width: u32, + image_height: u32, +} - if let Some(loc) = iloc_items - .iter() - .flatten() - .find(|loc| loc.item_id == primary_item_id) - { - Ok(loc.clone()) - } else { - Err(Error::InvalidData( - "primary_item_id not present in iloc box", - )) +/// Parse image spatial extents property +/// +/// See HEIF (ISO 23008-12:2017) § 6.5.3.1 +fn read_ispe(src: &mut BMFFBox) -> Result { + if read_fullbox_version_no_flags(src)? != 0 { + return Err(Error::Unsupported("ispe version")); } -} -/// Parse a Primary Item Box -/// See ISO 14496-12:2015 § 8.11.4 -fn read_pitm(src: &mut BMFFBox) -> Result { - let version = read_fullbox_version_no_flags(src)?; + let image_width = be_u32(src)?; + let image_height = be_u32(src)?; - let item_id = match version { - 0 => be_u16(src)?.into(), - 1 => be_u32(src)?, - _ => return Err(Error::Unsupported("unsupported pitm version")), - }; + Ok(ImageSpatialExtentsProperty { + image_width, + image_height, + }) +} - Ok(item_id) +#[derive(Debug)] +pub struct PixelInformation { + bits_per_channel: TryVec, } -/// Parse an Item Information Box -/// See ISO 14496-12:2015 § 8.11.6 -fn read_iinf(src: &mut BMFFBox) -> Result> { +/// Parse pixel information +/// See HEIF (ISO 23008-12:2017) § 6.5.6 +fn read_pixi(src: &mut BMFFBox) -> Result { let version = read_fullbox_version_no_flags(src)?; + if version != 0 { + return Err(Error::Unsupported("pixi version")); + } - match version { - 0 | 1 => (), - _ => return Err(Error::Unsupported("unsupported iinf version")), + let num_channels = src.read_u8()?; + let mut bits_per_channel = TryVec::with_capacity(num_channels.to_usize())?; + let num_channels_read = src.try_read_to_end(&mut bits_per_channel)?; + + if u8::try_from(num_channels_read)? != num_channels { + return Err(Error::InvalidData("invalid num_channels")); } - let entry_count = if version == 0 { - be_u16(src)?.to_usize() - } else { - be_u32(src)?.to_usize() - }; - let mut item_infos = vec_with_capacity(entry_count)?; + check_parser_state!(src.content); + Ok(PixelInformation { bits_per_channel }) +} - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - if b.head.name != BoxType::ItemInfoEntry { - return Err(Error::InvalidData( - "iinf box should contain only infe boxes", - )); - } +/// Despite [Rec. ITU-T H.273] (12/2016) defining the CICP fields as having a +/// range of 0-255, and only a small fraction of those values being used, +/// ISOBMFF (ISO 14496-12:2020) § 12.1.5 defines them as 16-bit values in the +/// `colr` box. Since we have no use for the additional range, and it would +/// complicate matters later, we fallibly convert before storing the input. +/// +/// [Rec. ITU-T H.273]: https://www.itu.int/rec/T-REC-H.273-201612-I/en +#[repr(C)] +#[derive(Debug)] +pub struct NclxColourInformation { + colour_primaries: u8, + transfer_characteristics: u8, + matrix_coefficients: u8, + full_range_flag: bool, +} - vec_push(&mut item_infos, read_infe(&mut b)?)?; +/// The raw bytes of the ICC profile +#[repr(C)] +pub struct IccColourInformation { + bytes: TryVec, +} - check_parser_state!(b.content); +impl fmt::Debug for IccColourInformation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("IccColourInformation") + .field("data", &format_args!("{} bytes", self.bytes.len())) + .finish() } +} - Ok(item_infos) +#[repr(C)] +#[derive(Debug)] +pub enum ColourInformation { + Nclx(NclxColourInformation), + Icc(IccColourInformation, FourCC), } -fn be_u32_to_string(src: u32) -> String { - String::from_utf8(src.to_be_bytes().to_vec()).unwrap_or(format!("{:x?}", src)) +impl ColourInformation { + fn colour_type(&self) -> FourCC { + match self { + Self::Nclx(_) => FourCC::from(*b"nclx"), + Self::Icc(_, colour_type) => colour_type.clone(), + } + } } -/// Parse an Item Info Entry -/// See ISO 14496-12:2015 § 8.11.6.2 -fn read_infe(src: &mut BMFFBox) -> Result { - // According to the standard, it seems the flags field should be 0, but - // at least one sample AVIF image has a nonzero value. - let (version, _) = read_fullbox_extra(src)?; +/// Parse colour information +/// See ISOBMFF (ISO 14496-12:2020) § 12.1.5 +fn read_colr( + src: &mut BMFFBox, + strictness: ParseStrictness, +) -> Result { + let colour_type = be_u32(src)?.to_be_bytes(); + + match &colour_type { + b"nclx" => { + const NUM_RESERVED_BITS: u8 = 7; + let colour_primaries = be_u16(src)?.try_into()?; + let transfer_characteristics = be_u16(src)?.try_into()?; + let matrix_coefficients = be_u16(src)?.try_into()?; + let bytes = src.read_into_try_vec()?; + let mut bit_reader = BitReader::new(&bytes); + let full_range_flag = bit_reader.read_bool()?; + if bit_reader.remaining() != NUM_RESERVED_BITS.into() { + error!( + "read_colr expected {} reserved bits, found {}", + NUM_RESERVED_BITS, + bit_reader.remaining() + ); + return Err(Error::InvalidData("Unexpected size for colr box")); + } + if bit_reader.read_u8(NUM_RESERVED_BITS)? != 0 { + fail_if( + strictness != ParseStrictness::Permissive, + "The 7 reserved bits at the end of the ColourInformationBox \ + for colour_type == 'nclx' must be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 12.1.5.2", + )?; + } - // mif1 brand (see ISO 23008-12:2017 § 10.2.1) only requires v2 and 3 - let item_id = match version { - 2 => be_u16(src)?.into(), - 3 => be_u32(src)?, - _ => return Err(Error::Unsupported("unsupported version in 'infe' box")), + Ok(ColourInformation::Nclx(NclxColourInformation { + colour_primaries, + transfer_characteristics, + matrix_coefficients, + full_range_flag, + })) + } + b"rICC" | b"prof" => Ok(ColourInformation::Icc( + IccColourInformation { + bytes: src.read_into_try_vec()?, + }, + FourCC::from(colour_type), + )), + _ => { + error!("read_colr colour_type: {:?}", colour_type); + Err(Error::InvalidData( + "Unsupported colour_type for ColourInformationBox", + )) + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +/// Rotation in the positive (that is, anticlockwise) direction +/// Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR +/// similar to a DIGIT ONE (1) +pub enum ImageRotation { + /// ⥠ UPWARDS HARPOON WITH BARB LEFT FROM BAR + D0, + /// ⥞ LEFTWARDS HARPOON WITH BARB DOWN FROM BAR + D90, + /// ⥝ DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR + D180, + /// ⥛ RIGHTWARDS HARPOON WITH BARB UP FROM BAR + D270, +} + +/// Parse image rotation box +/// See HEIF (ISO 23008-12:2017) § 6.5.10 +fn read_irot(src: &mut BMFFBox) -> Result { + let irot = src.read_into_try_vec()?; + let mut irot = BitReader::new(&irot); + let _reserved = irot.read_u8(6)?; + let image_rotation = match irot.read_u8(2)? { + 0 => ImageRotation::D0, + 1 => ImageRotation::D90, + 2 => ImageRotation::D180, + 3 => ImageRotation::D270, + _ => unreachable!(), }; - let item_protection_index = be_u16(src)?; + check_parser_state!(src.content); - if item_protection_index != 0 { - return Err(Error::Unsupported( - "protected items (infe.item_protection_index != 0) are not supported", - )); + Ok(image_rotation) +} + +/// The axis about which the image is mirrored (opposite of flip) +/// Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR +/// similar to a DIGIT ONE (1) +#[repr(C)] +#[derive(Debug)] +pub enum ImageMirror { + /// top and bottom parts exchanged + /// ⥡ DOWNWARDS HARPOON WITH BARB LEFT FROM BAR + TopBottom, + /// left and right parts exchanged + /// ⥜ UPWARDS HARPOON WITH BARB RIGHT FROM BAR + LeftRight, +} + +/// Parse image mirroring box +/// See HEIF (ISO 23008-12:2017) § 6.5.12
+/// Note: [ISO/IEC 23008-12:2017/DAmd 2](https://www.iso.org/standard/81688.html) +/// reverses the interpretation of the 'imir' box in § 6.5.12.3: +/// > `axis` specifies a vertical (`axis` = 0) or horizontal (`axis` = 1) axis +/// > for the mirroring operation. +/// +/// is replaced with: +/// > `mode` specifies how the mirroring is performed: 0 indicates that the top +/// > and bottom parts of the image are exchanged; 1 specifies that the left and +/// > right parts are exchanged. +/// > +/// > NOTE: In Exif, orientation tag can be used to signal mirroring operations. +/// > Exif orientation tag 4 corresponds to `mode` = 0 of `ImageMirror`, and +/// > Exif orientation tag 2 corresponds to `mode` = 1 accordingly. +/// +/// This implementation conforms to the text in Draft Amendment 2, which is the +/// opposite of the published standard as of 4 June 2021. +fn read_imir(src: &mut BMFFBox) -> Result { + let imir = src.read_into_try_vec()?; + let mut imir = BitReader::new(&imir); + let _reserved = imir.read_u8(7)?; + let image_mirror = match imir.read_u8(1)? { + 0 => ImageMirror::TopBottom, + 1 => ImageMirror::LeftRight, + _ => unreachable!(), + }; + + check_parser_state!(src.content); + + Ok(image_mirror) +} + +/// See HEIF (ISO 23008-12:2017) § 6.5.8 +#[derive(Debug, PartialEq)] +pub struct AuxiliaryTypeProperty { + aux_type: TryString, + aux_subtype: TryString, +} + +/// Parse image properties for auxiliary images +/// See HEIF (ISO 23008-12:2017) § 6.5.8 +fn read_auxc(src: &mut BMFFBox) -> Result { + let version = read_fullbox_version_no_flags(src)?; + if version != 0 { + return Err(Error::Unsupported("auxC version")); } - let item_type = be_u32(src)?; - debug!( - "infe item_id {} item_type: {}", - item_id, - be_u32_to_string(item_type) - ); + let mut aux = TryString::new(); + src.try_read_to_end(&mut aux)?; - // There are some additional fields here, but they're not of interest to us - skip_box_remain(src)?; + let (aux_type, aux_subtype): (TryString, TryVec); + if let Some(nul_byte_pos) = aux.iter().position(|&b| b == b'\0') { + let (a, b) = aux.as_slice().split_at(nul_byte_pos); + aux_type = a.try_into()?; + aux_subtype = (&b[1..]).try_into()?; + } else { + aux_type = aux; + aux_subtype = TryVec::new(); + } - Ok(ItemInfoEntry { item_id, item_type }) + Ok(AuxiliaryTypeProperty { + aux_type, + aux_subtype, + }) } /// Parse an item location box inside a meta box -/// See ISO 14496-12:2015 § 8.11.3 -fn read_iloc(src: &mut BMFFBox) -> Result> { +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.3 +fn read_iloc(src: &mut BMFFBox) -> Result> { let version: IlocVersion = read_fullbox_version_no_flags(src)?.try_into()?; - let mut iloc = vec_with_capacity(src.bytes_left().try_into()?)?; - src.read_to_end(&mut iloc)?; - let mut iloc = BitReader::new(iloc.as_slice()); + let iloc = src.read_into_try_vec()?; + let mut iloc = BitReader::new(&iloc); let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; @@ -1538,13 +3065,13 @@ fn read_iloc(src: &mut BMFFBox) -> Result> IlocVersion::Two => iloc.read_u32(32)?, }; - let mut items = vec_with_capacity(item_count.to_usize())?; + let mut items = TryHashMap::with_capacity(item_count.to_usize())?; for _ in 0..item_count { - let item_id = match version { + let item_id = ItemId(match version { IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?, IlocVersion::Two => iloc.read_u32(32)?, - }; + }); // The spec isn't entirely clear how an `iloc` should be interpreted for version 0, // which has no `construction_method` field. It does say: @@ -1559,7 +3086,7 @@ fn read_iloc(src: &mut BMFFBox) -> Result> 0 => ConstructionMethod::File, 1 => ConstructionMethod::Idat, 2 => return Err(Error::Unsupported("construction_method 'item_offset' is not supported")), - _ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISO 14496-12:2015 § 8.11.3.3")) + _ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISOBMFF (ISO 14496-12:2015) § 8.11.3.3")) } } }; @@ -1572,73 +3099,82 @@ fn read_iloc(src: &mut BMFFBox) -> Result> )); } - let base_offset = iloc.read_u64(base_offset_size.to_bits())?; + let base_offset = iloc.read_u64(base_offset_size.as_bits())?; let extent_count = iloc.read_u16(16)?; if extent_count < 1 { return Err(Error::InvalidData( - "extent_count must have a value 1 or greater per ISO 14496-12:2015 § 8.11.3.3", + "extent_count must have a value 1 or greater per ISOBMFF (ISO 14496-12:2015) § 8.11.3.3", + )); + } + + // "If only one extent is used (extent_count = 1) then either or both of the + // offset and length may be implied" + if extent_count != 1 + && (offset_size == IlocFieldSize::Zero || length_size == IlocFieldSize::Zero) + { + return Err(Error::InvalidData( + "extent_count != 1 requires explicit offset and length per ISOBMFF (ISO 14496-12:2015) § 8.11.3.3", )); } - let mut extents = vec_with_capacity(extent_count.to_usize())?; + let mut extents = TryVec::with_capacity(extent_count.to_usize())?; for _ in 0..extent_count { - // Parsed but currently ignored, see `ItemLocationBoxExtent` + // Parsed but currently ignored, see `Extent` let _extent_index = match &index_size { None | Some(IlocFieldSize::Zero) => None, Some(index_size) => { debug_assert!(version == IlocVersion::One || version == IlocVersion::Two); - Some(iloc.read_u64(index_size.to_bits())?) + Some(iloc.read_u64(index_size.as_bits())?) } }; - // Per ISO 14496-12:2015 § 8.11.3.1: + // Per ISOBMFF (ISO 14496-12:2015) § 8.11.3.1: // "If the offset is not identified (the field has a length of zero), then the // beginning of the source (offset 0) is implied" // This behavior will follow from BitReader::read_u64(0) -> 0. - let extent_offset = iloc.read_u64(offset_size.to_bits())?; - let extent_length = iloc.read_u64(length_size.to_bits())?; + let extent_offset = iloc.read_u64(offset_size.as_bits())?; + let extent_length = iloc.read_u64(length_size.as_bits())?.try_into()?; // "If the length is not specified, or specified as zero, then the entire length of // the source is implied" (ibid) - let start = base_offset + let offset = base_offset .checked_add(extent_offset) .ok_or(Error::InvalidData("offset calculation overflow"))?; - let extent_range = if extent_length == 0 { - ExtentRange::ToEnd(RangeFrom { start }) + let extent = if extent_length == 0 { + Extent::ToEnd { offset } } else { - let end = start - .checked_add(extent_length) - .ok_or(Error::InvalidData("end calculation overflow"))?; - ExtentRange::WithLength(Range { start, end }) + Extent::WithLength { + offset, + len: extent_length, + } }; - vec_push(&mut extents, ItemLocationBoxExtent { extent_range })?; + extents.push(extent)?; } - vec_push( - &mut items, - ItemLocationBoxItem { - item_id, - construction_method, - extents, - }, - )?; - } + let loc = ItemLocationBoxItem { + construction_method, + extents, + }; - debug_assert_eq!(iloc.remaining(), 0); + if items.insert(item_id, loc)?.is_some() { + return Err(Error::InvalidData("duplicate item_ID in iloc")); + } + } - Ok(items) + if iloc.remaining() == 0 { + Ok(items) + } else { + Err(Error::InvalidData("invalid iloc size")) + } } /// Read the contents of a box, including sub boxes. -/// -/// Metadata is accumulated in the passed-through `MediaContext` struct, -/// which can be examined later. -pub fn read_mp4(f: &mut T, context: &mut MediaContext) -> Result<()> { +pub fn read_mp4(f: &mut T) -> Result { + let mut context = None; let mut found_ftyp = false; - let mut found_moov = false; // TODO(kinetik): Top-level parsing should handle zero-sized boxes // rather than throwing an error. let mut iter = BoxIter::new(f); @@ -1665,13 +3201,18 @@ pub fn read_mp4(f: &mut T, context: &mut MediaContext) -> Result<()> { debug!("{:?}", ftyp); } BoxType::MovieBox => { - read_moov(&mut b, context)?; - found_moov = true; + context = Some(read_moov(&mut b, context)?); + } + #[cfg(feature = "meta-xml")] + BoxType::MetadataBox => { + if let Some(ctx) = &mut context { + ctx.metadata = Some(read_meta(&mut b)); + } } _ => skip_box_content(&mut b)?, }; check_parser_state!(b.content); - if found_moov { + if context.is_some() { debug!( "found moov {}, could stop pure 'moov' parser now", if found_ftyp { @@ -1686,87 +3227,111 @@ pub fn read_mp4(f: &mut T, context: &mut MediaContext) -> Result<()> { // XXX(kinetik): This isn't perfect, as a "moov" with no contents is // treated as okay but we haven't found anything useful. Needs more // thought for clearer behaviour here. - if found_moov { - Ok(()) - } else { - Err(Error::NoMoov) - } + context.ok_or(Error::NoMoov) } -fn parse_mvhd(f: &mut BMFFBox) -> Result<(MovieHeaderBox, Option)> { +/// Parse a Movie Header Box +/// See ISOBMFF (ISO 14496-12:2015) § 8.2.2 +fn parse_mvhd(f: &mut BMFFBox) -> Result> { let mvhd = read_mvhd(f)?; + debug!("{:?}", mvhd); if mvhd.timescale == 0 { return Err(Error::InvalidData("zero timescale in mdhd")); } let timescale = Some(MediaTimeScale(u64::from(mvhd.timescale))); - Ok((mvhd, timescale)) -} + Ok(timescale) +} + +/// Parse a Movie Box +/// See ISOBMFF (ISO 14496-12:2015) § 8.2.1 +/// Note that despite the spec indicating "exactly one" moov box should exist at +/// the file container level, we support reading and merging multiple moov boxes +/// such as with tests/test_case_1185230.mp4. +fn read_moov(f: &mut BMFFBox, context: Option) -> Result { + let MediaContext { + mut timescale, + mut tracks, + mut mvex, + mut psshs, + mut userdata, + #[cfg(feature = "meta-xml")] + metadata, + } = context.unwrap_or_default(); -fn read_moov(f: &mut BMFFBox, context: &mut MediaContext) -> Result<()> { let mut iter = f.box_iter(); while let Some(mut b) = iter.next_box()? { match b.head.name { BoxType::MovieHeaderBox => { - let (mvhd, timescale) = parse_mvhd(&mut b)?; - context.timescale = timescale; - debug!("{:?}", mvhd); + timescale = parse_mvhd(&mut b)?; } BoxType::TrackBox => { - let mut track = Track::new(context.tracks.len()); + let mut track = Track::new(tracks.len()); read_trak(&mut b, &mut track)?; - vec_push(&mut context.tracks, track)?; + tracks.push(track)?; } BoxType::MovieExtendsBox => { - let mvex = read_mvex(&mut b)?; + mvex = Some(read_mvex(&mut b)?); debug!("{:?}", mvex); - context.mvex = Some(mvex); } BoxType::ProtectionSystemSpecificHeaderBox => { let pssh = read_pssh(&mut b)?; debug!("{:?}", pssh); - vec_push(&mut context.psshs, pssh)?; + psshs.push(pssh)?; } BoxType::UserdataBox => { - let udta = read_udta(&mut b); - debug!("{:?}", udta); - context.userdata = Some(udta); + userdata = Some(read_udta(&mut b)); + debug!("{:?}", userdata); + if let Some(Err(_)) = userdata { + // There was an error parsing userdata. Such failures are not fatal to overall + // parsing, just skip the rest of the box. + skip_box_remain(&mut b)?; + } } _ => skip_box_content(&mut b)?, }; check_parser_state!(b.content); } - Ok(()) + + Ok(MediaContext { + timescale, + tracks, + mvex, + psshs, + userdata, + #[cfg(feature = "meta-xml")] + metadata, + }) } fn read_pssh(src: &mut BMFFBox) -> Result { let len = src.bytes_left(); let mut box_content = read_buf(src, len)?; let (system_id, kid, data) = { - let pssh = &mut Cursor::new(box_content.as_slice()); + let pssh = &mut Cursor::new(&box_content); let (version, _) = read_fullbox_extra(pssh)?; let system_id = read_buf(pssh, 16)?; - let mut kid: Vec = Vec::new(); + let mut kid = TryVec::::new(); if version > 0 { - let count = be_u32_with_limit(pssh)?; + let count = be_u32(pssh)?; for _ in 0..count { let item = read_buf(pssh, 16)?; - vec_push(&mut kid, item)?; + kid.push(item)?; } } - let data_size = be_u32_with_limit(pssh)?; + let data_size = be_u32(pssh)?; let data = read_buf(pssh, data_size.into())?; (system_id, kid, data) }; - let mut pssh_box = Vec::new(); + let mut pssh_box = TryVec::new(); write_be_u32(&mut pssh_box, src.head.size.try_into()?)?; - pssh_box.extend_from_slice(b"pssh"); - pssh_box.append(&mut box_content); + pssh_box.extend_from_slice(b"pssh")?; + pssh_box.append(&mut box_content)?; Ok(ProtectionSystemSpecificHeaderBox { system_id, @@ -1776,6 +3341,8 @@ fn read_pssh(src: &mut BMFFBox) -> Result(src: &mut BMFFBox) -> Result { let mut iter = src.box_iter(); let mut fragment_duration = None; @@ -1801,6 +3368,8 @@ fn read_mehd(src: &mut BMFFBox) -> Result { Ok(MediaScaledTime(fragment_duration)) } +/// Parse a Track Box +/// See ISOBMFF (ISO 14496-12:2015) § 8.3.1. fn read_trak(f: &mut BMFFBox, track: &mut Track) -> Result<()> { let mut iter = f.box_iter(); while let Some(mut b) = iter.next_box()? { @@ -1893,12 +3462,12 @@ fn read_mdia(f: &mut BMFFBox, track: &mut Track) -> Result<()> { debug!("{:?}", mdhd); } BoxType::HandlerBox => { - let hdlr = read_hdlr(&mut b)?; + let hdlr = read_hdlr(&mut b, ParseStrictness::Permissive)?; match hdlr.handler_type.value.as_ref() { - "vide" => track.track_type = TrackType::Video, - "soun" => track.track_type = TrackType::Audio, - "meta" => track.track_type = TrackType::Metadata, + b"vide" => track.track_type = TrackType::Video, + b"soun" => track.track_type = TrackType::Audio, + b"meta" => track.track_type = TrackType::Metadata, _ => (), } debug!("{:?}", hdlr); @@ -1975,6 +3544,7 @@ fn read_stbl(f: &mut BMFFBox, track: &mut Track) -> Result<()> { } /// Parse an ftyp box. +/// See ISOBMFF (ISO 14496-12:2015) § 4.3 fn read_ftyp(src: &mut BMFFBox) -> Result { let major = be_u32(src)?; let minor = be_u32(src)?; @@ -1984,9 +3554,9 @@ fn read_ftyp(src: &mut BMFFBox) -> Result { } // Is a brand_count of zero valid? let brand_count = bytes_left / 4; - let mut brands = Vec::new(); + let mut brands = TryVec::with_capacity(brand_count.try_into()?)?; for _ in 0..brand_count { - vec_push(&mut brands, From::from(be_u32(src)?))?; + brands.push(be_u32(src)?.into())?; } Ok(FileTypeBox { major_brand: From::from(major), @@ -2080,10 +3650,11 @@ fn read_tkhd(src: &mut BMFFBox) -> Result { } /// Parse a elst box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.6.6 fn read_elst(src: &mut BMFFBox) -> Result { let (version, _) = read_fullbox_extra(src)?; - let edit_count = be_u32_with_limit(src)?; - let mut edits = Vec::new(); + let edit_count = be_u32(src)?; + let mut edits = TryVec::with_capacity(edit_count.to_usize())?; for _ in 0..edit_count { let (segment_duration, media_time) = match version { 1 => { @@ -2098,15 +3669,12 @@ fn read_elst(src: &mut BMFFBox) -> Result { }; let media_rate_integer = be_i16(src)?; let media_rate_fraction = be_i16(src)?; - vec_push( - &mut edits, - Edit { - segment_duration, - media_time, - media_rate_integer, - media_rate_fraction, - }, - )?; + edits.push(Edit { + segment_duration, + media_time, + media_rate_integer, + media_rate_fraction, + })?; } // Padding could be added in some contents. @@ -2158,12 +3726,13 @@ fn read_mdhd(src: &mut BMFFBox) -> Result { } /// Parse a stco box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.7.5 fn read_stco(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; - let offset_count = be_u32_with_limit(src)?; - let mut offsets = Vec::new(); + let offset_count = be_u32(src)?; + let mut offsets = TryVec::with_capacity(offset_count.to_usize())?; for _ in 0..offset_count { - vec_push(&mut offsets, u64::from(be_u32(src)?))?; + offsets.push(be_u32(src)?.into())?; } // Padding could be added in some contents. @@ -2173,12 +3742,13 @@ fn read_stco(src: &mut BMFFBox) -> Result { } /// Parse a co64 box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.7.5 fn read_co64(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; - let offset_count = be_u32_with_limit(src)?; - let mut offsets = Vec::new(); + let offset_count = be_u32(src)?; + let mut offsets = TryVec::with_capacity(offset_count.to_usize())?; for _ in 0..offset_count { - vec_push(&mut offsets, be_u64(src)?)?; + offsets.push(be_u64(src)?)?; } // Padding could be added in some contents. @@ -2188,12 +3758,13 @@ fn read_co64(src: &mut BMFFBox) -> Result { } /// Parse a stss box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.6.2 fn read_stss(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; - let sample_count = be_u32_with_limit(src)?; - let mut samples = Vec::new(); + let sample_count = be_u32(src)?; + let mut samples = TryVec::with_capacity(sample_count.to_usize())?; for _ in 0..sample_count { - vec_push(&mut samples, be_u32(src)?)?; + samples.push(be_u32(src)?)?; } // Padding could be added in some contents. @@ -2203,22 +3774,20 @@ fn read_stss(src: &mut BMFFBox) -> Result { } /// Parse a stsc box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.7.4 fn read_stsc(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; - let sample_count = be_u32_with_limit(src)?; - let mut samples = Vec::new(); + let sample_count = be_u32(src)?; + let mut samples = TryVec::with_capacity(sample_count.to_usize())?; for _ in 0..sample_count { let first_chunk = be_u32(src)?; - let samples_per_chunk = be_u32_with_limit(src)?; + let samples_per_chunk = be_u32(src)?; let sample_description_index = be_u32(src)?; - vec_push( - &mut samples, - SampleToChunk { - first_chunk, - samples_per_chunk, - sample_description_index, - }, - )?; + samples.push(SampleToChunk { + first_chunk, + samples_per_chunk, + sample_description_index, + })?; } // Padding could be added in some contents. @@ -2227,23 +3796,28 @@ fn read_stsc(src: &mut BMFFBox) -> Result { Ok(SampleToChunkBox { samples }) } +/// Parse a Composition Time to Sample Box +/// See ISOBMFF (ISO 14496-12:2015) § 8.6.1.3 fn read_ctts(src: &mut BMFFBox) -> Result { let (version, _) = read_fullbox_extra(src)?; - let counts = u64::from(be_u32_with_limit(src)?); + let counts = be_u32(src)?; - if src.bytes_left() < counts.checked_mul(8).expect("counts -> bytes overflow") { + if counts + .checked_mul(8) + .map_or(true, |bytes| u64::from(bytes) > src.bytes_left()) + { return Err(Error::InvalidData("insufficient data in 'ctts' box")); } - let mut offsets = Vec::new(); + let mut offsets = TryVec::with_capacity(counts.to_usize())?; for _ in 0..counts { let (sample_count, time_offset) = match version { // According to spec, Version0 shoule be used when version == 0; // however, some buggy contents have negative value when version == 0. // So we always use Version1 here. 0..=1 => { - let count = be_u32_with_limit(src)?; + let count = be_u32(src)?; let offset = TimeOffsetVersion::Version1(be_i32(src)?); (count, offset) } @@ -2251,29 +3825,28 @@ fn read_ctts(src: &mut BMFFBox) -> Result { return Err(Error::InvalidData("unsupported version in 'ctts' box")); } }; - vec_push( - &mut offsets, - TimeOffset { - sample_count, - time_offset, - }, - )?; + offsets.push(TimeOffset { + sample_count, + time_offset, + })?; } - skip_box_remain(src)?; + check_parser_state!(src.content); Ok(CompositionOffsetBox { samples: offsets }) } /// Parse a stsz box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.7.3.2 fn read_stsz(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let sample_size = be_u32(src)?; - let sample_count = be_u32_with_limit(src)?; - let mut sample_sizes = Vec::new(); + let sample_count = be_u32(src)?; + let mut sample_sizes = TryVec::new(); if sample_size == 0 { + sample_sizes.reserve(sample_count.to_usize())?; for _ in 0..sample_count { - vec_push(&mut sample_sizes, be_u32(src)?)?; + sample_sizes.push(be_u32(src)?)?; } } @@ -2287,20 +3860,18 @@ fn read_stsz(src: &mut BMFFBox) -> Result { } /// Parse a stts box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.6.1.2 fn read_stts(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; - let sample_count = be_u32_with_limit(src)?; - let mut samples = Vec::new(); + let sample_count = be_u32(src)?; + let mut samples = TryVec::with_capacity(sample_count.to_usize())?; for _ in 0..sample_count { - let sample_count = be_u32_with_limit(src)?; + let sample_count = be_u32(src)?; let sample_delta = be_u32(src)?; - vec_push( - &mut samples, - Sample { - sample_count, - sample_delta, - }, - )?; + samples.push(Sample { + sample_count, + sample_delta, + })?; } // Padding could be added in some contents. @@ -2380,18 +3951,23 @@ fn read_vpcc(src: &mut BMFFBox) -> Result { }) } +/// See [AV1-ISOBMFF § 2.3.3](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax) fn read_av1c(src: &mut BMFFBox) -> Result { - let marker_byte = src.read_u8()?; + // We want to store the raw config as well as a structured (parsed) config, so create a copy of + // the raw config so we have it later, and then parse the structured data from that. + let raw_config = src.read_into_try_vec()?; + let mut raw_config_slice = raw_config.as_slice(); + let marker_byte = raw_config_slice.read_u8()?; if marker_byte & 0x80 != 0x80 { return Err(Error::Unsupported("missing av1C marker bit")); } if marker_byte & 0x7f != 0x01 { return Err(Error::Unsupported("missing av1C marker bit")); } - let profile_byte = src.read_u8()?; + let profile_byte = raw_config_slice.read_u8()?; let profile = (profile_byte & 0xe0) >> 5; let level = profile_byte & 0x1f; - let flags_byte = src.read_u8()?; + let flags_byte = raw_config_slice.read_u8()?; let tier = (flags_byte & 0x80) >> 7; let bit_depth = match flags_byte & 0x60 { 0x60 => 12, @@ -2402,7 +3978,7 @@ fn read_av1c(src: &mut BMFFBox) -> Result { let chroma_subsampling_x = (flags_byte & 0x08) >> 3; let chroma_subsampling_y = (flags_byte & 0x04) >> 2; let chroma_sample_position = flags_byte & 0x03; - let delay_byte = src.read_u8()?; + let delay_byte = raw_config_slice.read_u8()?; let initial_presentation_delay_present = (delay_byte & 0x10) == 0x10; let initial_presentation_delay_minus_one = if initial_presentation_delay_present { delay_byte & 0x0f @@ -2410,9 +3986,6 @@ fn read_av1c(src: &mut BMFFBox) -> Result { 0 }; - let config_obus_size = src.bytes_left(); - let config_obus = read_buf(src, config_obus_size)?; - Ok(AV1ConfigBox { profile, level, @@ -2424,7 +3997,7 @@ fn read_av1c(src: &mut BMFFBox) -> Result { chroma_sample_position, initial_presentation_delay_present, initial_presentation_delay_minus_one, - config_obus, + raw_config, }) } @@ -2441,6 +4014,7 @@ fn read_flac_metadata(src: &mut BMFFBox) -> Result Result<()> { // Tags for elementary stream description const ESDESCR_TAG: u8 = 0x03; @@ -2454,6 +4028,8 @@ fn find_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { let des = &mut Cursor::new(remains); let tag = des.read_u8()?; + // See MPEG-4 Systems (ISO 14496-1:2010) § 8.3.3 for interpreting size of expandable classes + let mut end: u32 = 0; // It's u8 without declaration type that is incorrect. // MSB of extend_or_len indicates more bytes, up to 4 bytes. for _ in 0..4 { @@ -2465,7 +4041,7 @@ fn find_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { } let extend_or_len = des.read_u8()?; end = (end << 7) + u32::from(extend_or_len & 0x7F); - if (extend_or_len & 0x80) == 0 { + if (extend_or_len & 0b1000_0000) == 0 { end += des.position() as u32; break; } @@ -2493,6 +4069,7 @@ fn find_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { } remains = &remains[end.to_usize()..remains.len()]; + debug!("remains.len(): {}", remains.len()); } Ok(()) @@ -2509,7 +4086,16 @@ fn get_audio_object_type(bit_reader: &mut BitReader) -> Result { Ok(audio_object_type) } +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.7 and probably 14496-3 somewhere? fn read_ds_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { + #[cfg(feature = "mp4v")] + // Check if we are in a Visual esda Box. + if esds.video_codec != CodecType::Unknown { + esds.decoder_specific_data.extend_from_slice(data)?; + return Ok(()); + } + + // We are in an Audio esda Box. let frequency_table = vec![ (0x0, 96000), (0x1, 88200), @@ -2656,8 +4242,12 @@ fn read_ds_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { esds.extended_audio_object_type = extended_audio_object_type; esds.audio_sample_rate = Some(sample_frequency_value); esds.audio_channel_count = Some(channel_counts); - assert!(esds.decoder_specific_data.is_empty()); - esds.decoder_specific_data.extend_from_slice(data); + if !esds.decoder_specific_data.is_empty() { + return Err(Error::InvalidData( + "There can be only one DecSpecificInfoTag descriptor", + )); + } + esds.decoder_specific_data.extend_from_slice(data)?; Ok(()) } @@ -2675,10 +4265,19 @@ fn read_surround_channel_count(bit_reader: &mut BitReader, channels: u8) -> Resu Ok(count) } +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.6 fn read_dc_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { let des = &mut Cursor::new(data); let object_profile = des.read_u8()?; + #[cfg(feature = "mp4v")] + { + esds.video_codec = match object_profile { + 0x20..=0x24 => CodecType::MP4V, + _ => CodecType::Unknown, + }; + } + // Skip uninteresting fields. skip(des, 12)?; @@ -2688,13 +4287,19 @@ fn read_dc_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { esds.audio_codec = match object_profile { 0x40 | 0x41 => CodecType::AAC, - 0x6B => CodecType::MP3, + 0x69 | 0x6B => CodecType::MP3, _ => CodecType::Unknown, }; + debug!( + "read_dc_descriptor: esds.audio_codec = {:?}", + esds.audio_codec + ); + Ok(()) } +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 fn read_es_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { let des = &mut Cursor::new(data); @@ -2722,17 +4327,11 @@ fn read_es_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { Ok(()) } +/// See MP4 (ISO 14496-14:2020) § 6.7.2 fn read_esds(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; - // Subtract 4 extra to offset the members of fullbox not accounted for in - // head.offset - let esds_size = src - .head - .size - .checked_sub(src.head.offset + 4) - .expect("offset invalid"); - let esds_array = read_buf(src, esds_size)?; + let esds_array = read_buf(src, src.bytes_left())?; let mut es_data = ES_Descriptor::default(); find_descriptor(&esds_array, &mut es_data)?; @@ -2743,6 +4342,7 @@ fn read_esds(src: &mut BMFFBox) -> Result { } /// Parse `FLACSpecificBox`. +/// See [Encapsulation of FLAC in ISO Base Media File Format](https://github.com/xiph/flac/blob/master/doc/isoflac.txt) § 3.3.2 fn read_dfla(src: &mut BMFFBox) -> Result { let (version, flags) = read_fullbox_extra(src)?; if version != 0 { @@ -2751,10 +4351,10 @@ fn read_dfla(src: &mut BMFFBox) -> Result { if flags != 0 { return Err(Error::InvalidData("no-zero dfLa (FLAC) flags")); } - let mut blocks = Vec::new(); + let mut blocks = TryVec::new(); while src.bytes_left() > 0 { let block = read_flac_metadata(src)?; - vec_push(&mut blocks, block)?; + blocks.push(block)?; } // The box must have at least one meta block, and the first block // must be the METADATA_BLOCK_STREAMINFO @@ -2882,20 +4482,52 @@ fn read_alac(src: &mut BMFFBox) -> Result { Ok(ALACSpecificBox { version, data }) } -/// Parse a hdlr box. -fn read_hdlr(src: &mut BMFFBox) -> Result { - let (_, _) = read_fullbox_extra(src)?; +/// Parse a Handler Reference Box.
+/// See ISOBMFF (ISO 14496-12:2020) § 8.4.3
+/// See [\[ISOBMFF\]: reserved (field = 0;) handling is ambiguous](https://github.com/MPEGGroup/FileFormat/issues/36) +fn read_hdlr(src: &mut BMFFBox, strictness: ParseStrictness) -> Result { + if read_fullbox_version_no_flags(src)? != 0 { + return Err(Error::Unsupported("hdlr version")); + } - // Skip uninteresting fields. - skip(src, 4)?; + let pre_defined = be_u32(src)?; + if pre_defined != 0 { + fail_if( + strictness == ParseStrictness::Strict, + "The HandlerBox 'pre_defined' field shall be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", + )?; + } let handler_type = FourCC::from(be_u32(src)?); - // Skip uninteresting fields. - skip(src, 12)?; + for _ in 1..=3 { + let reserved = be_u32(src)?; + if reserved != 0 { + fail_if( + strictness == ParseStrictness::Strict, + "The HandlerBox 'reserved' fields shall be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", + )?; + } + } - // Skip name. - skip_box_remain(src)?; + match std::str::from_utf8(src.read_into_try_vec()?.as_slice()) { + Ok(name) => { + if name.bytes().last() != Some(b'\0') { + fail_if( + strictness != ParseStrictness::Permissive, + "The HandlerBox 'name' field shall be null-terminated \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", + )? + } + } + Err(_) => fail_if( + strictness != ParseStrictness::Permissive, + "The HandlerBox 'name' field shall be valid utf8 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", + )?, + } Ok(HandlerBox { handler_type }) } @@ -2910,6 +4542,7 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result BoxType::VP9SampleEntry => CodecType::VP9, BoxType::AV1SampleEntry => CodecType::AV1, BoxType::ProtectedVisualSampleEntry => CodecType::EncryptedVideo, + BoxType::H263SampleEntry => CodecType::H263, _ => { debug!("Unsupported video codec, box {:?} found", name); CodecType::Unknown @@ -2932,7 +4565,7 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result // Skip clap/pasp/etc. for now. let mut codec_specific = None; - let mut protection_info = Vec::new(); + let mut protection_info = TryVec::new(); let mut iter = src.box_iter(); while let Some(mut b) = iter.next_box()? { match b.head.name { @@ -2954,6 +4587,20 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result // TODO(kinetik): Parse avcC box? For now we just stash the data. codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc)); } + BoxType::H263SpecificBox => { + if (name != BoxType::H263SampleEntry) || codec_specific.is_some() { + return Err(Error::InvalidData("malformed video sample entry")); + } + let h263_dec_spec_struc_size = b + .head + .size + .checked_sub(b.head.offset) + .expect("offset invalid"); + let h263_dec_spec_struc = read_buf(&mut b.content, h263_dec_spec_struc_size)?; + debug!("{:?} (h263DecSpecStruc)", h263_dec_spec_struc); + + codec_specific = Some(VideoCodecSpecific::H263Config(h263_dec_spec_struc)); + } BoxType::VPCodecConfigurationBox => { // vpcC if (name != BoxType::VP8SampleEntry @@ -2967,7 +4614,7 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc)); } BoxType::AV1CodecConfigurationBox => { - if name != BoxType::AV1SampleEntry { + if name != BoxType::AV1SampleEntry && name != BoxType::ProtectedVisualSampleEntry { return Err(Error::InvalidData("malformed video sample entry")); } let av1c = read_av1c(&mut b)?; @@ -2977,16 +4624,27 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result if name != BoxType::MP4VideoSampleEntry || codec_specific.is_some() { return Err(Error::InvalidData("malformed video sample entry")); } - let (_, _) = read_fullbox_extra(&mut b.content)?; - // Subtract 4 extra to offset the members of fullbox not - // accounted for in head.offset - let esds_size = b - .head - .size - .checked_sub(b.head.offset + 4) - .expect("offset invalid"); - let esds = read_buf(&mut b.content, esds_size)?; - codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds)); + #[cfg(not(feature = "mp4v"))] + { + let (_, _) = read_fullbox_extra(&mut b.content)?; + // Subtract 4 extra to offset the members of fullbox not + // accounted for in head.offset + let esds_size = b + .head + .size + .checked_sub(b.head.offset + 4) + .expect("offset invalid"); + let esds = read_buf(&mut b.content, esds_size)?; + codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds)); + } + #[cfg(feature = "mp4v")] + { + // Read ES_Descriptor inside an esds box. + // See ISOBMFF (ISO 14496-1:2010 §7.2.6.5) + let esds = read_esds(&mut b)?; + codec_specific = + Some(VideoCodecSpecific::ESDSConfig(esds.decoder_specific_data)); + } } BoxType::ProtectionSchemeInfoBox => { if name != BoxType::ProtectedVisualSampleEntry { @@ -2994,7 +4652,7 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result } let sinf = read_sinf(&mut b)?; debug!("{:?} (sinf)", sinf); - vec_push(&mut protection_info, sinf)?; + protection_info.push(sinf)?; } _ => { debug!("Unsupported video codec, box {:?} found", b.head.name); @@ -3031,10 +4689,11 @@ fn read_qt_wave_atom(src: &mut BMFFBox) -> Result { } } - codec_specific.ok_or_else(|| Error::InvalidData("malformed audio sample entry")) + codec_specific.ok_or(Error::InvalidData("malformed audio sample entry")) } /// Parse an audio description inside an stsd box. +/// See ISOBMFF (ISO 14496-12:2015) § 12.2.3 fn read_audio_sample_entry(src: &mut BMFFBox) -> Result { let name = src.get_header().name; @@ -3084,9 +4743,21 @@ fn read_audio_sample_entry(src: &mut BMFFBox) -> Result let (mut codec_type, mut codec_specific) = match name { BoxType::MP3AudioSampleEntry => (CodecType::MP3, Some(AudioCodecSpecific::MP3)), BoxType::LPCMAudioSampleEntry => (CodecType::LPCM, Some(AudioCodecSpecific::LPCM)), + // Some mp4 file with AMR doesn't have AMRSpecificBox "damr" in followed while loop, + // we use empty box by default. + #[cfg(feature = "3gpp")] + BoxType::AMRNBSampleEntry => ( + CodecType::AMRNB, + Some(AudioCodecSpecific::AMRSpecificBox(Default::default())), + ), + #[cfg(feature = "3gpp")] + BoxType::AMRWBSampleEntry => ( + CodecType::AMRWB, + Some(AudioCodecSpecific::AMRSpecificBox(Default::default())), + ), _ => (CodecType::Unknown, None), }; - let mut protection_info = Vec::new(); + let mut protection_info = TryVec::new(); let mut iter = src.box_iter(); while let Some(mut b) = iter.next_box()? { match b.head.name { @@ -3141,7 +4812,21 @@ fn read_audio_sample_entry(src: &mut BMFFBox) -> Result let sinf = read_sinf(&mut b)?; debug!("{:?} (sinf)", sinf); codec_type = CodecType::EncryptedAudio; - vec_push(&mut protection_info, sinf)?; + protection_info.push(sinf)?; + } + #[cfg(feature = "3gpp")] + BoxType::AMRSpecificBox => { + if codec_type != CodecType::AMRNB && codec_type != CodecType::AMRWB { + return Err(Error::InvalidData("malformed audio sample entry")); + } + let amr_dec_spec_struc_size = b + .head + .size + .checked_sub(b.head.offset) + .expect("offset invalid"); + let amr_dec_spec_struc = read_buf(&mut b.content, amr_dec_spec_struc_size)?; + debug!("{:?} (AMRDecSpecStruc)", amr_dec_spec_struc); + codec_specific = Some(AudioCodecSpecific::AMRSpecificBox(amr_dec_spec_struc)); } _ => { debug!("Unsupported audio codec, box {:?} found", b.head.name); @@ -3167,11 +4852,13 @@ fn read_audio_sample_entry(src: &mut BMFFBox) -> Result } /// Parse a stsd box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.5.2 +/// See MP4 (ISO 14496-14:2020) § 6.7.2 fn read_stsd(src: &mut BMFFBox, track: &mut Track) -> Result { let (_, _) = read_fullbox_extra(src)?; let description_count = be_u32(src)?; - let mut descriptions = Vec::new(); + let mut descriptions = TryVec::new(); { let mut iter = src.box_iter(); @@ -3194,7 +4881,7 @@ fn read_stsd(src: &mut BMFFBox, track: &mut Track) -> Result return Err(e), }; - vec_push(&mut descriptions, description)?; + descriptions.push(description)?; check_parser_state!(b.content); if descriptions.len() == description_count.to_usize() { break; @@ -3215,8 +4902,7 @@ fn read_sinf(src: &mut BMFFBox) -> Result { while let Some(mut b) = iter.next_box()? { match b.head.name { BoxType::OriginalFormatBox => { - let frma = read_frma(&mut b)?; - sinf.code_name = frma; + sinf.original_format = FourCC::from(be_u32(&mut b)?); } BoxType::SchemeTypeBox => { sinf.scheme_type = Some(read_schm(&mut b)?); @@ -3293,11 +4979,6 @@ fn read_tenc(src: &mut BMFFBox) -> Result { }) } -fn read_frma(src: &mut BMFFBox) -> Result { - let code_name = read_buf(src, 4)?; - String::from_utf8(code_name).map_err(From::from) -} - fn read_schm(src: &mut BMFFBox) -> Result { // Flags can be used to signal presence of URI in the box, but we don't // use the URI so don't bother storing the flags. @@ -3313,6 +4994,7 @@ fn read_schm(src: &mut BMFFBox) -> Result { } /// Parse a metadata box inside a moov, trak, or mdia box. +/// See ISOBMFF (ISO 14496-12:2015) § 8.10.1. fn read_udta(src: &mut BMFFBox) -> Result { let mut iter = src.box_iter(); let mut udta = UserdataBox { meta: None }; @@ -3330,7 +5012,8 @@ fn read_udta(src: &mut BMFFBox) -> Result { Ok(udta) } -/// Parse a metadata box inside a udta box +/// Parse the meta box +/// See ISOBMFF (ISO 14496-12:2015) § 8.111. fn read_meta(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let mut iter = src.box_iter(); @@ -3338,6 +5021,10 @@ fn read_meta(src: &mut BMFFBox) -> Result { while let Some(mut b) = iter.next_box()? { match b.head.name { BoxType::MetadataItemListEntry => read_ilst(&mut b, &mut meta)?, + #[cfg(feature = "meta-xml")] + BoxType::MetadataXMLBox => read_xml_(&mut b, &mut meta)?, + #[cfg(feature = "meta-xml")] + BoxType::MetadataBXMLBox => read_bxml(&mut b, &mut meta)?, _ => skip_box_content(&mut b)?, }; check_parser_state!(b.content); @@ -3345,6 +5032,28 @@ fn read_meta(src: &mut BMFFBox) -> Result { Ok(meta) } +/// Parse a XML box inside a meta box +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.2 +#[cfg(feature = "meta-xml")] +fn read_xml_(src: &mut BMFFBox, meta: &mut MetadataBox) -> Result<()> { + if read_fullbox_version_no_flags(src)? != 0 { + return Err(Error::Unsupported("unsupported XmlBox version")); + } + meta.xml = Some(XmlBox::StringXmlBox(src.read_into_try_vec()?)); + Ok(()) +} + +/// Parse a Binary XML box inside a meta box +/// See ISOBMFF (ISO 14496-12:2015) § 8.11.2 +#[cfg(feature = "meta-xml")] +fn read_bxml(src: &mut BMFFBox, meta: &mut MetadataBox) -> Result<()> { + if read_fullbox_version_no_flags(src)? != 0 { + return Err(Error::Unsupported("unsupported XmlBox version")); + } + meta.xml = Some(XmlBox::BinaryXmlBox(src.read_into_try_vec()?)); + Ok(()) +} + /// Parse a metadata box inside a udta box fn read_ilst(src: &mut BMFFBox, meta: &mut MetadataBox) -> Result<()> { let mut iter = src.box_iter(); @@ -3453,24 +5162,22 @@ fn read_ilst_bool_data(src: &mut BMFFBox) -> Result> { Ok(read_ilst_u8_data(src)?.and_then(|d| Some(d.get(0)? == &1))) } -fn read_ilst_string_data(src: &mut BMFFBox) -> Result> { - read_ilst_u8_data(src)?.map_or(Ok(None), |d| { - String::from_utf8(d).map_err(From::from).map(Some) - }) +fn read_ilst_string_data(src: &mut BMFFBox) -> Result> { + read_ilst_u8_data(src) } -fn read_ilst_u8_data(src: &mut BMFFBox) -> Result>> { +fn read_ilst_u8_data(src: &mut BMFFBox) -> Result>> { // For all non-covr atoms, there must only be one data atom. Ok(read_ilst_multiple_u8_data(src)?.pop()) } -fn read_ilst_multiple_u8_data(src: &mut BMFFBox) -> Result>> { +fn read_ilst_multiple_u8_data(src: &mut BMFFBox) -> Result>> { let mut iter = src.box_iter(); - let mut data = Vec::new(); + let mut data = TryVec::new(); while let Some(mut b) = iter.next_box()? { match b.head.name { BoxType::MetadataItemDataEntry => { - vec_push(&mut data, read_ilst_data(&mut b)?)?; + data.push(read_ilst_data(&mut b)?)?; } _ => skip_box_content(&mut b)?, }; @@ -3479,7 +5186,7 @@ fn read_ilst_multiple_u8_data(src: &mut BMFFBox) -> Result(src: &mut BMFFBox) -> Result> { +fn read_ilst_data(src: &mut BMFFBox) -> Result> { // Skip past the padding bytes skip(&mut src.content, src.head.offset)?; let size = src.content.limit(); @@ -3493,14 +5200,9 @@ fn skip(src: &mut T, bytes: u64) -> Result<()> { } /// Read size bytes into a Vector or return error. -fn read_buf(src: &mut T, size: u64) -> Result> { - if size > BUF_SIZE_LIMIT { - return Err(Error::InvalidData("read_buf size exceeds BUF_SIZE_LIMIT")); - } - - let mut buf = vec![]; - let r: u64 = read_to_end(&mut src.take(size), &mut buf)?.try_into()?; - if r != size { +fn read_buf(src: &mut T, size: u64) -> Result> { + let buf = src.take(size).read_into_try_vec()?; + if buf.len().to_u64() != size { return Err(Error::InvalidData("failed buffer read")); } @@ -3531,16 +5233,6 @@ fn be_u32(src: &mut T) -> Result { src.read_u32::().map_err(From::from) } -/// Using in reading table size and return error if it exceeds limitation. -fn be_u32_with_limit(src: &mut T) -> Result { - be_u32(src).and_then(|v| { - if v > TABLE_SIZE_LIMIT { - return Err(Error::OutOfMemory); - } - Ok(v) - }) -} - fn be_u64(src: &mut T) -> Result { src.read_u64::().map_err(From::from) } diff --git a/media/mp4parse-rust/mp4parse/src/tests.rs b/media/mp4parse-rust/mp4parse/src/tests.rs index a49a9c0e49..8f96ec5548 100644 --- a/media/mp4parse-rust/mp4parse/src/tests.rs +++ b/media/mp4parse-rust/mp4parse/src/tests.rs @@ -7,15 +7,16 @@ use super::read_mp4; use super::Error; -use super::MediaContext; -#[cfg(feature = "mp4parse_fallible")] +use super::ParseStrictness; +use fallible_collections::TryRead as _; + use std::convert::TryInto as _; use std::io::Cursor; use std::io::Read as _; extern crate test_assembler; use self::test_assembler::*; -use boxes::{BoxType, FourCC}; +use boxes::BoxType; enum BoxSize { Short(u32), @@ -179,11 +180,11 @@ fn read_ftyp() { assert_eq!(stream.head.name, BoxType::FileTypeBox); assert_eq!(stream.head.size, 24); let parsed = super::read_ftyp(&mut stream).unwrap(); - assert_eq!(parsed.major_brand, FourCC::from("mp42")); // mp42 + assert_eq!(parsed.major_brand, b"mp42"); // mp42 assert_eq!(parsed.minor_version, 0); assert_eq!(parsed.compatible_brands.len(), 2); - assert_eq!(parsed.compatible_brands[0], FourCC::from("isom")); // isom - assert_eq!(parsed.compatible_brands[1], FourCC::from("mp42")); // mp42 + assert_eq!(parsed.compatible_brands[0], b"isom"); // isom + assert_eq!(parsed.compatible_brands[1], b"mp42"); // mp42 } #[test] @@ -194,8 +195,7 @@ fn read_truncated_ftyp() { .B32(0) // minor version .append_bytes(b"isom") }); - let mut context = MediaContext::new(); - match read_mp4(&mut stream, &mut context) { + match read_mp4(&mut stream) { Err(Error::UnexpectedEOF) => (), Ok(_) => panic!("expected an error result"), _ => panic!("expected a different error result"), @@ -221,11 +221,11 @@ fn read_ftyp_case() { assert_eq!(stream.head.name, BoxType::FileTypeBox); assert_eq!(stream.head.size, 24); let parsed = super::read_ftyp(&mut stream).unwrap(); - assert_eq!(parsed.major_brand, FourCC::from("MP42")); + assert_eq!(parsed.major_brand, b"MP42"); assert_eq!(parsed.minor_version, 0); assert_eq!(parsed.compatible_brands.len(), 2); - assert_eq!(parsed.compatible_brands[0], FourCC::from("ISOM")); // ISOM - assert_eq!(parsed.compatible_brands[1], FourCC::from("MP42")); // MP42 + assert_eq!(parsed.compatible_brands[0], b"ISOM"); // ISOM + assert_eq!(parsed.compatible_brands[1], b"MP42"); // MP42 } #[test] @@ -345,7 +345,7 @@ fn read_mdhd_invalid_timescale() { assert_eq!(stream.head.name, BoxType::MediaHeaderBox); assert_eq!(stream.head.size, 44); let r = super::parse_mdhd(&mut stream, &mut super::Track::new(0)); - assert_eq!(r.is_err(), true); + assert!(r.is_err()); } #[test] @@ -386,7 +386,7 @@ fn read_mvhd_invalid_timescale() { assert_eq!(stream.head.name, BoxType::MovieHeaderBox); assert_eq!(stream.head.size, 120); let r = super::parse_mvhd(&mut stream); - assert_eq!(r.is_err(), true); + assert!(r.is_err()); } #[test] @@ -427,7 +427,7 @@ fn read_vpcc_version_0() { // TODO: it'd be better to find a real sample here. #[test] -#[allow(clippy::inconsistent_digit_grouping)] // Allow odd grouping for test readability. +#[allow(clippy::unusual_byte_groupings)] // Allow odd grouping for test readability. fn read_vpcc_version_1() { let data_length = 12u16; let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 1, |s| { @@ -449,7 +449,7 @@ fn read_vpcc_version_1() { Ok(vpcc) => { assert_eq!(vpcc.bit_depth, 8); assert_eq!(vpcc.chroma_subsampling, 3); - assert_eq!(vpcc.video_full_range_flag, false); + assert!(!vpcc.video_full_range_flag); assert_eq!(vpcc.matrix_coefficients.unwrap(), 1); } _ => panic!("vpcc parsing error"), @@ -471,8 +471,8 @@ fn read_hdlr() { let mut stream = iter.next_box().unwrap().unwrap(); assert_eq!(stream.head.name, BoxType::HandlerBox); assert_eq!(stream.head.size, 45); - let parsed = super::read_hdlr(&mut stream).unwrap(); - assert_eq!(parsed.handler_type, FourCC::from("vide")); + let parsed = super::read_hdlr(&mut stream, ParseStrictness::Normal).unwrap(); + assert_eq!(parsed.handler_type, b"vide"); } #[test] @@ -484,8 +484,70 @@ fn read_hdlr_short_name() { let mut stream = iter.next_box().unwrap().unwrap(); assert_eq!(stream.head.name, BoxType::HandlerBox); assert_eq!(stream.head.size, 33); - let parsed = super::read_hdlr(&mut stream).unwrap(); - assert_eq!(parsed.handler_type, FourCC::from("vide")); + let parsed = super::read_hdlr(&mut stream, ParseStrictness::Normal).unwrap(); + assert_eq!(parsed.handler_type, b"vide"); +} + +#[test] +fn read_hdlr_unsupported_version() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 1, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + match super::read_hdlr(&mut stream, ParseStrictness::Normal) { + Err(Error::Unsupported(msg)) => assert_eq!("hdlr version", msg), + result => { + eprintln!("{:?}", result); + panic!("expected Error::Unsupported") + } + } +} + +#[test] +fn read_hdlr_invalid_pre_defined_field() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { + s.B32(1).append_bytes(b"vide").B32(0).B32(0).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + match super::read_hdlr(&mut stream, ParseStrictness::Strict) { + Err(Error::InvalidData(msg)) => assert_eq!( + "The HandlerBox 'pre_defined' field shall be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", + msg + ), + result => { + eprintln!("{:?}", result); + panic!("expected Error::InvalidData") + } + } +} + +#[test] +fn read_hdlr_invalid_reserved_field() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(1).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + match super::read_hdlr(&mut stream, ParseStrictness::Strict) { + Err(Error::InvalidData(msg)) => assert_eq!( + "The HandlerBox 'reserved' fields shall be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", + msg + ), + result => { + eprintln!("{:?}", result); + panic!("expected Error::InvalidData") + } + } } #[test] @@ -497,8 +559,30 @@ fn read_hdlr_zero_length_name() { let mut stream = iter.next_box().unwrap().unwrap(); assert_eq!(stream.head.name, BoxType::HandlerBox); assert_eq!(stream.head.size, 32); - let parsed = super::read_hdlr(&mut stream).unwrap(); - assert_eq!(parsed.handler_type, FourCC::from("vide")); + match super::read_hdlr(&mut stream, ParseStrictness::Normal) { + Err(Error::InvalidData(msg)) => assert_eq!( + "The HandlerBox 'name' field shall be null-terminated \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", + msg + ), + result => { + eprintln!("{:?}", result); + panic!("expected Error::InvalidData") + } + } +} + +#[test] +fn read_hdlr_zero_length_name_permissive() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + let parsed = super::read_hdlr(&mut stream, ParseStrictness::Permissive).unwrap(); + assert_eq!(parsed.handler_type, b"vide"); } fn flac_streaminfo() -> Vec { @@ -680,7 +764,7 @@ fn serialize_opus_header() { channel_mapping_table: Some(super::ChannelMappingTable { stream_count: 4, coupled_count: 2, - channel_mapping: vec![0, 4, 1, 2, 3, 5], + channel_mapping: vec![0, 4, 1, 2, 3, 5].into(), }), }; let mut v = Vec::::new(); @@ -718,57 +802,8 @@ fn read_alac() { assert!(r.is_ok()); } -#[test] -fn avcc_limit() { - let mut stream = make_box(BoxSize::Auto, b"avc1", |s| { - s.append_repeated(0, 6) - .B16(1) - .append_repeated(0, 16) - .B16(320) - .B16(240) - .append_repeated(0, 14) - .append_repeated(0, 32) - .append_repeated(0, 4) - .B32(0xffff_ffff) - .append_bytes(b"avcC") - .append_repeated(0, 100) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - match super::read_video_sample_entry(&mut stream) { - Err(Error::InvalidData(s)) => assert_eq!(s, "read_buf size exceeds BUF_SIZE_LIMIT"), - Ok(_) => panic!("expected an error result"), - _ => panic!("expected a different error result"), - } -} - #[test] fn esds_limit() { - let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| { - s.append_repeated(0, 6) - .B16(1) - .B32(0) - .B32(0) - .B16(2) - .B16(16) - .B16(0) - .B16(0) - .B32(48000 << 16) - .B32(0xffff_ffff) - .append_bytes(b"esds") - .append_repeated(0, 100) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - match super::read_audio_sample_entry(&mut stream) { - Err(Error::InvalidData(s)) => assert_eq!(s, "read_buf size exceeds BUF_SIZE_LIMIT"), - Ok(_) => panic!("expected an error result"), - _ => panic!("expected a different error result"), - } -} - -#[test] -fn esds_limit_2() { let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| { s.append_repeated(0, 6) .B16(1) @@ -1040,8 +1075,10 @@ fn read_stsd_mp4v() { 0x2e, 0xa6, 0x60, 0x16, 0xf4, 0x01, 0xf4, 0x24, 0xc8, 0x01, 0xe5, 0x16, 0x84, 0x3c, 0x14, 0x63, 0x06, 0x01, 0x02, ]; - + #[cfg(not(feature = "mp4v"))] let esds_specific_data = &mp4v[90..]; + #[cfg(feature = "mp4v")] + let esds_specific_data = &mp4v[112..151]; println!("esds_specific_data {:?}", esds_specific_data); let mut stream = make_box(BoxSize::Auto, b"mp4v", |s| s.append_bytes(mp4v.as_slice())); @@ -1057,7 +1094,7 @@ fn read_stsd_mp4v() { assert_eq!(v.height, 480); match v.codec_specific { super::VideoCodecSpecific::ESDSConfig(esds_data) => { - assert_eq!(esds_data, esds_specific_data.to_vec()); + assert_eq!(esds_data.as_slice(), esds_specific_data); } _ => panic!("it should be ESDSConfig!"), } @@ -1132,23 +1169,6 @@ fn read_f4v_stsd() { } } -#[test] -fn max_table_limit() { - let elst = make_fullbox(BoxSize::Auto, b"elst", 1, |s| { - s.B32(super::TABLE_SIZE_LIMIT + 1) - }) - .into_inner(); - let mut stream = make_box(BoxSize::Auto, b"edts", |s| s.append_bytes(elst.as_slice())); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let mut track = super::Track::new(0); - match super::read_edts(&mut stream, &mut track) { - Err(Error::OutOfMemory) => (), - Ok(_) => panic!("expected an error result"), - _ => panic!("expected a different error result"), - } -} - #[test] fn unknown_video_sample_entry() { let unknown_codec = make_box(BoxSize::Auto, b"yyyy", |s| s.append_repeated(0, 16)).into_inner(); @@ -1236,26 +1256,6 @@ fn read_esds_redundant_descriptor() { } } -#[test] -fn read_invalid_pssh() { - // invalid pssh header length - let pssh = vec![ - 0x00, 0x00, 0x00, 0x01, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, - 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, - 0x00, 0x02, 0x7e, 0x57, 0x1d, 0x01, 0x7e, - ]; - - let mut stream = make_box(BoxSize::Auto, b"moov", |s| s.append_bytes(pssh.as_slice())); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let mut context = super::MediaContext::new(); - - match super::read_moov(&mut stream, &mut context) { - Err(Error::InvalidData(s)) => assert_eq!(s, "read_buf size exceeds BUF_SIZE_LIMIT"), - _ => panic!("unexpected result with invalid descriptor"), - } -} - #[test] fn read_stsd_lpcm() { // Extract from sample converted by ffmpeg. @@ -1293,16 +1293,13 @@ fn read_stsd_lpcm() { #[test] fn read_to_end_() { let mut src = b"1234567890".take(5); - let mut buf = vec![]; - let bytes_read = super::read_to_end(&mut src, &mut buf).unwrap(); - assert_eq!(bytes_read, 5); - assert_eq!(buf, b"12345"); + let buf = src.read_into_try_vec().unwrap(); + assert_eq!(buf.len(), 5); + assert_eq!(buf, b"12345".as_ref()); } #[test] -#[cfg(feature = "mp4parse_fallible")] fn read_to_end_oom() { let mut src = b"1234567890".take(std::usize::MAX.try_into().expect("usize < u64")); - let mut buf = vec![]; - assert!(super::read_to_end(&mut src, &mut buf).is_err()); + assert!(src.read_into_try_vec().is_err()); } diff --git a/media/mp4parse-rust/mp4parse/src/unstable.rs b/media/mp4parse-rust/mp4parse/src/unstable.rs new file mode 100644 index 0000000000..eeb16f8ed8 --- /dev/null +++ b/media/mp4parse-rust/mp4parse/src/unstable.rs @@ -0,0 +1,546 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +use num_traits::{CheckedAdd, CheckedSub, PrimInt, Zero}; +use std::ops::{Add, Neg, Sub}; + +use super::*; + +/// A zero-overhead wrapper around integer types for the sake of always +/// requiring checked arithmetic +#[repr(transparent)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CheckedInteger(pub T); + +impl From for CheckedInteger { + fn from(i: T) -> Self { + Self(i) + } +} + +// Orphan rules prevent a more general implementation, but this suffices +impl From> for i64 { + fn from(checked: CheckedInteger) -> i64 { + checked.0 + } +} + +impl> Add for CheckedInteger +where + T: CheckedAdd, +{ + type Output = Option; + + fn add(self, other: U) -> Self::Output { + self.0.checked_add(&other.into()).map(Into::into) + } +} + +impl> Sub for CheckedInteger +where + T: CheckedSub, +{ + type Output = Option; + + fn sub(self, other: U) -> Self::Output { + self.0.checked_sub(&other.into()).map(Into::into) + } +} + +/// Implement subtraction of checked `u64`s returning i64 +// This is necessary for handling Mp4parseTrackInfo::media_time gracefully +impl Sub for CheckedInteger { + type Output = Option>; + + fn sub(self, other: Self) -> Self::Output { + if self >= other { + self.0 + .checked_sub(other.0) + .and_then(|u| i64::try_from(u).ok()) + .map(CheckedInteger) + } else { + other + .0 + .checked_sub(self.0) + .and_then(|u| i64::try_from(u).ok()) + .map(i64::neg) + .map(CheckedInteger) + } + } +} + +#[test] +fn u64_subtraction_returning_i64() { + // self > other + assert_eq!( + CheckedInteger(2u64) - CheckedInteger(1u64), + Some(CheckedInteger(1i64)) + ); + + // self == other + assert_eq!( + CheckedInteger(1u64) - CheckedInteger(1u64), + Some(CheckedInteger(0i64)) + ); + + // difference too large to store in i64 + assert_eq!(CheckedInteger(u64::MAX) - CheckedInteger(1u64), None); + + // self < other + assert_eq!( + CheckedInteger(1u64) - CheckedInteger(2u64), + Some(CheckedInteger(-1i64)) + ); + + // difference not representable due to overflow + assert_eq!(CheckedInteger(1u64) - CheckedInteger(u64::MAX), None); +} + +impl PartialEq for CheckedInteger { + fn eq(&self, other: &T) -> bool { + self.0 == *other + } +} + +/// Provides the following information about a sample in the source file: +/// sample data offset (start and end), composition time in microseconds +/// (start and end) and whether it is a sync sample +#[repr(C)] +#[derive(Default, Debug, PartialEq)] +pub struct Indice { + /// The byte offset in the file where the indexed sample begins. + pub start_offset: CheckedInteger, + /// The byte offset in the file where the indexed sample ends. This is + /// equivalent to `start_offset` + the length in bytes of the indexed + /// sample. Typically this will be the `start_offset` of the next sample + /// in the file. + pub end_offset: CheckedInteger, + /// The time in microseconds when the indexed sample should be displayed. + /// Analogous to the concept of presentation time stamp (pts). + pub start_composition: CheckedInteger, + /// The time in microseconds when the indexed sample should stop being + /// displayed. Typically this would be the `start_composition` time of the + /// next sample if samples were ordered by composition time. + pub end_composition: CheckedInteger, + /// The time in microseconds that the indexed sample should be decoded at. + /// Analogous to the concept of decode time stamp (dts). + pub start_decode: CheckedInteger, + /// Set if the indexed sample is a sync sample. The meaning of sync is + /// somewhat codec specific, but essentially amounts to if the sample is a + /// key frame. + pub sync: bool, +} + +/// Create a vector of `Indice`s with the information about track samples. +/// It uses `stsc`, `stco`, `stsz` and `stts` boxes to construct a list of +/// every sample in the file and provides offsets which can be used to read +/// raw sample data from the file. +#[allow(clippy::reversed_empty_ranges)] +pub fn create_sample_table( + track: &Track, + track_offset_time: CheckedInteger, +) -> Option> { + let timescale = match track.timescale { + Some(ref t) => TrackTimeScale::(t.0 as i64, t.1), + _ => return None, + }; + + let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) { + (&Some(ref a), &Some(ref b), &Some(ref c), &Some(ref d)) => (a, b, c, d), + _ => return None, + }; + + // According to spec, no sync table means every sample is sync sample. + let has_sync_table = matches!(track.stss, Some(_)); + + let mut sample_size_iter = stsz.sample_sizes.iter(); + + // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample + // offset address. + + // With large numbers of samples, the cost of many allocations dominates, + // so it's worth iterating twice to allocate sample_table just once. + let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets) + .map(|(_, sample_counts)| sample_counts.to_usize()) + .try_fold(0usize, usize::checked_add)?; + let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?; + + for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) { + let chunk_id = i.0 as usize; + let sample_counts = i.1; + let mut cur_position = match stco.offsets.get(chunk_id) { + Some(&i) => i.into(), + _ => return None, + }; + for _ in 0..sample_counts { + let start_offset = cur_position; + let end_offset = match (stsz.sample_size, sample_size_iter.next()) { + (_, Some(t)) => (start_offset + *t)?, + (t, _) if t > 0 => (start_offset + t)?, + _ => 0.into(), + }; + if end_offset == 0 { + return None; + } + cur_position = end_offset; + + sample_table + .push(Indice { + start_offset, + end_offset, + sync: !has_sync_table, + ..Default::default() + }) + .ok()?; + } + } + + // Mark the sync sample in sample_table according to 'stss'. + if let Some(ref v) = track.stss { + for iter in &v.samples { + match iter + .checked_sub(&1) + .and_then(|idx| sample_table.get_mut(idx as usize)) + { + Some(elem) => elem.sync = true, + _ => return None, + } + } + } + + let ctts_iter = track.ctts.as_ref().map(|v| v.samples.as_slice().iter()); + + let mut ctts_offset_iter = TimeOffsetIterator { + cur_sample_range: (0..0), + cur_offset: 0, + ctts_iter, + track_id: track.id, + }; + + let mut stts_iter = TimeToSampleIterator { + cur_sample_count: (0..0), + cur_sample_delta: 0, + stts_iter: stts.samples.as_slice().iter(), + track_id: track.id, + }; + + // sum_delta is the sum of stts_iter delta. + // According to spec: + // decode time => DT(n) = DT(n-1) + STTS(n) + // composition time => CT(n) = DT(n) + CTTS(n) + // Note: + // composition time needs to add the track offset time from 'elst' table. + let mut sum_delta = TrackScaledTime::(0, track.id); + for sample in sample_table.as_mut_slice() { + let decode_time = sum_delta; + sum_delta = (sum_delta + stts_iter.next_delta())?; + + // ctts_offset is the current sample offset time. + let ctts_offset = ctts_offset_iter.next_offset_time(); + + let start_composition = track_time_to_us((decode_time + ctts_offset)?, timescale)?.0; + + let end_composition = track_time_to_us((sum_delta + ctts_offset)?, timescale)?.0; + + let start_decode = track_time_to_us(decode_time, timescale)?.0; + + sample.start_composition = (track_offset_time + start_composition)?; + sample.end_composition = (track_offset_time + end_composition)?; + sample.start_decode = start_decode.into(); + } + + // Correct composition end time due to 'ctts' causes composition time re-ordering. + // + // Composition end time is not in specification. However, gecko needs it, so we need to + // calculate to correct the composition end time. + if !sample_table.is_empty() { + // Create an index table refers to sample_table and sorted by start_composisiton time. + let mut sort_table = TryVec::with_capacity(sample_table.len()).ok()?; + + for i in 0..sample_table.len() { + sort_table.push(i).ok()?; + } + + sort_table.sort_by_key(|i| match sample_table.get(*i) { + Some(v) => v.start_composition, + _ => 0.into(), + }); + + for indices in sort_table.windows(2) { + if let [current_index, peek_index] = *indices { + let next_start_composition_time = sample_table[peek_index].start_composition; + let sample = &mut sample_table[current_index]; + sample.end_composition = next_start_composition_time; + } + } + } + + Some(sample_table) +} + +// Convert a 'ctts' compact table to full table by iterator, +// (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ... +// +// For example: +// (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time(). +struct TimeOffsetIterator<'a> { + cur_sample_range: std::ops::Range, + cur_offset: i64, + ctts_iter: Option>, + track_id: usize, +} + +impl<'a> Iterator for TimeOffsetIterator<'a> { + type Item = i64; + + #[allow(clippy::reversed_empty_ranges)] + fn next(&mut self) -> Option { + let has_sample = self.cur_sample_range.next().or_else(|| { + // At end of current TimeOffset, find the next TimeOffset. + let iter = match self.ctts_iter { + Some(ref mut v) => v, + _ => return None, + }; + let offset_version; + self.cur_sample_range = match iter.next() { + Some(v) => { + offset_version = v.time_offset; + 0..v.sample_count + } + _ => { + offset_version = TimeOffsetVersion::Version0(0); + 0..0 + } + }; + + self.cur_offset = match offset_version { + TimeOffsetVersion::Version0(i) => i64::from(i), + TimeOffsetVersion::Version1(i) => i64::from(i), + }; + + self.cur_sample_range.next() + }); + + has_sample.and(Some(self.cur_offset)) + } +} + +impl<'a> TimeOffsetIterator<'a> { + fn next_offset_time(&mut self) -> TrackScaledTime { + match self.next() { + Some(v) => TrackScaledTime::(v as i64, self.track_id), + _ => TrackScaledTime::(0, self.track_id), + } + } +} + +// Convert 'stts' compact table to full table by iterator, +// (sample_count_with_the_same_time, time) => (time, time, time) ... repeats +// sample_count_with_the_same_time. +// +// For example: +// (2, 3000), (1, 2999) to (3000, 3000, 2999). +struct TimeToSampleIterator<'a> { + cur_sample_count: std::ops::Range, + cur_sample_delta: u32, + stts_iter: std::slice::Iter<'a, Sample>, + track_id: usize, +} + +impl<'a> Iterator for TimeToSampleIterator<'a> { + type Item = u32; + + #[allow(clippy::reversed_empty_ranges)] + fn next(&mut self) -> Option { + let has_sample = self.cur_sample_count.next().or_else(|| { + self.cur_sample_count = match self.stts_iter.next() { + Some(v) => { + self.cur_sample_delta = v.sample_delta; + 0..v.sample_count + } + _ => 0..0, + }; + + self.cur_sample_count.next() + }); + + has_sample.and(Some(self.cur_sample_delta)) + } +} + +impl<'a> TimeToSampleIterator<'a> { + fn next_delta(&mut self) -> TrackScaledTime { + match self.next() { + Some(v) => TrackScaledTime::(i64::from(v), self.track_id), + _ => TrackScaledTime::(0, self.track_id), + } + } +} + +// Convert 'stco' compact table to full table by iterator. +// (start_chunk_num, sample_number) => (start_chunk_num, sample_number), +// (start_chunk_num + 1, sample_number), +// (start_chunk_num + 2, sample_number), +// ... +// (next start_chunk_num, next sample_number), +// ... +// +// For example: +// (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10), +// (7, 10), (8, 10), (9, 2) +fn sample_to_chunk_iter<'a>( + stsc_samples: &'a TryVec, + stco_offsets: &'a TryVec, +) -> SampleToChunkIterator<'a> { + SampleToChunkIterator { + chunks: (0..0), + sample_count: 0, + stsc_peek_iter: stsc_samples.as_slice().iter().peekable(), + remain_chunk_count: stco_offsets + .len() + .try_into() + .expect("stco.entry_count is u32"), + } +} + +struct SampleToChunkIterator<'a> { + chunks: std::ops::Range, + sample_count: u32, + stsc_peek_iter: std::iter::Peekable>, + remain_chunk_count: u32, // total chunk number from 'stco'. +} + +impl<'a> Iterator for SampleToChunkIterator<'a> { + type Item = (u32, u32); + + fn next(&mut self) -> Option<(u32, u32)> { + let has_chunk = self.chunks.next().or_else(|| { + self.chunks = self.locate(); + self.remain_chunk_count + .checked_sub( + self.chunks + .len() + .try_into() + .expect("len() of a Range must fit in u32"), + ) + .and_then(|res| { + self.remain_chunk_count = res; + self.chunks.next() + }) + }); + + has_chunk.map(|id| (id, self.sample_count)) + } +} + +impl<'a> SampleToChunkIterator<'a> { + #[allow(clippy::reversed_empty_ranges)] + fn locate(&mut self) -> std::ops::Range { + loop { + return match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) { + (Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => { + // Invalid entry, skip it and will continue searching at + // next loop iteration. + continue; + } + (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => { + self.sample_count = next.samples_per_chunk; + (next.first_chunk - 1)..(peek.first_chunk - 1) + } + (Some(next), None) if next.first_chunk > 0 => { + self.sample_count = next.samples_per_chunk; + // Total chunk number in 'stsc' could be different to 'stco', + // there could be more chunks at the last 'stsc' record. + match next.first_chunk.checked_add(self.remain_chunk_count) { + Some(r) => (next.first_chunk - 1)..r - 1, + _ => 0..0, + } + } + _ => 0..0, + }; + } + } +} + +/// Calculate numerator * scale / denominator, if possible. +/// +/// Applying the associativity of integer arithmetic, we divide first +/// and add the remainder after multiplying each term separately +/// to preserve precision while leaving more headroom. That is, +/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. +/// +/// Return None on overflow or if the denominator is zero. +fn rational_scale(numerator: T, denominator: T, scale2: S) -> Option +where + T: PrimInt + Zero, + S: PrimInt, +{ + if denominator.is_zero() { + return None; + } + + let integer = numerator / denominator; + let remainder = numerator % denominator; + num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { + Some(integer) => remainder + .checked_mul(&s) + .and_then(|remainder| (remainder / denominator).checked_add(&integer)), + None => None, + }) +} + +#[derive(Debug, PartialEq)] +pub struct Microseconds(pub T); + +/// Convert `time` in media's global (mvhd) timescale to microseconds, +/// using provided `MediaTimeScale` +pub fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option> { + let microseconds_per_second = 1_000_000; + rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) +} + +/// Convert `time` in track's local (mdhd) timescale to microseconds, +/// using provided `TrackTimeScale` +pub fn track_time_to_us( + time: TrackScaledTime, + scale: TrackTimeScale, +) -> Option> +where + T: PrimInt + Zero, +{ + assert_eq!(time.1, scale.1); + let microseconds_per_second = 1_000_000; + rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) +} + +#[test] +fn rational_scale_overflow() { + assert_eq!(rational_scale::(17, 3, 1000), Some(5666)); + let large = 0x4000_0000_0000_0000; + assert_eq!(rational_scale::(large, 2, 2), Some(large)); + assert_eq!(rational_scale::(large, 4, 4), Some(large)); + assert_eq!(rational_scale::(large, 2, 8), None); + assert_eq!(rational_scale::(large, 8, 4), Some(large / 2)); + assert_eq!(rational_scale::(large + 1, 4, 4), Some(large + 1)); + assert_eq!(rational_scale::(large, 40, 1000), None); +} + +#[test] +fn media_time_overflow() { + let scale = MediaTimeScale(90000); + let duration = MediaScaledTime(9_007_199_254_710_000); + assert_eq!( + media_time_to_us(duration, scale), + Some(Microseconds(100_079_991_719_000_000u64)) + ); +} + +#[test] +fn track_time_overflow() { + let scale = TrackTimeScale(44100u64, 0); + let duration = TrackScaledTime(4_413_527_634_807_900u64, 0); + assert_eq!( + track_time_to_us(duration, scale), + Some(Microseconds(100_079_991_719_000_000u64)) + ); +} diff --git a/media/mp4parse-rust/mp4parse_capi/.cargo-checksum.json b/media/mp4parse-rust/mp4parse_capi/.cargo-checksum.json new file mode 100644 index 0000000000..0358591794 --- /dev/null +++ b/media/mp4parse-rust/mp4parse_capi/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"fa0e2b46e9547dba9134da4001b2cd803fac58f9252394aa62653fe99bd220fa","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"f776ed4bbb7b58a5684402a9c5c28dfe1fa02b6b184139b2c2c49384cc1e3723","cbindgen.toml":"f82065edc5be3d2e43db65ac67490194701b3800ce3d42976fc7ad351928a296","examples/dump.rs":"268093925d28d1636e106d93a2f2917fb1d7ddaf04ecd70880e1551fb578de1a","src/lib.rs":"9bec128eb9ac71a79e20a5e2c7125579baab921d285fb897ed04525df4ab9827","tests/test_chunk_out_of_range.rs":"b5da583218d98027ed973a29c67434a91a1306f2d2fb39ec4d640d4824c308ce","tests/test_encryption.rs":"b918f0f10e7708bff5fae4becf1d09a188db25d874d0919d509b90266504eed7","tests/test_fragment.rs":"e90eb5a4418d30002655466c0c4b3125c7fd70a74b6871471eaa172f1def9db8","tests/test_rotation.rs":"fb43c2f2dfa496d151c33bdd46c0fd3252387c23cc71e2cac9ed0234de715a81","tests/test_sample_table.rs":"19b8d0b0f7ed79a857329321b49f5a7f687901cadd4cd22bc6728febd919d3ce","tests/test_workaround_stsc.rs":"7dd419f3d55b9a3a039cac57e58a9240a9c8166bcd4356c24f69f731c3ced83b"},"package":null} \ No newline at end of file diff --git a/media/mp4parse-rust/mp4parse_capi/Cargo.toml b/media/mp4parse-rust/mp4parse_capi/Cargo.toml index 708a16360c..9193d22e00 100644 --- a/media/mp4parse-rust/mp4parse_capi/Cargo.toml +++ b/media/mp4parse-rust/mp4parse_capi/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "mp4parse_capi" -version = "0.11.2" +version = "0.11.5" authors = [ "Ralph Giles ", "Matthew Gregan ", "Alfredo Yang ", + "Jon Bauman ", + "Bryce Seager van Dyk ", ] description = "Parser for ISO base media file format (mp4)" @@ -18,22 +20,21 @@ exclude = [ "*.mp4", ] -build = false # See bug 1611431 - Generate mp4parse-rust bindings as part of mach build - [badges] travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } [dependencies] byteorder = "1.2.1" +fallible_collections = { version = "0.4", features = ["std_io"] } log = "0.4" -mp4parse = {version = "0.11.2", path = "../mp4parse"} -num-traits = "0.2.0" +mp4parse = { version = "0.11.5", path = "../mp4parse", features = ["unstable-api"] } +num-traits = "0.2.10" [dev-dependencies] -env_logger = "0.7.1" +env_logger = "0.6.2" [features] -# Enable mp4parse_fallible to use fallible memory allocation rather than -# panicking on OOM. Note that this is only safe within Gecko where the system -# allocator has been globally overridden (see BMO 1457359). -mp4parse_fallible = ["mp4parse/mp4parse_fallible"] +missing-pixi-permitted = ["mp4parse/missing-pixi-permitted"] +3gpp = ["mp4parse/3gpp"] +meta-xml = ["mp4parse/meta-xml"] +mp4v = ["mp4parse/mp4v"] diff --git a/media/mp4parse-rust/mp4parse_capi/LICENSE b/media/mp4parse-rust/mp4parse_capi/LICENSE new file mode 100644 index 0000000000..14e2f777f6 --- /dev/null +++ b/media/mp4parse-rust/mp4parse_capi/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/media/mp4parse-rust/mp4parse_capi/README.md b/media/mp4parse-rust/mp4parse_capi/README.md new file mode 100644 index 0000000000..de01503734 --- /dev/null +++ b/media/mp4parse-rust/mp4parse_capi/README.md @@ -0,0 +1,2 @@ +`mp4parse-capi` is a C API that exposes the functionality of `mp4parse`. +See [the README in the mp4parse-rust repo](https://github.com/mozilla/mp4parse-rust/blob/master/README.md) for more details. \ No newline at end of file diff --git a/media/mp4parse-rust/mp4parse_capi/cbindgen.toml b/media/mp4parse-rust/mp4parse_capi/cbindgen.toml index f23797a754..73e8ce459a 100644 --- a/media/mp4parse-rust/mp4parse_capi/cbindgen.toml +++ b/media/mp4parse-rust/mp4parse_capi/cbindgen.toml @@ -1,8 +1,8 @@ header = """/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" -autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ -#ifndef mozilla_media_mp4parse_rust_mp4parse_h +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */ +#ifndef mp4parse_rust_mp4parse_h #error "Don't include this file directly, instead include mp4parse.h" #endif """ @@ -11,9 +11,32 @@ braces = "SameLine" line_length = 100 tab_width = 2 language = "C" - -# If we change the language to C++, it would be nice to namespace it -# namespaces = ["mozilla", "media", "ffi"] +cpp_compat = true [enum] rename_variants = "QualifiedScreamingSnakeCase" + +[defines] +"feature = 3gpp" = "MP4PARSE_FEATURE_3GPP" +"feature = meta-xml" = "MP4PARSE_FEATURE_META_XML" +"feature = unstable-api" = "MP4PARSE_UNSTABLE_API" + +[parse] +parse_deps = true +include = ["mp4parse"] + +[export] +# `mp4parse::Status` was previously defined as `mp4parse_capi::Mp4parseStatus`, +# but now is referenced from `mp4parse_capi` via `pub use mp4parse::Status as Mp4parseStatus`, +# the name `Status` does not appear in the public C API, so we must force its inclusion +include = ["Status"] + +[export.rename] +# We need to declare these types in mp4parse, but we rename them in the generated +# header to match mp4parse_capi naming conventions +"Status" = "Mp4parseStatus" +"ParseStrictness" = "Mp4parseStrictness" +"ImageSpatialExtentsProperty" = "Mp4parseImageSpatialExtents" +"ImageRotation" = "Mp4parseIrot" +"ImageMirror" = "Mp4parseImir" +"Indice" = "Mp4parseIndice" diff --git a/media/mp4parse-rust/mp4parse_capi/src/lib.rs b/media/mp4parse-rust/mp4parse_capi/src/lib.rs index 7321c03aa8..2fe5de56af 100644 --- a/media/mp4parse-rust/mp4parse_capi/src/lib.rs +++ b/media/mp4parse-rust/mp4parse_capi/src/lib.rs @@ -16,8 +16,8 @@ //! Err(_) => -1, //! } //! } -//! -//! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap(); +//! let capi_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); +//! let mut file = std::fs::File::open(capi_dir + "/../mp4parse/tests/minimal.mp4").unwrap(); //! let io = mp4parse_capi::Mp4parseIo { //! read: Some(buf_read), //! userdata: &mut file as *mut _ as *mut std::os::raw::c_void @@ -36,45 +36,44 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. extern crate byteorder; +extern crate log; extern crate mp4parse; extern crate num_traits; use byteorder::WriteBytesExt; -use num_traits::{PrimInt, Zero}; -use std::collections::HashMap; +use std::convert::TryFrom; +use std::convert::TryInto; + use std::io::Read; // Symbols we need from our rust api. -use mp4parse::extend_from_slice; -use mp4parse::read_avif; -use mp4parse::read_mp4; use mp4parse::serialize_opus_header; -use mp4parse::vec_push; +use mp4parse::unstable::{ + create_sample_table, media_time_to_us, track_time_to_us, CheckedInteger, Indice, Microseconds, +}; use mp4parse::AudioCodecSpecific; use mp4parse::AvifContext; use mp4parse::CodecType; -use mp4parse::Error; use mp4parse::MediaContext; -use mp4parse::MediaScaledTime; -use mp4parse::MediaTimeScale; +// Re-exported so consumers don't have to depend on mp4parse as well +pub use mp4parse::ParseStrictness; use mp4parse::SampleEntry; -use mp4parse::Track; -use mp4parse::TrackScaledTime; -use mp4parse::TrackTimeScale; +pub use mp4parse::Status as Mp4parseStatus; use mp4parse::TrackType; +use mp4parse::TryBox; +use mp4parse::TryHashMap; +use mp4parse::TryVec; use mp4parse::VideoCodecSpecific; -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum Mp4parseStatus { - Ok = 0, - BadArg = 1, - Invalid = 2, - Unsupported = 3, - Eof = 4, - Io = 5, - Oom = 6, -} +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; #[repr(C)] #[derive(PartialEq, Debug)] @@ -90,7 +89,7 @@ impl Default for Mp4parseTrackType { } } -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[repr(C)] #[derive(PartialEq, Debug)] pub enum Mp4parseCodec { @@ -107,6 +106,11 @@ pub enum Mp4parseCodec { Ac3, Ec3, Alac, + H263, + #[cfg(feature = "3gpp")] + AMRNB, + #[cfg(feature = "3gpp")] + AMRWB, } impl Default for Mp4parseCodec { @@ -140,43 +144,33 @@ pub struct Mp4parseTrackInfo { pub track_type: Mp4parseTrackType, pub track_id: u32, pub duration: u64, - pub media_time: i64, // wants to be u64? understand how elst adjustment works - // TODO(kinetik): include crypto guff -} - -#[repr(C)] -#[derive(Default, Debug, PartialEq)] -pub struct Mp4parseIndice { - /// The byte offset in the file where the indexed sample begins. - pub start_offset: u64, - /// The byte offset in the file where the indexed sample ends. This is - /// equivalent to `start_offset` + the length in bytes of the indexed - /// sample. Typically this will be the `start_offset` of the next sample - /// in the file. - pub end_offset: u64, - /// The time in microseconds when the indexed sample should be displayed. - /// Analogous to the concept of presentation time stamp (pts). - pub start_composition: i64, - /// The time in microseconds when the indexed sample should stop being - /// displayed. Typically this would be the `start_composition` time of the - /// next sample if samples were ordered by composition time. - pub end_composition: i64, - /// The time in microseconds that the indexed sample should be decoded at. - /// Analogous to the concept of decode time stamp (dts). - pub start_decode: i64, - /// Set if the indexed sample is a sync sample. The meaning of sync is - /// somewhat codec specific, but essentially amounts to if the sample is a - /// key frame. - pub sync: bool, + pub media_time: CheckedInteger, // wants to be u64? understand how elst adjustment works + // TODO(kinetik): include crypto guff + // If this changes to u64, we can get rid of the strange + // impl Sub for CheckedInteger } #[repr(C)] #[derive(Debug)] pub struct Mp4parseByteData { - pub length: u32, + pub length: usize, // cheddar can't handle generic type, so it needs to be multiple data types here. pub data: *const u8, - pub indices: *const Mp4parseIndice, + pub indices: *const Indice, +} + +impl Mp4parseByteData { + fn with_data(slice: &[u8]) -> Self { + Self { + length: slice.len(), + data: if !slice.is_empty() { + slice.as_ptr() + } else { + std::ptr::null() + }, + indices: std::ptr::null(), + } + } } impl Default for Mp4parseByteData { @@ -191,12 +185,12 @@ impl Default for Mp4parseByteData { impl Mp4parseByteData { fn set_data(&mut self, data: &[u8]) { - self.length = data.len() as u32; + self.length = data.len(); self.data = data.as_ptr(); } - fn set_indices(&mut self, data: &[Mp4parseIndice]) { - self.length = data.len() as u32; + fn set_indices(&mut self, data: &[Indice]) { + self.length = data.len(); self.indices = data.as_ptr(); } } @@ -207,9 +201,23 @@ pub struct Mp4parsePsshInfo { pub data: Mp4parseByteData, } +#[repr(u8)] +#[derive(Debug, PartialEq)] +pub enum OptionalFourCc { + None, + Some([u8; 4]), +} + +impl Default for OptionalFourCc { + fn default() -> Self { + Self::None + } +} + #[repr(C)] #[derive(Default, Debug)] pub struct Mp4parseSinfInfo { + pub original_format: OptionalFourCc, pub scheme_type: Mp4ParseEncryptionSchemeType, pub is_encrypted: u8, pub iv_size: u8, @@ -295,15 +303,37 @@ pub struct Mp4parseFragmentInfo { #[derive(Default)] pub struct Mp4parseParser { context: MediaContext, - opus_header: HashMap>, - pssh_data: Vec, - sample_table: HashMap>, + opus_header: TryHashMap>, + pssh_data: TryVec, + sample_table: TryHashMap>, // Store a mapping from track index (not id) to associated sample // descriptions. Because each track has a variable number of sample // descriptions, and because we need the data to live long enough to be // copied out by callers, we store these on the parser struct. - audio_track_sample_descriptions: HashMap>, - video_track_sample_descriptions: HashMap>, + audio_track_sample_descriptions: TryHashMap>, + video_track_sample_descriptions: TryHashMap>, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseAvifImageItem { + pub coded_data: Mp4parseByteData, + pub bits_per_channel: Mp4parseByteData, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseAvifImage { + pub primary_image: Mp4parseAvifImageItem, + /// The size of the image; should never be null unless using permissive parsing + pub spatial_extents: *const mp4parse::ImageSpatialExtentsProperty, + pub nclx_colour_information: *const mp4parse::NclxColourInformation, + pub icc_colour_information: Mp4parseByteData, + pub image_rotation: mp4parse::ImageRotation, + pub image_mirror: *const mp4parse::ImageMirror, + /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null + pub alpha_image: Mp4parseAvifImageItem, + pub premultiplied_alpha: bool, } /// A unified interface for the parsers which have different contexts, but @@ -313,11 +343,11 @@ trait ContextParser where Self: Sized, { - type Context: Default; + type Context; fn with_context(context: Self::Context) -> Self; - fn read(io: &mut T, context: &mut Self::Context) -> mp4parse::Result<()>; + fn read(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result; } impl Mp4parseParser { @@ -328,18 +358,6 @@ impl Mp4parseParser { fn context_mut(&mut self) -> &mut MediaContext { &mut self.context } - - fn opus_header_mut(&mut self) -> &mut HashMap> { - &mut self.opus_header - } - - fn pssh_data_mut(&mut self) -> &mut Vec { - &mut self.pssh_data - } - - fn sample_table_mut(&mut self) -> &mut HashMap> { - &mut self.sample_table - } } impl ContextParser for Mp4parseParser { @@ -352,12 +370,13 @@ impl ContextParser for Mp4parseParser { } } - fn read(io: &mut T, context: &mut Self::Context) -> mp4parse::Result<()> { - read_mp4(io, context) + fn read(io: &mut T, _strictness: ParseStrictness) -> mp4parse::Result { + let r = mp4parse::read_mp4(io); + log::debug!("mp4parse::read_mp4 -> {:?}", r); + r } } -#[derive(Default)] pub struct Mp4parseAvifParser { context: AvifContext, } @@ -375,8 +394,13 @@ impl ContextParser for Mp4parseAvifParser { Self { context } } - fn read(io: &mut T, context: &mut Self::Context) -> mp4parse::Result<()> { - read_avif(io, context) + fn read(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result { + let r = mp4parse::read_avif(io, strictness); + if r.is_err() { + log::debug!("{:?}", r); + } + log::trace!("mp4parse::read_avif -> {:?}", r); + r } } @@ -433,7 +457,7 @@ pub unsafe extern "C" fn mp4parse_new( io: *const Mp4parseIo, parser_out: *mut *mut Mp4parseParser, ) -> Mp4parseStatus { - mp4parse_new_common(io, parser_out) + mp4parse_new_common(io, ParseStrictness::Normal, parser_out) } /// Allocate an `Mp4parseAvifParser*` to read from the supplied `Mp4parseIo`. @@ -448,13 +472,15 @@ pub unsafe extern "C" fn mp4parse_new( #[no_mangle] pub unsafe extern "C" fn mp4parse_avif_new( io: *const Mp4parseIo, + strictness: ParseStrictness, parser_out: *mut *mut Mp4parseAvifParser, ) -> Mp4parseStatus { - mp4parse_new_common(io, parser_out) + mp4parse_new_common(io, strictness, parser_out) } unsafe fn mp4parse_new_common( io: *const Mp4parseIo, + strictness: ParseStrictness, parser_out: *mut *mut P, ) -> Mp4parseStatus { // Validate arguments from C. @@ -466,7 +492,7 @@ unsafe fn mp4parse_new_common( { Mp4parseStatus::BadArg } else { - match mp4parse_new_common_safe(&mut (*io).clone()) { + match mp4parse_new_common_safe(&mut (*io).clone(), strictness) { Ok(parser) => { *parser_out = parser; Mp4parseStatus::Ok @@ -478,33 +504,15 @@ unsafe fn mp4parse_new_common( fn mp4parse_new_common_safe( io: &mut T, + strictness: ParseStrictness, ) -> Result<*mut P, Mp4parseStatus> { - let mut context = P::Context::default(); - - P::read(io, &mut context) - .map(|_| P::with_context(context)) - .map(Box::new) - .map(Box::into_raw) + P::read(io, strictness) + .map(P::with_context) + .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from)) + .map(TryBox::into_raw) .map_err(Mp4parseStatus::from) } -impl From for Mp4parseStatus { - fn from(error: mp4parse::Error) -> Self { - match error { - Error::NoMoov | Error::InvalidData(_) => Mp4parseStatus::Invalid, - Error::Unsupported(_) => Mp4parseStatus::Unsupported, - Error::UnexpectedEOF => Mp4parseStatus::Eof, - Error::Io(_) => { - // Getting std::io::ErrorKind::UnexpectedEof is normal - // but our From trait implementation should have converted - // those to our Error::UnexpectedEOF variant. - Mp4parseStatus::Io - } - Error::OutOfMemory => Mp4parseStatus::Oom, - } - } -} - /// Free an `Mp4parseParser*` allocated by `mp4parse_new()`. /// /// # Safety @@ -515,7 +523,7 @@ impl From for Mp4parseStatus { #[no_mangle] pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) { assert!(!parser.is_null()); - let _ = Box::from_raw(parser); + let _ = TryBox::from_raw(parser); } /// Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`. @@ -528,7 +536,7 @@ pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) { #[no_mangle] pub unsafe extern "C" fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) { assert!(!parser.is_null()); - let _ = Box::from_raw(parser); + let _ = TryBox::from_raw(parser); } /// Return the number of tracks parsed by previous `mp4parse_read()` call. @@ -558,47 +566,6 @@ pub unsafe extern "C" fn mp4parse_get_track_count( Mp4parseStatus::Ok } -/// Calculate numerator * scale / denominator, if possible. -/// -/// Applying the associativity of integer arithmetic, we divide first -/// and add the remainder after multiplying each term separately -/// to preserve precision while leaving more headroom. That is, -/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. -/// -/// Return None on overflow or if the denominator is zero. -fn rational_scale(numerator: T, denominator: T, scale2: S) -> Option -where - T: PrimInt + Zero, - S: PrimInt, -{ - if denominator.is_zero() { - return None; - } - - let integer = numerator / denominator; - let remainder = numerator % denominator; - num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { - Some(integer) => remainder - .checked_mul(&s) - .and_then(|remainder| (remainder / denominator).checked_add(&integer)), - None => None, - }) -} - -fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option { - let microseconds_per_second = 1_000_000; - rational_scale::(time.0, scale.0, microseconds_per_second) -} - -fn track_time_to_us(time: TrackScaledTime, scale: TrackTimeScale) -> Option -where - T: PrimInt + Zero, -{ - assert_eq!(time.1, scale.1); - let microseconds_per_second = 1_000_000; - rational_scale::(time.0, scale.0, microseconds_per_second) -} - /// Fill the supplied `Mp4parseTrackInfo` with metadata for `track`. /// /// # Safety @@ -638,23 +605,30 @@ pub unsafe extern "C" fn mp4parse_get_track_info( let track = &context.tracks[track_index]; if let (Some(track_timescale), Some(context_timescale)) = (track.timescale, context.timescale) { - let media_time = match track.media_time.map_or(Some(0), |media_time| { - track_time_to_us(media_time, track_timescale) - }) { - Some(time) => time as i64, + let media_time: CheckedInteger<_> = match track + .media_time + .map_or(Some(Microseconds(0)), |media_time| { + track_time_to_us(media_time, track_timescale) + }) { + Some(time) => time.0.into(), + None => return Mp4parseStatus::Invalid, + }; + let empty_duration: CheckedInteger<_> = match track + .empty_duration + .map_or(Some(Microseconds(0)), |empty_duration| { + media_time_to_us(empty_duration, context_timescale) + }) { + Some(time) => time.0.into(), None => return Mp4parseStatus::Invalid, }; - let empty_duration = match track.empty_duration.map_or(Some(0), |empty_duration| { - media_time_to_us(empty_duration, context_timescale) - }) { - Some(time) => time as i64, + info.media_time = match media_time - empty_duration { + Some(difference) => difference, None => return Mp4parseStatus::Invalid, }; - info.media_time = media_time - empty_duration; if let Some(track_duration) = track.duration { match track_time_to_us(track_duration, track_timescale) { - Some(duration) => info.duration = duration, + Some(duration) => info.duration = duration.0, None => return Mp4parseStatus::Invalid, } } else { @@ -694,34 +668,46 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context(); + get_track_audio_info(&mut *parser, track_index, &mut *info).into() +} + +fn get_track_audio_info( + parser: &mut Mp4parseParser, + track_index: u32, + info: &mut Mp4parseTrackAudioInfo, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, + opus_header, + .. + } = parser; if track_index as usize >= context.tracks.len() { - return Mp4parseStatus::BadArg; + return Err(Mp4parseStatus::BadArg); } let track = &context.tracks[track_index as usize]; if track.track_type != TrackType::Audio { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } // Handle track.stsd let stsd = match track.stsd { Some(ref stsd) => stsd, - None => return Mp4parseStatus::Invalid, // Stsd should be present + None => return Err(Mp4parseStatus::Invalid), // Stsd should be present }; if stsd.descriptions.is_empty() { - return Mp4parseStatus::Invalid; // Should have at least 1 description + return Err(Mp4parseStatus::Invalid); // Should have at least 1 description } - let mut audio_sample_infos = Vec::with_capacity(stsd.descriptions.len()); + let mut audio_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; for description in stsd.descriptions.iter() { let mut sample_info = Mp4parseTrackAudioSampleInfo::default(); let audio = match description { SampleEntry::Audio(a) => a, - _ => return Mp4parseStatus::Invalid, + _ => return Err(Mp4parseStatus::Invalid), }; // UNKNOWN for unsupported format. @@ -739,6 +725,14 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( } AudioCodecSpecific::MP3 => Mp4parseCodec::Mp3, AudioCodecSpecific::ALACSpecificBox(_) => Mp4parseCodec::Alac, + #[cfg(feature = "3gpp")] + AudioCodecSpecific::AMRSpecificBox(_) => { + if audio.codec_type == CodecType::AMRNB { + Mp4parseCodec::AMRNB + } else { + Mp4parseCodec::AMRWB + } + } }; sample_info.channels = audio.channelcount as u16; sample_info.bit_depth = audio.samplesize; @@ -748,11 +742,11 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( match audio.codec_specific { AudioCodecSpecific::ES_Descriptor(ref esds) => { if esds.codec_esds.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - sample_info.extra_data.length = esds.codec_esds.len() as u32; + sample_info.extra_data.length = esds.codec_esds.len(); sample_info.extra_data.data = esds.codec_esds.as_ptr(); - sample_info.codec_specific_config.length = esds.decoder_specific_data.len() as u32; + sample_info.codec_specific_config.length = esds.decoder_specific_data.len(); sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr(); if let Some(rate) = esds.audio_sample_rate { sample_info.sample_rate = rate; @@ -772,35 +766,36 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( // Return the STREAMINFO metadata block in the codec_specific. let streaminfo = &flac.blocks[0]; if streaminfo.block_type != 0 || streaminfo.data.len() != 34 { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - sample_info.codec_specific_config.length = streaminfo.data.len() as u32; + sample_info.codec_specific_config.length = streaminfo.data.len(); sample_info.codec_specific_config.data = streaminfo.data.as_ptr(); } AudioCodecSpecific::OpusSpecificBox(ref opus) => { - let mut v = Vec::new(); + let mut v = TryVec::new(); match serialize_opus_header(opus, &mut v) { Err(_) => { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } Ok(_) => { - let header = (*parser).opus_header_mut(); - header.insert(track_index, v); - if let Some(v) = header.get(&track_index) { + opus_header.insert(track_index, v)?; + if let Some(v) = opus_header.get(&track_index) { if v.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - sample_info.codec_specific_config.length = v.len() as u32; + sample_info.codec_specific_config.length = v.len(); sample_info.codec_specific_config.data = v.as_ptr(); } } } } AudioCodecSpecific::ALACSpecificBox(ref alac) => { - sample_info.codec_specific_config.length = alac.data.len() as u32; + sample_info.codec_specific_config.length = alac.data.len(); sample_info.codec_specific_config.data = alac.data.as_ptr(); } AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (), + #[cfg(feature = "3gpp")] + AudioCodecSpecific::AMRSpecificBox(_) => (), } if let Some(p) = audio @@ -808,11 +803,13 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( .iter() .find(|sinf| sinf.tenc.is_some()) { + sample_info.protected_data.original_format = + OptionalFourCc::Some(p.original_format.value); sample_info.protected_data.scheme_type = match p.scheme_type { Some(ref scheme_type_box) => { match scheme_type_box.scheme_type.value.as_ref() { - "cenc" => Mp4ParseEncryptionSchemeType::Cenc, - "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, + b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, // We don't support other schemes, and shouldn't reach // this case. Try to gracefully handle by treating as // no encryption case. @@ -825,45 +822,38 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( sample_info.protected_data.is_encrypted = tenc.is_encrypted; sample_info.protected_data.iv_size = tenc.iv_size; sample_info.protected_data.kid.set_data(&(tenc.kid)); - sample_info.protected_data.crypt_byte_block = match tenc.crypt_byte_block_count { - Some(n) => n, - None => 0, - }; - sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count { - Some(n) => n, - None => 0, - }; + sample_info.protected_data.crypt_byte_block = + tenc.crypt_byte_block_count.unwrap_or(0); + sample_info.protected_data.skip_byte_block = + tenc.skip_byte_block_count.unwrap_or(0); if let Some(ref iv_vec) = tenc.constant_iv { if iv_vec.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } sample_info.protected_data.constant_iv.set_data(iv_vec); }; } } - let res = vec_push(&mut audio_sample_infos, sample_info); - if res.is_err() { - return Mp4parseStatus::Oom; - } + audio_sample_infos.push(sample_info)?; } - (*parser) + parser .audio_track_sample_descriptions - .insert(track_index, audio_sample_infos); - match (*parser).audio_track_sample_descriptions.get(&track_index) { + .insert(track_index, audio_sample_infos)?; + match parser.audio_track_sample_descriptions.get(&track_index) { Some(sample_info) => { if sample_info.len() > std::u32::MAX as usize { // Should never happen due to upper limits on number of sample // descriptions a track can have, but lets be safe. - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - (*info).sample_info_count = sample_info.len() as u32; - (*info).sample_info = sample_info.as_ptr(); + info.sample_info_count = sample_info.len() as u32; + info.sample_info = sample_info.as_ptr(); } - None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info! + None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! } - Mp4parseStatus::Ok + Ok(()) } /// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`. @@ -887,54 +877,62 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context(); + mp4parse_get_track_video_info_safe(&mut *parser, track_index, &mut *info).into() +} + +fn mp4parse_get_track_video_info_safe( + parser: &mut Mp4parseParser, + track_index: u32, + info: &mut Mp4parseTrackVideoInfo, +) -> Result<(), Mp4parseStatus> { + let context = parser.context(); if track_index as usize >= context.tracks.len() { - return Mp4parseStatus::BadArg; + return Err(Mp4parseStatus::BadArg); } let track = &context.tracks[track_index as usize]; if track.track_type != TrackType::Video { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } // Handle track.tkhd if let Some(ref tkhd) = track.tkhd { - (*info).display_width = tkhd.width >> 16; // 16.16 fixed point - (*info).display_height = tkhd.height >> 16; // 16.16 fixed point + info.display_width = tkhd.width >> 16; // 16.16 fixed point + info.display_height = tkhd.height >> 16; // 16.16 fixed point let matrix = ( tkhd.matrix.a >> 16, tkhd.matrix.b >> 16, tkhd.matrix.c >> 16, tkhd.matrix.d >> 16, ); - (*info).rotation = match matrix { + info.rotation = match matrix { (0, 1, -1, 0) => 90, // rotate 90 degrees (-1, 0, 0, -1) => 180, // rotate 180 degrees (0, -1, 1, 0) => 270, // rotate 270 degrees _ => 0, }; } else { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } // Handle track.stsd let stsd = match track.stsd { Some(ref stsd) => stsd, - None => return Mp4parseStatus::Invalid, // Stsd should be present + None => return Err(Mp4parseStatus::Invalid), // Stsd should be present }; if stsd.descriptions.is_empty() { - return Mp4parseStatus::Invalid; // Should have at least 1 description + return Err(Mp4parseStatus::Invalid); // Should have at least 1 description } - let mut video_sample_infos = Vec::with_capacity(stsd.descriptions.len()); + let mut video_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; for description in stsd.descriptions.iter() { let mut sample_info = Mp4parseTrackVideoSampleInfo::default(); let video = match description { SampleEntry::Video(v) => v, - _ => return Mp4parseStatus::Invalid, + _ => return Err(Mp4parseStatus::Invalid), }; // UNKNOWN for unsupported format. @@ -942,6 +940,10 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( VideoCodecSpecific::VPxConfig(_) => Mp4parseCodec::Vp9, VideoCodecSpecific::AV1Config(_) => Mp4parseCodec::Av1, VideoCodecSpecific::AVCConfig(_) => Mp4parseCodec::Avc, + VideoCodecSpecific::H263Config(_) => Mp4parseCodec::H263, + #[cfg(feature = "mp4v")] + VideoCodecSpecific::ESDSConfig(_) => Mp4parseCodec::Mp4v, + #[cfg(not(feature = "mp4v"))] VideoCodecSpecific::ESDSConfig(_) => // MP4V (14496-2) video is unsupported. { @@ -952,6 +954,9 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( sample_info.image_height = video.height; match video.codec_specific { + VideoCodecSpecific::AV1Config(ref config) => { + sample_info.extra_data.set_data(&config.raw_config); + } VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => { sample_info.extra_data.set_data(data); } @@ -963,11 +968,13 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( .iter() .find(|sinf| sinf.tenc.is_some()) { + sample_info.protected_data.original_format = + OptionalFourCc::Some(p.original_format.value); sample_info.protected_data.scheme_type = match p.scheme_type { Some(ref scheme_type_box) => { match scheme_type_box.scheme_type.value.as_ref() { - "cenc" => Mp4ParseEncryptionSchemeType::Cenc, - "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, + b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, // We don't support other schemes, and shouldn't reach // this case. Try to gracefully handle by treating as // no encryption case. @@ -980,44 +987,37 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( sample_info.protected_data.is_encrypted = tenc.is_encrypted; sample_info.protected_data.iv_size = tenc.iv_size; sample_info.protected_data.kid.set_data(&(tenc.kid)); - sample_info.protected_data.crypt_byte_block = match tenc.crypt_byte_block_count { - Some(n) => n, - None => 0, - }; - sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count { - Some(n) => n, - None => 0, - }; + sample_info.protected_data.crypt_byte_block = + tenc.crypt_byte_block_count.unwrap_or(0); + sample_info.protected_data.skip_byte_block = + tenc.skip_byte_block_count.unwrap_or(0); if let Some(ref iv_vec) = tenc.constant_iv { if iv_vec.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } sample_info.protected_data.constant_iv.set_data(iv_vec); }; } } - let res = vec_push(&mut video_sample_infos, sample_info); - if res.is_err() { - return Mp4parseStatus::Oom; - } + video_sample_infos.push(sample_info)?; } - (*parser) + parser .video_track_sample_descriptions - .insert(track_index, video_sample_infos); - match (*parser).video_track_sample_descriptions.get(&track_index) { + .insert(track_index, video_sample_infos)?; + match parser.video_track_sample_descriptions.get(&track_index) { Some(sample_info) => { if sample_info.len() > std::u32::MAX as usize { // Should never happen due to upper limits on number of sample // descriptions a track can have, but lets be safe. - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - (*info).sample_info_count = sample_info.len() as u32; - (*info).sample_info = sample_info.as_ptr(); + info.sample_info_count = sample_info.len() as u32; + info.sample_info = sample_info.as_ptr(); } - None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info! + None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! } - Mp4parseStatus::Ok + Ok(()) } /// Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call. @@ -1025,29 +1025,56 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( /// # Safety /// /// This function is unsafe because it dereferences both the parser and -/// primary_item raw pointers passed into it. Callers should ensure the parser -/// pointer points to a valid `Mp4parseAvifParser`, and that the primary_item -/// pointer points to a valid `Mp4parseByteData`. If there was not a previous +/// avif_image raw pointers passed into it. Callers should ensure the parser +/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_image +/// pointer points to a valid `Mp4parseAvifImage`. If there was not a previous /// successful call to `mp4parse_avif_read()`, no guarantees are made as to -/// the state of `primary_item`. +/// the state of `avif_image`. If `avif_image.alpha_image.coded_data` is set to +/// a positive `length` and non-null `data`, then the `avif_image` contains a +/// valid alpha channel data. Otherwise, the image is opaque. #[no_mangle] -pub unsafe extern "C" fn mp4parse_avif_get_primary_item( - parser: *mut Mp4parseAvifParser, - primary_item: *mut Mp4parseByteData, +pub unsafe extern "C" fn mp4parse_avif_get_image( + parser: *const Mp4parseAvifParser, + avif_image: *mut Mp4parseAvifImage, ) -> Mp4parseStatus { - if parser.is_null() { + if parser.is_null() || avif_image.is_null() { return Mp4parseStatus::BadArg; } - // Initialize fields to default values to ensure all fields are always valid. - *primary_item = Default::default(); + if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) { + *avif_image = image; + Mp4parseStatus::Ok + } else { + Mp4parseStatus::Invalid + } +} - let context = (*parser).context(); +pub fn mp4parse_avif_get_image_safe( + parser: &Mp4parseAvifParser, +) -> mp4parse::Result { + let context = parser.context(); - // TODO: check for a valid parsed context. See https://github.com/mozilla/mp4parse-rust/issues/195 - (*primary_item).set_data(&context.primary_item); + let primary_image = Mp4parseAvifImageItem { + coded_data: Mp4parseByteData::with_data(context.primary_item_coded_data()), + bits_per_channel: Mp4parseByteData::with_data(context.primary_item_bits_per_channel()?), + }; - Mp4parseStatus::Ok + // If there is no alpha present, all the `Mp4parseByteData`s will be zero length + let alpha_image = Mp4parseAvifImageItem { + coded_data: Mp4parseByteData::with_data(context.alpha_item_coded_data()), + bits_per_channel: Mp4parseByteData::with_data(context.alpha_item_bits_per_channel()?), + }; + + Ok(Mp4parseAvifImage { + primary_image, + spatial_extents: context.spatial_extents_ptr()?, + nclx_colour_information: context.nclx_colour_information_ptr()?, + icc_colour_information: Mp4parseByteData::with_data(context.icc_colour_information()?), + image_rotation: context.image_rotation()?, + image_mirror: context.image_mirror_ptr()?, + alpha_image, + premultiplied_alpha: context.premultiplied_alpha, + }) } /// Fill the supplied `Mp4parseByteData` with index information from `track`. @@ -1071,365 +1098,62 @@ pub unsafe extern "C" fn mp4parse_get_indice_table( // Initialize fields to default values to ensure all fields are always valid. *indices = Default::default(); - let context = (*parser).context(); + get_indice_table(&mut *parser, track_id, &mut *indices).into() +} + +fn get_indice_table( + parser: &mut Mp4parseParser, + track_id: u32, + indices: &mut Mp4parseByteData, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, + sample_table: index_table, + .. + } = parser; let tracks = &context.tracks; let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) { Some(t) => t, - _ => return Mp4parseStatus::Invalid, + _ => return Err(Mp4parseStatus::Invalid), }; - let index_table = (*parser).sample_table_mut(); if let Some(v) = index_table.get(&track_id) { - (*indices).set_indices(v); - return Mp4parseStatus::Ok; + indices.set_indices(v); + return Ok(()); } let media_time = match (&track.media_time, &track.timescale) { - (&Some(t), &Some(s)) => track_time_to_us(t, s).map(|v| v as i64), + (&Some(t), &Some(s)) => track_time_to_us(t, s) + .and_then(|v| i64::try_from(v.0).ok()) + .map(Into::into), _ => None, }; - let empty_duration = match (&track.empty_duration, &context.timescale) { - (&Some(e), &Some(s)) => media_time_to_us(e, s).map(|v| v as i64), - _ => None, - }; + let empty_duration: Option> = + match (&track.empty_duration, &context.timescale) { + (&Some(e), &Some(s)) => media_time_to_us(e, s) + .and_then(|v| i64::try_from(v.0).ok()) + .map(Into::into), + _ => None, + }; // Find the track start offset time from 'elst'. // 'media_time' maps start time onward, 'empty_duration' adds time offset // before first frame is displayed. let offset_time = match (empty_duration, media_time) { - (Some(e), Some(m)) => e - m, + (Some(e), Some(m)) => (e - m).ok_or(Err(Mp4parseStatus::Invalid))?, (Some(e), None) => e, (None, Some(m)) => m, - _ => 0, + _ => 0.into(), }; if let Some(v) = create_sample_table(track, offset_time) { - (*indices).set_indices(&v); - index_table.insert(track_id, v); - return Mp4parseStatus::Ok; + indices.set_indices(&v); + index_table.insert(track_id, v)?; + return Ok(()); } - Mp4parseStatus::Invalid -} - -// Convert a 'ctts' compact table to full table by iterator, -// (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ... -// -// For example: -// (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time(). -struct TimeOffsetIterator<'a> { - cur_sample_range: std::ops::Range, - cur_offset: i64, - ctts_iter: Option>, - track_id: usize, -} - -impl<'a> Iterator for TimeOffsetIterator<'a> { - type Item = i64; - - fn next(&mut self) -> Option { - let has_sample = self.cur_sample_range.next().or_else(|| { - // At end of current TimeOffset, find the next TimeOffset. - let iter = match self.ctts_iter { - Some(ref mut v) => v, - _ => return None, - }; - let offset_version; - self.cur_sample_range = match iter.next() { - Some(v) => { - offset_version = v.time_offset; - 0..v.sample_count - } - _ => { - offset_version = mp4parse::TimeOffsetVersion::Version0(0); - 0..0 - } - }; - - self.cur_offset = match offset_version { - mp4parse::TimeOffsetVersion::Version0(i) => i64::from(i), - mp4parse::TimeOffsetVersion::Version1(i) => i64::from(i), - }; - - self.cur_sample_range.next() - }); - - has_sample.and(Some(self.cur_offset)) - } -} - -impl<'a> TimeOffsetIterator<'a> { - fn next_offset_time(&mut self) -> TrackScaledTime { - match self.next() { - Some(v) => TrackScaledTime::(v as i64, self.track_id), - _ => TrackScaledTime::(0, self.track_id), - } - } -} - -// Convert 'stts' compact table to full table by iterator, -// (sample_count_with_the_same_time, time) => (time, time, time) ... repeats -// sample_count_with_the_same_time. -// -// For example: -// (2, 3000), (1, 2999) to (3000, 3000, 2999). -struct TimeToSampleIterator<'a> { - cur_sample_count: std::ops::Range, - cur_sample_delta: u32, - stts_iter: std::slice::Iter<'a, mp4parse::Sample>, - track_id: usize, -} - -impl<'a> Iterator for TimeToSampleIterator<'a> { - type Item = u32; - - fn next(&mut self) -> Option { - let has_sample = self.cur_sample_count.next().or_else(|| { - self.cur_sample_count = match self.stts_iter.next() { - Some(v) => { - self.cur_sample_delta = v.sample_delta; - 0..v.sample_count - } - _ => 0..0, - }; - - self.cur_sample_count.next() - }); - - has_sample.and(Some(self.cur_sample_delta)) - } -} - -impl<'a> TimeToSampleIterator<'a> { - fn next_delta(&mut self) -> TrackScaledTime { - match self.next() { - Some(v) => TrackScaledTime::(i64::from(v), self.track_id), - _ => TrackScaledTime::(0, self.track_id), - } - } -} - -// Convert 'stco' compact table to full table by iterator. -// (start_chunk_num, sample_number) => (start_chunk_num, sample_number), -// (start_chunk_num + 1, sample_number), -// (start_chunk_num + 2, sample_number), -// ... -// (next start_chunk_num, next sample_number), -// ... -// -// For example: -// (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10), -// (7, 10), (8, 10), (9, 2) -struct SampleToChunkIterator<'a> { - chunks: std::ops::Range, - sample_count: u32, - stsc_peek_iter: std::iter::Peekable>, - remain_chunk_count: u32, // total chunk number from 'stco'. -} - -impl<'a> Iterator for SampleToChunkIterator<'a> { - type Item = (u32, u32); - - fn next(&mut self) -> Option<(u32, u32)> { - let has_chunk = self.chunks.next().or_else(|| { - self.chunks = self.locate(); - self.remain_chunk_count - .checked_sub(self.chunks.len() as u32) - .and_then(|res| { - self.remain_chunk_count = res; - self.chunks.next() - }) - }); - - has_chunk.map(|id| (id, self.sample_count)) - } -} - -impl<'a> SampleToChunkIterator<'a> { - fn locate(&mut self) -> std::ops::Range { - loop { - return match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) { - (Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => { - // Invalid entry, skip it and will continue searching at - // next loop iteration. - continue; - } - (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => { - self.sample_count = next.samples_per_chunk; - (next.first_chunk - 1)..(peek.first_chunk - 1) - } - (Some(next), None) if next.first_chunk > 0 => { - self.sample_count = next.samples_per_chunk; - // Total chunk number in 'stsc' could be different to 'stco', - // there could be more chunks at the last 'stsc' record. - match next.first_chunk.checked_add(self.remain_chunk_count) { - Some(r) => (next.first_chunk - 1)..r - 1, - _ => 0..0, - } - } - _ => 0..0, - }; - } - } -} - -fn create_sample_table(track: &Track, track_offset_time: i64) -> Option> { - let timescale = match track.timescale { - Some(ref t) => TrackTimeScale::(t.0 as i64, t.1), - _ => return None, - }; - - let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) { - (&Some(ref a), &Some(ref b), &Some(ref c), &Some(ref d)) => (a, b, c, d), - _ => return None, - }; - - // According to spec, no sync table means every sample is sync sample. - let has_sync_table = match track.stss { - Some(_) => true, - _ => false, - }; - - let mut sample_table = Vec::new(); - let mut sample_size_iter = stsz.sample_sizes.iter(); - - // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample - // offset address. - let stsc_iter = SampleToChunkIterator { - chunks: (0..0), - sample_count: 0, - stsc_peek_iter: stsc.samples.as_slice().iter().peekable(), - remain_chunk_count: stco.offsets.len() as u32, - }; - - for i in stsc_iter { - let chunk_id = i.0 as usize; - let sample_counts = i.1; - let mut cur_position = match stco.offsets.get(chunk_id) { - Some(&i) => i, - _ => return None, - }; - for _ in 0..sample_counts { - let start_offset = cur_position; - let end_offset = match (stsz.sample_size, sample_size_iter.next()) { - (_, Some(t)) => start_offset + u64::from(*t), - (t, _) if t > 0 => start_offset + u64::from(t), - _ => 0, - }; - if end_offset == 0 { - return None; - } - cur_position = end_offset; - - let res = vec_push( - &mut sample_table, - Mp4parseIndice { - start_offset, - end_offset, - start_composition: 0, - end_composition: 0, - start_decode: 0, - sync: !has_sync_table, - }, - ); - if res.is_err() { - return None; - } - } - } - - // Mark the sync sample in sample_table according to 'stss'. - if let Some(ref v) = track.stss { - for iter in &v.samples { - match iter - .checked_sub(1) - .and_then(|idx| sample_table.get_mut(idx as usize)) - { - Some(elem) => elem.sync = true, - _ => return None, - } - } - } - - let ctts_iter = match track.ctts { - Some(ref v) => Some(v.samples.as_slice().iter()), - _ => None, - }; - - let mut ctts_offset_iter = TimeOffsetIterator { - cur_sample_range: (0..0), - cur_offset: 0, - ctts_iter, - track_id: track.id, - }; - - let mut stts_iter = TimeToSampleIterator { - cur_sample_count: (0..0), - cur_sample_delta: 0, - stts_iter: stts.samples.as_slice().iter(), - track_id: track.id, - }; - - // sum_delta is the sum of stts_iter delta. - // According to sepc: - // decode time => DT(n) = DT(n-1) + STTS(n) - // composition time => CT(n) = DT(n) + CTTS(n) - // Note: - // composition time needs to add the track offset time from 'elst' table. - let mut sum_delta = TrackScaledTime::(0, track.id); - for sample in sample_table.as_mut_slice() { - let decode_time = sum_delta; - sum_delta = sum_delta + stts_iter.next_delta(); - - // ctts_offset is the current sample offset time. - let ctts_offset = ctts_offset_iter.next_offset_time(); - - let start_composition = track_time_to_us(decode_time + ctts_offset, timescale); - - let end_composition = track_time_to_us(sum_delta + ctts_offset, timescale); - - let start_decode = track_time_to_us(decode_time, timescale); - - match (start_composition, end_composition, start_decode) { - (Some(s_c), Some(e_c), Some(s_d)) => { - sample.start_composition = s_c + track_offset_time; - sample.end_composition = e_c + track_offset_time; - sample.start_decode = s_d; - } - _ => return None, - } - } - - // Correct composition end time due to 'ctts' causes composition time re-ordering. - // - // Composition end time is not in specification. However, gecko needs it, so we need to - // calculate to correct the composition end time. - if !sample_table.is_empty() { - // Create an index table refers to sample_table and sorted by start_composisiton time. - let mut sort_table = Vec::new(); - for i in 0..sample_table.len() { - if vec_push(&mut sort_table, i).is_err() { - return None; - } - } - - sort_table.sort_by_key(|i| match sample_table.get(*i) { - Some(v) => v.start_composition, - _ => 0, - }); - - let iter = sort_table.iter(); - for i in 0..(iter.len() - 1) { - let current_index = sort_table[i]; - let peek_index = sort_table[i + 1]; - let next_start_composition_time = sample_table[peek_index].start_composition; - let sample = &mut sample_table[current_index]; - sample.end_composition = next_start_composition_time; - } - } - - Some(sample_table) + Err(Mp4parseStatus::Invalid) } /// Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file. @@ -1465,7 +1189,7 @@ pub unsafe extern "C" fn mp4parse_get_fragment_info( if let (Some(time), Some(scale)) = (duration, context.timescale) { info.fragment_duration = match media_time_to_us(time, scale) { - Some(time_us) => time_us as u64, + Some(time_us) => time_us.0 as u64, None => return Mp4parseStatus::Invalid, } } @@ -1544,37 +1268,39 @@ pub unsafe extern "C" fn mp4parse_get_pssh_info( // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context_mut(); - let pssh_data = (*parser).pssh_data_mut(); - let info: &mut Mp4parsePsshInfo = &mut *info; + get_pssh_info(&mut *parser, &mut *info).into() +} + +fn get_pssh_info( + parser: &mut Mp4parseParser, + info: &mut Mp4parsePsshInfo, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, pssh_data, .. + } = parser; pssh_data.clear(); for pssh in &context.psshs { - let content_len = pssh.box_content.len(); - if content_len > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; - } - let mut data_len = Vec::new(); + let content_len = pssh + .box_content + .len() + .try_into() + .map_err(|_| Mp4parseStatus::Invalid)?; + let mut data_len = TryVec::new(); if data_len - .write_u32::(content_len as u32) + .write_u32::(content_len) .is_err() { - return Mp4parseStatus::Io; - } - pssh_data.extend_from_slice(pssh.system_id.as_slice()); - pssh_data.extend_from_slice(data_len.as_slice()); - // The previous two calls have known, small sizes, but pssh_data has - // arbitrary size based on untrusted input, so use fallible allocation - let res = extend_from_slice(pssh_data, pssh.box_content.as_slice()); - - if res.is_err() { - return Mp4parseStatus::Oom; + return Err(Mp4parseStatus::Io); } + pssh_data.extend_from_slice(pssh.system_id.as_slice())?; + pssh_data.extend_from_slice(data_len.as_slice())?; + pssh_data.extend_from_slice(pssh.box_content.as_slice())?; } info.data.set_data(pssh_data); - Mp4parseStatus::Ok + Ok(()) } #[cfg(test)] @@ -1640,9 +1366,7 @@ fn arg_validation() { let mut dummy_info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - track_id: 0, - duration: 0, - media_time: 0, + ..Default::default() }; assert_eq!( Mp4parseStatus::BadArg, @@ -1676,7 +1400,7 @@ fn parser_input_must_be_null() { read: Some(error_read), userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, }; - let mut parser = 0xDEADBEEF as *mut _; + let mut parser = 0xDEAD_BEEF as *mut _; let rv = unsafe { mp4parse_new(&io, &mut parser) }; assert_eq!(rv, Mp4parseStatus::BadArg); } @@ -1710,9 +1434,7 @@ fn arg_validation_with_parser() { let mut dummy_info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - track_id: 0, - duration: 0, - media_time: 0, + ..Default::default() }; assert_eq!( Mp4parseStatus::BadArg, @@ -1784,9 +1506,7 @@ fn minimal_mp4_get_track_info() { let mut info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - track_id: 0, - duration: 0, - media_time: 0, + ..Default::default() }; assert_eq!(Mp4parseStatus::Ok, unsafe { mp4parse_get_track_info(parser, 0, &mut info) @@ -1858,9 +1578,7 @@ fn minimal_mp4_get_track_info_invalid_track_number() { let mut info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - track_id: 0, - duration: 0, - media_time: 0, + ..Default::default() }; assert_eq!(Mp4parseStatus::BadArg, unsafe { mp4parse_get_track_info(parser, 3, &mut info) @@ -1888,35 +1606,3 @@ fn minimal_mp4_get_track_info_invalid_track_number() { mp4parse_free(parser); } } - -#[test] -fn rational_scale_overflow() { - assert_eq!(rational_scale::(17, 3, 1000), Some(5666)); - let large = 0x4000_0000_0000_0000; - assert_eq!(rational_scale::(large, 2, 2), Some(large)); - assert_eq!(rational_scale::(large, 4, 4), Some(large)); - assert_eq!(rational_scale::(large, 2, 8), None); - assert_eq!(rational_scale::(large, 8, 4), Some(large / 2)); - assert_eq!(rational_scale::(large + 1, 4, 4), Some(large + 1)); - assert_eq!(rational_scale::(large, 40, 1000), None); -} - -#[test] -fn media_time_overflow() { - let scale = MediaTimeScale(90000); - let duration = MediaScaledTime(9_007_199_254_710_000); - assert_eq!( - media_time_to_us(duration, scale), - Some(100_079_991_719_000_000) - ); -} - -#[test] -fn track_time_overflow() { - let scale = TrackTimeScale(44100u64, 0); - let duration = TrackScaledTime(4_413_527_634_807_900u64, 0); - assert_eq!( - track_time_to_us(duration, scale), - Some(100_079_991_719_000_000) - ); -} diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index c45439fa96..fce5195348 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -4026,6 +4026,32 @@ value: true mirror: always +# Whether we attempt to decode AVIF images or not. +- name: image.avif.enabled + type: RelaxedAtomicBool +#if defined(MOZ_AV1) + value: true +#else + value: false +#endif + mirror: always + +# How strict we are in accepting/rejecting AVIF inputs according to whether they +# conform to the specification +# 0 = Permissive: accept whatever we can simply, unambiguously interpret +# 1 = Normal: reject violations of "shall" specification directives +# 2 = Strict: reject violations of "should" specification directives +- name: image.avif.compliance_strictness + type: RelaxedAtomicInt32 + value: 1 + mirror: always + +# Whether we apply container-level transforms like mirroring and rotation +- name: image.avif.apply_transforms + type: RelaxedAtomicBool + value: true + mirror: always + #--------------------------------------------------------------------------- # Prefs starting with "intl." #--------------------------------------------------------------------------- diff --git a/netwerk/mime/nsMimeTypes.h b/netwerk/mime/nsMimeTypes.h index 030a201b87..efa9985af2 100644 --- a/netwerk/mime/nsMimeTypes.h +++ b/netwerk/mime/nsMimeTypes.h @@ -118,6 +118,7 @@ #define IMAGE_JNG "image/x-jng" #define IMAGE_SVG_XML "image/svg+xml" #define IMAGE_WEBP "image/webp" +#define IMAGE_AVIF "image/avif" #define MESSAGE_EXTERNAL_BODY "message/external-body" #define MESSAGE_NEWS "message/news" diff --git a/third_party/rust/ahash/.cargo-checksum.json b/third_party/rust/ahash/.cargo-checksum.json new file mode 100644 index 0000000000..c7c18efd62 --- /dev/null +++ b/third_party/rust/ahash/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"ca9d74aa988a7af08d61e87c813584993c4fbc4c03af2b69bfcced15866d842e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2","README.md":"96e6af5def25a27317b764c060c944dbaee267f32096708e47eb159a8159379c","rustfmt.toml":"e090969e99df9360705680cc0097cfaddae10c22dc2e01470592cf3b9787fd36","smhasher/0001-Add-support-for-aHash.patch":"df01a714dd66554cab415afdec1bd634d19a53e564029ba4b84d2e1baffb6104","smhasher/0002-Add-support-for-aHash.patch":"47fe89f174b921dd134ce95d7147468680b87ad12180d3a00aa8aebdace1c104","smhasher/ahashOutput.txt":"25eaaf3615286ababb15d560ce16a741ed6447a8679b6b6633e0f39c0cc9c5c2","smhasher/clone_smhasher.sh":"510e12ec67052486853f186ce3e18f342fc80da88a47bf9b1aca81c827cb72b7","smhasher/fallbackOutput.txt":"247a408cb8426ed6c0119861d1b06809983cbbff73eafc181fb29910139c1f01","src/aes_hash.rs":"552c00906b6711a0f0f523f639100cfe626a4fd04e9b0cad5fd2d2a1ebdd0186","src/convert.rs":"59e9c20bfd37e8604c80b97339b77e6f6a600c8b177b672cad9ac3600f7f88e0","src/fallback_hash.rs":"5636a30aac53aff84dad46b9228db8957a9b2c2358ab51b1bcb786d751e55954","src/hash_map.rs":"36bf0b13e334d7cedbf83e4822438098b227e7c7e381abe5e1eeac1ff6caa209","src/hash_quality_test.rs":"bc642c29539a4acc75aa460d409008b7939c0eb8687e07e1a0d47f29fbbc9abb","src/hash_set.rs":"4289672c142e314a0bfc6535b5e8f5e07cc78b60c0c7b308a43fa361eca6ddea","src/lib.rs":"4a0b56b99ba7b941fc3a9412e825af4a9525e003192a894a055b8df4fda1bfbc","src/operations.rs":"82663e2e9d2dac714e84eb0c1daac979d20f7e80a7391027f8d03b0987186804","src/random_state.rs":"ac2aae2cf85d05be97376819291fe094109b85ce6bdcdcbeda26d85e10d2f6b3","src/specialize.rs":"3136ba21f0e9b991f6df589744f724d8e71586e0b48c969183a8b33ce7966c8c","tests/bench.rs":"90cc49473a25184c4d8d21d623a52b103e2d7c7c30e487da000b510c4ba5d944","tests/map_tests.rs":"c1e14ec39c2009d8a081349245e70af317c2d8424d40dc239a6c250c0adfae77","tests/nopanic.rs":"5edbaa43a16f8fc96180c2f3d576436640e6fc3221856df329b0540024625cab"},"package":"739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"} \ No newline at end of file diff --git a/third_party/rust/ahash/Cargo.toml b/third_party/rust/ahash/Cargo.toml new file mode 100644 index 0000000000..8ecf11b73d --- /dev/null +++ b/third_party/rust/ahash/Cargo.toml @@ -0,0 +1,91 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "ahash" +version = "0.4.7" +authors = ["Tom Kaitchuck "] +description = "A non-cryptographic hash function using AES-NI for high performance" +documentation = "https://docs.rs/ahash" +readme = "README.md" +keywords = ["hash", "hashmap", "aes", "aes-ni", "no-std"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/tkaitchuck/ahash" +[package.metadata.docs.rs] +features = ["std"] +rustc-args = ["-C", "target-feature=+aes"] +rustdoc-args = ["-C", "target-feature=+aes"] +[profile.bench] +opt-level = 3 +lto = "fat" +codegen-units = 1 +debug = false +debug-assertions = false + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +debug = false +debug-assertions = false + +[profile.test] +opt-level = 2 +lto = "fat" + +[lib] +name = "ahash" +path = "src/lib.rs" +test = true +doctest = true +bench = true +doc = true + +[[bench]] +name = "ahash" +path = "tests/bench.rs" +harness = false + +[[bench]] +name = "map" +path = "tests/map_tests.rs" +harness = false +[dependencies.const-random] +version = "0.1.6" +optional = true +[dev-dependencies.criterion] +version = "0.3.2" + +[dev-dependencies.fnv] +version = "1.0.5" + +[dev-dependencies.fxhash] +version = "0.2.1" + +[dev-dependencies.hex] +version = "0.3.2" + +[dev-dependencies.no-panic] +version = "0.1.10" + +[dev-dependencies.rand] +version = "0.6.5" + +[dev-dependencies.seahash] +version = "3.0.5" + +[features] +compile-time-rng = ["const-random"] +default = ["compile-time-rng", "std"] +specialize = [] +std = [] diff --git a/third_party/rust/ahash/LICENSE-APACHE b/third_party/rust/ahash/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/ahash/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/ahash/LICENSE-MIT b/third_party/rust/ahash/LICENSE-MIT new file mode 100644 index 0000000000..5afc2a7b0a --- /dev/null +++ b/third_party/rust/ahash/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 Amanieu d'Antras + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/ahash/README.md b/third_party/rust/ahash/README.md new file mode 100644 index 0000000000..b29d7080ec --- /dev/null +++ b/third_party/rust/ahash/README.md @@ -0,0 +1,245 @@ +# aHash ![Build Status](https://img.shields.io/github/workflow/status/tkaitchuck/ahash/Rust) ![Licence](https://img.shields.io/crates/l/ahash) ![Downloads](https://img.shields.io/crates/d/ahash) + +AHash is a high speed keyed hashing algorithm intended for use in in-memory hashmaps. It provides a high quality +64bit hash. AHash is designed for performance and is not cryptographically secure. + +## Goals + +AHash is the fastest DOS resistant hash for use in HashMaps available in the Rust language. +Failing in any of these criteria will be treated as a bug. + +## Design + +AHash is a keyed hash, so two instances initialized with different keys will produce completely different hashes, and the +resulting hashes cannot be predicted without knowing the keys. [This prevents DOS attacks where an attacker sends a large +number of items whose hashes collide that get used as keys in a hashmap.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks) + +AHash takes advantage of specialized hardware instructions whenever possible including the [hardware AES instruction](https://en.wikipedia.org/wiki/AES_instruction_set) +on X86 processors when it is available. If it is not available it falls back on a somewhat slower (but still DOS resistant) +[algorithm based on multiplication](https://github.com/tkaitchuck/aHash/wiki/AHash-fallback-algorithm). + +As such aHash does not have a fixed standard for its output. This is not a problem for Hashmaps, and allows aHash to achieve high performance and improve over time. + +## Non-Goals + +Because different computers or computers on versions of the code will observe different outputs Hash is not recommended +for use other than in-memory maps. Specifically aHash does not intend to be: + +* Used as a MACs or other application requiring a cryptographically secure hash +* Used for distributed applications or ones requiring persisting hashed values + +## Hash quality + +**Both aHash's aes variant and the fallback pass the full [SMHasher test suite](https://github.com/rurban/smhasher)** (the output of the tests is checked into the smhasher subdirectory.) + +At **over 50GB/s** aHash is the fastest algorithm to pass the full test suite by more than a factor of 2. Even the fallback algorithm is in the top 5 in terms of throughput. + +## Speed + +When it is available aHash uses AES rounds using the AES-NI instruction. AES-NI is very fast (on an intel i7-6700 it +is as fast as a 64 bit multiplication.) and handles 16 bytes of input at a time, while being a very strong permutation. + +This is obviously much faster than most standard approaches to hashing, and does a better job of scrambling data than most non-secure hashes. + +On an intel i7-6700 compiled on nightly Rust with flags `-C opt-level=3 -C target-cpu=native -C codegen-units=1`: + +| Input | SipHash 1-3 time | FnvHash time|FxHash time| aHash time| aHash Fallback* | +|----------------|-----------|-----------|-----------|-----------|---------------| +| u8 | 9.3271 ns | 0.808 ns | **0.594 ns** | 0.7704 ns | 0.7664 ns | +| u16 | 9.5139 ns | 0.803 ns | **0.594 ns** | 0.7653 ns | 0.7704 ns | +| u32 | 9.1196 ns | 1.4424 ns | **0.594 ns** | 0.7637 ns | 0.7712 ns | +| u64 | 10.854 ns | 3.0484 ns | **0.628 ns** | 0.7788 ns | 0.7888 ns | +| u128 | 12.465 ns | 7.0728 ns | 0.799 ns | **0.6174 ns** | 0.6250 ns | +| 1 byte string | 11.745 ns | 2.4743 ns | 2.4000 ns | **1.4921 ns** | 1.5861 ns | +| 3 byte string | 12.066 ns | 3.5221 ns | 2.9253 ns | **1.4745 ns** | 1.8518 ns | +| 4 byte string | 11.634 ns | 4.0770 ns | 1.8818 ns | **1.5206 ns** | 1.8924 ns | +| 7 byte string | 14.762 ns | 5.9780 ns | 3.2282 ns | **1.5207 ns** | 1.8933 ns | +| 8 byte string | 13.442 ns | 4.0535 ns | 2.9422 ns | **1.6262 ns** | 1.8929 ns | +| 15 byte string | 16.880 ns | 8.3434 ns | 4.6070 ns | **1.6265 ns** | 1.7965 ns | +| 16 byte string | 15.155 ns | 7.5796 ns | 3.2619 ns | **1.6262 ns** | 1.8011 ns | +| 24 byte string | 16.521 ns | 12.492 ns | 3.5424 ns | **1.6266 ns** | 2.8311 ns | +| 68 byte string | 24.598 ns | 50.715 ns | 5.8312 ns | **4.8282 ns** | 5.4824 ns | +| 132 byte string| 39.224 ns | 119.96 ns | 11.777 ns | **6.5087 ns** | 9.1459 ns | +|1024 byte string| 254.00 ns | 1087.3 ns | 156.41 ns | **25.402 ns** | 54.566 ns | + +* Fallback refers to the algorithm aHash would use if AES instructions are unavailable. +For reference a hash that does nothing (not even reads the input data takes) **0.520 ns**. So that represents the fastest +possible time. + +As you can see above aHash like `FxHash` provides a large speedup over `SipHash-1-3` which is already nearly twice as fast as `SipHash-2-4`. + +Rust's HashMap by default uses `SipHash-1-3` because faster hash functions such as `FxHash` are predictable and vulnerable to denial of +service attacks. While `aHash` has both very strong scrambling and very high performance. + +AHash performs well when dealing with large inputs because aHash reads 8 or 16 bytes at a time. (depending on availability of AES-NI) + +Because of this, and its optimized logic, `aHash` is able to outperform `FxHash` with strings. +It also provides especially good performance dealing with unaligned input. +(Notice the big performance gaps between 3 vs 4, 7 vs 8 and 15 vs 16 in `FxHash` above) + +For more a more representative performance comparison which includes the overhead of using a HashMap, see [HashBrown's benchmarks](https://github.com/rust-lang/hashbrown#performance) +as HashBrown now uses aHash as its hasher by default. + +## Security + +AHash is designed to [prevent an adversary that does not know the key from being able to create hash collisions or partial collisions.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks) + +This achieved by ensuring that: + +* aHash is designed to [resist differential crypto analysis](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks#differential-analysis). Meaning it should not be possible to devise a scheme to "cancel" out a modification of the internal state from a block of input via some corresponding change in a subsequent block of input. + * This is achieved by not performing any "premixing" - This reversible mixing gave previous hashes such as murmurhash confidence in their quality, but could be undone by a deliberate attack. + * Before it is used each chunk of input is "masked" such as by xoring it with an unpredictable value. +* aHash obeys the '[strict avalanche criterion](https://en.wikipedia.org/wiki/Avalanche_effect#Strict_avalanche_criterion)': +Each bit of input has the potential to flip every bit of the output. +* Similarly, each bit in the key can affect every bit in the output. +* Input bits never affect just one, or a very few, bits in intermediate state. This is specifically designed to prevent the sort of +[differential attacks launched by the sipHash authors](https://emboss.github.io/blog/2012/12/14/breaking-murmur-hash-flooding-dos-reloaded/) which cancel previous inputs. +* The `finish` call at the end of the hash is designed to not expose individual bits of the internal state. + * For example in the main algorithm 256bits of state and 256bits of keys are reduced to 64 total bits using 3 rounds of AES encryption. +Reversing this is more than non-trivial. Most of the information is by definition gone, and any given bit of the internal state is fully diffused across the output. +* In both aHash and its fallback the internal state is divided into two halves which are updated by two unrelated techniques using the same input. - This means that if there is a way to attack one of them it likely won't be able to attack both of them at the same time. +* It is deliberately difficult to 'chain' collisions. + * To attack Previous attacks on hash functions have relied on the ability + +More details are available on [the wiki](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks). + +### aHash is not cryptographically secure + +AHash should not be used for situations where cryptographic security is needed. +It is not intended for this and will likely fail to hold up for several reasons. + +1. aHash relies on random keys which are assumed to not be observable by an attacker. For a cryptographic hash all inputs can be seen and controlled by the attacker. +2. aHash has not yet gone through peer review. +3. Because aHash uses reduced rounds of AES as opposed to the standard of 10. Things like the SQUARE attack apply to part of the internal state. +(These are mitigated by other means to prevent producing collections, but would be a problem in other contexts). +4. Like any cypher based hash, it will show certain statistical deviations from truly random output when comparing a (VERY) large number of hashes. +(By definition cyphers have fewer collisions than truly random data.) + +There are several efforts to build a secure hash function that uses AES-NI for acceleration, but aHash is not one of them. + +## Accelerated CPUs + +Hardware AES instructions are built into Intel processors built after 2010 and AMD processors after 2012. +It is also available on [many other CPUs](https://en.wikipedia.org/wiki/AES_instruction_set) should in eventually +be able to get aHash to work. However, only X86 and X86-64 are the only supported architectures at the moment, as currently +they are the only architectures for which Rust provides an intrinsic. + +aHash also uses `sse2` and `sse3` instructions. X86 processors that have `aesni` also have these instruction sets. + +## Why not use a cryptographic hash in a hashmap. + +Cryptographic hashes are designed to make is nearly impossible to find two items that collide when the attacker has full control +over the input. This has several implications: + +* They are very difficult to construct, and have to go to a lot of effort to ensure that collisions are not possible. +* They have no notion of a 'key'. Rather, they are fully deterministic and provide exactly one hash for a given input. + +For a HashMap the requirements are different. + +* Speed is very important, especially for short inputs. Often the key for a HashMap is a single `u32` or similar, and to be effective +the bucket that it should be hashed to needs to be computed in just a few CPU cycles. +* A hashmap does not need to provide a hard and fast guarantee that no two inputs will ever collide. Hence, hashCodes are not 256bits +but are just 64 or 32 bits in length. Often the first thing done with the hashcode is to truncate it further to compute which among a few buckets should be used for a key. + * Here collisions are expected, and a cheap to deal with provided there is no systematic way to generated huge numbers of values that all +go to the same bucket. + * This also means that unlike a cryptographic hash partial collisions matter. It doesn't do a hashmap any good to produce a unique 256bit hash if +the lower 12 bits are all the same. This means that even a provably irreversible hash would not offer protection from a DOS attack in a hashmap +because an attacker can easily just brute force the bottom N bits. + +From a cryptography point of view, a hashmap needs something closer to a block cypher. +Where the input can be quickly mixed in a way that cannot be reversed without knowing a key. + +# Why use aHash over X + +## SipHash + +For a hashmap: Because aHash nearly **10x** faster. + +SipHash is however useful in other contexts, such as for a HMAC, where aHash would be completely inappropriate. + +*SipHash-2-4* is designed to provide DOS attack resistance, and has no presently known attacks +against this claim that doesn't involve learning bits of the key. + +SipHash is also available in the "1-3" variant which is about twice as fast as the standard version. +The SipHash authors don't recommend using this variation when DOS attacks are a concern, but there are still no known +practical DOS attacks against the algorithm. Rust has opted for the "1-3" version as the default in `std::collections::HashMap`, +because the speed trade off of "2-4" was not worth it. + +As you can see in the table above, aHash is **much** faster than even *SipHash-1-3*, but it also provides DOS resistance, +and any attack against the accelerated form would likely involve a weakness in AES. + +## FxHash + +In terms of performance, aHash is faster than the FXhash for strings and byte arrays but not primitives. +So it might seem like using Fxhash for hashmaps when the key is a primitive is a good idea. This is *not* the case. + +When FX hash is operating on a 4 or 8 byte input such as a u32 or a u64, it reduces to multiplying the input by a fixed +constant. This is a bad hashing algorithm because it means that lower bits can never be influenced by any higher bit. In +the context of a hashmap where the low order bits are used to determine which bucket to put an item in, this isn't +any better than the identity function. Any keys that happen to end in the same bit pattern will all collide. +Some examples of where this is likely to occur are: + +* Strings encoded in base64 +* Null terminated strings (when working with C code) +* Integers that have the lower bits as zeros. (IE any multiple of small power of 2, which isn't a rare pattern in computer programs.) + * For example when taking lengths of data or locations in data it is common for values to +have a multiple of 1024, if these were used as keys in a map they will collide and end up in the same bucket. + +Like any non-keyed hash FxHash can be attacked. But FxHash is so prone to this that you may find yourself doing it accidentally. + +For example, it is possible to [accidentally introduce quadratic behavior by reading from one map in iteration order and writing to another.](https://accidentallyquadratic.tumblr.com/post/153545455987/rust-hash-iteration-reinsertion) + +Fxhash flaws make sense when you understand it for what it is. It is a quick and dirty hash, nothing more. +it was not published and promoted by its creator, it was **found**! + +Because it is error-prone, FxHash should never be used as a default. In specialized instances where the keys are understood +it makes sense, but given that aHash is faster on almost any object, it's probably not worth it. + +## FnvHash + +FnvHash is also a poor default. It only handles one byte at a time, so its performance really suffers with large inputs. +It is also non-keyed so it is still subject to DOS attacks and [accidentally quadratic behavior.](https://accidentallyquadratic.tumblr.com/post/153545455987/rust-hash-iteration-reinsertion) + +## MurmurHash, CityHash, MetroHash, FarmHash, HighwayHash, XXHash, SeaHash + +Murmur, City, Metro, Farm and Highway are all related, and appear to directly replace one another. Sea and XX are independent +and compete. + +They are all fine hashing algorithms, they do a good job of scrambling data, but they are all targeted at a different +usecase. They are intended to work in distributed systems where the hash is expected to be the same over time and from one +computer to the next, efficiently hashing large volumes of data. + +This is quite different from the needs of a Hasher used in a hashmap. In a map the typical value is under 10 bytes. None +of these algorithms scale down to handle that small of data at a competitive time. What's more the restriction that they +provide consistent output prevents them from taking advantage of different hardware capabilities on different CPUs. It makes +sense for a hashmap to work differently on a phone than on a server, or in wasm. + +If you need to persist or transmit a hash of a file, then using one of these is probably a good idea. HighwayHash seems to be the preferred solution du jour. But inside a simple Hashmap, stick with aHash. + +## AquaHash + +AquaHash is structured similarly to aHash. (Though the two were designed completely independently). AquaHash does not scale down nearly as well and +does poorly with for example a single `i32` as input. Its only implementation at this point is in C++. + +## t1ha + +T1ha is fairly fast at large sizes, and the output is of fairly high quality, but it is not clear what usecase it aims for. +It has many different versions and is very complex, and uses hardware tricks, so one might infer it is meant for +hashmaps like aHash. But any hash using it take at least **20ns**, and it doesn't outperform even SipHash until the +input sizes are larger than 128 bytes and is not designed to be DOS resistant. So uses are likely niche. + +# License + +Licensed under either of: + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. + diff --git a/third_party/rust/ahash/rustfmt.toml b/third_party/rust/ahash/rustfmt.toml new file mode 100644 index 0000000000..7530651796 --- /dev/null +++ b/third_party/rust/ahash/rustfmt.toml @@ -0,0 +1 @@ +max_width = 120 diff --git a/third_party/rust/ahash/smhasher/0001-Add-support-for-aHash.patch b/third_party/rust/ahash/smhasher/0001-Add-support-for-aHash.patch new file mode 100644 index 0000000000..99a98d380f --- /dev/null +++ b/third_party/rust/ahash/smhasher/0001-Add-support-for-aHash.patch @@ -0,0 +1,135 @@ +From 426384ce34cf410d892eeeeeb7f6046d52bff8e7 Mon Sep 17 00:00:00 2001 +From: Tom Kaitchuck +Date: Sat, 11 Jul 2020 17:15:56 -0700 +Subject: [PATCH] Add support for ahash + +--- + CMakeLists.txt | 1 + + Hashes.h | 5 +++++ + ahash.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + main.cpp | 2 +- + 4 files changed, 55 insertions(+), 1 deletion(-) + create mode 100644 ahash.h + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 6ebab1a..9d79e98 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -470,10 +470,11 @@ add_executable( + target_link_libraries( + SMHasher + SMHasherSupport + ${HIGHWAY_LIB} + ${BLAKE3_LIB} ++ libahash_c.a + ${CMAKE_THREAD_LIBS_INIT} + ) + + #add_executable( + # bittest +diff --git a/Hashes.h b/Hashes.h +index 4e111c1..fcd3e38 100644 +--- a/Hashes.h ++++ b/Hashes.h +@@ -19,10 +19,11 @@ + #if defined(__SSE4_2__) && defined(__x86_64__) + #include "metrohash/metrohash64crc.h" + #include "metrohash/metrohash128crc.h" + #endif + ++#include "ahash.h" + #include "fasthash.h" + #include "jody_hash32.h" + #include "jody_hash64.h" + + // objsize: 0-0x113 = 276 +@@ -356,10 +357,14 @@ inline void fasthash32_test ( const void * key, int len, uint32_t seed, void * o + } + #ifdef HAVE_INT64 + inline void fasthash64_test ( const void * key, int len, uint32_t seed, void * out ) { + *(uint64_t*)out = fasthash64(key, (size_t) len, (uint64_t)seed); + } ++inline void ahash64_test ( const void * key, int len, uint32_t seed, void * out ) { ++ *(uint64_t*)out = ahash64(key, (size_t) len, (uint64_t)seed); ++} ++ + #endif + + // objsize 0-778: 1912 + void mum_hash_test(const void * key, int len, uint32_t seed, void * out); + +diff --git a/ahash.h b/ahash.h +new file mode 100644 +index 0000000..6c59caf +--- /dev/null ++++ b/ahash.h +@@ -0,0 +1,48 @@ ++/* The MIT License ++ ++ Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com) ++ ++ Permission is hereby granted, free of charge, to any person ++ obtaining a copy of this software and associated documentation ++ files (the "Software"), to deal in the Software without ++ restriction, including without limitation the rights to use, copy, ++ modify, merge, publish, distribute, sublicense, and/or sell copies ++ of the Software, and to permit persons to whom the Software is ++ furnished to do so, subject to the following conditions: ++ ++ The above copyright notice and this permission notice shall be ++ included in all copies or substantial portions of the Software. ++ ++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS ++ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ++ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ++ SOFTWARE. ++*/ ++ ++#ifndef _AHASH_H ++#define _AHASH_H ++ ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/** ++ * Ahash - 64-bit implementation of aHash ++ * @buf: data buffer ++ * @len: data size ++ * @seed: the seed ++ */ ++ uint64_t ahash64(const void *buf, size_t len, uint64_t seed); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +\ No newline at end of file +diff --git a/main.cpp b/main.cpp +index 04060f2..7489aaf 100644 +--- a/main.cpp ++++ b/main.cpp +@@ -263,11 +263,11 @@ HashInfo g_hashes[] = + + { xxh3_test, 64, 0x39CD9E4A, "xxh3", "xxHash v3, 64-bit", GOOD }, + { xxh3low_test, 32, 0xFAE8467B, "xxh3low", "xxHash v3, 64-bit, low 32-bits part", GOOD }, + { xxh128_test, 128, 0xEB61B3A0, "xxh128", "xxHash v3, 128-bit", GOOD }, + { xxh128low_test, 64, 0x54D1CC70, "xxh128low", "xxHash v3, 128-bit, low 64-bits part", GOOD }, +- ++ { ahash64_test, 64, 0x00000000, "ahash64", "ahash 64bit", GOOD }, //Expected value set to zero because aHash does not adhere to a fixed output. + #if __WORDSIZE >= 64 + # define TIFU_VERIF 0x644236D4 + #else + // broken on certain travis + # define TIFU_VERIF 0x0 +-- +2.25.1 + diff --git a/third_party/rust/ahash/smhasher/0002-Add-support-for-aHash.patch b/third_party/rust/ahash/smhasher/0002-Add-support-for-aHash.patch new file mode 100644 index 0000000000..93ccbff47a --- /dev/null +++ b/third_party/rust/ahash/smhasher/0002-Add-support-for-aHash.patch @@ -0,0 +1,269 @@ +From 426384ce34cf410d892eeeeeb7f6046d52bff8e7 Mon Sep 17 00:00:00 2001 +From: Tom Kaitchuck +Date: Sat, 11 Jul 2020 17:15:56 -0700 +Subject: [PATCH] Add support for ahash + +--- + CMakeLists.txt | 1 + + Hashes.h | 5 +++++ + ahash.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + main.cpp | 2 +- + 4 files changed, 55 insertions(+), 1 deletion(-) + create mode 100644 ahash.h + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 6ebab1a..9d79e98 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -470,10 +470,11 @@ add_executable( + target_link_libraries( + SMHasher + SMHasherSupport + ${HIGHWAY_LIB} + ${BLAKE3_LIB} ++ libahash_c.a + ${CMAKE_THREAD_LIBS_INIT} + ) + + #add_executable( + # bittest +diff --git a/Hashes.h b/Hashes.h +index 4e111c1..fcd3e38 100644 +--- a/Hashes.h ++++ b/Hashes.h +@@ -19,10 +19,11 @@ + #if defined(__SSE4_2__) && defined(__x86_64__) + #include "metrohash/metrohash64crc.h" + #include "metrohash/metrohash128crc.h" + #endif + ++#include "ahash.h" + #include "fasthash.h" + #include "jody_hash32.h" + #include "jody_hash64.h" + + // objsize: 0-0x113 = 276 +@@ -356,10 +357,14 @@ inline void fasthash32_test ( const void * key, int len, uint32_t seed, void * o + } + #ifdef HAVE_INT64 + inline void fasthash64_test ( const void * key, int len, uint32_t seed, void * out ) { + *(uint64_t*)out = fasthash64(key, (size_t) len, (uint64_t)seed); + } ++inline void ahash64_test ( const void * key, int len, uint32_t seed, void * out ) { ++ *(uint64_t*)out = ahash64(key, (size_t) len, (uint64_t)seed); ++} ++ + #endif + + // objsize 0-778: 1912 + void mum_hash_test(const void * key, int len, uint32_t seed, void * out); + +diff --git a/ahash.h b/ahash.h +new file mode 100644 +index 0000000..6c59caf +--- /dev/null ++++ b/ahash.h +@@ -0,0 +1,48 @@ ++/* The MIT License ++ ++ Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com) ++ ++ Permission is hereby granted, free of charge, to any person ++ obtaining a copy of this software and associated documentation ++ files (the "Software"), to deal in the Software without ++ restriction, including without limitation the rights to use, copy, ++ modify, merge, publish, distribute, sublicense, and/or sell copies ++ of the Software, and to permit persons to whom the Software is ++ furnished to do so, subject to the following conditions: ++ ++ The above copyright notice and this permission notice shall be ++ included in all copies or substantial portions of the Software. ++ ++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS ++ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ++ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ++ SOFTWARE. ++*/ ++ ++#ifndef _AHASH_H ++#define _AHASH_H ++ ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/** ++ * Ahash - 64-bit implementation of aHash ++ * @buf: data buffer ++ * @len: data size ++ * @seed: the seed ++ */ ++ uint64_t ahash64(const void *buf, size_t len, uint64_t seed); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +\ No newline at end of file +diff --git a/main.cpp b/main.cpp +index 04060f2..7489aaf 100644 +--- a/main.cpp ++++ b/main.cpp +@@ -263,11 +263,11 @@ HashInfo g_hashes[] = + + { xxh3_test, 64, 0x39CD9E4A, "xxh3", "xxHash v3, 64-bit", GOOD }, + { xxh3low_test, 32, 0xFAE8467B, "xxh3low", "xxHash v3, 64-bit, low 32-bits part", GOOD }, + { xxh128_test, 128, 0xEB61B3A0, "xxh128", "xxHash v3, 128-bit", GOOD }, + { xxh128low_test, 64, 0x54D1CC70, "xxh128low", "xxHash v3, 128-bit, low 64-bits part", GOOD }, +- ++ { ahash64_test, 64, 0x00000000, "ahash64", "ahash 64bit", GOOD }, //Expected value set to zero because aHash does not adhere to a fixed output. + #if __WORDSIZE >= 64 + # define TIFU_VERIF 0x644236D4 + #else + // broken on certain travis + # define TIFU_VERIF 0x0 +-- +2.25.1 + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index e4658a7..efef724 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -630,20 +630,21 @@ if(ipo_supported) + set_property(TARGET SMHasherSupport PROPERTY INTERPROCEDURAL_OPTIMIZATION + True) + set_property(TARGET SMHasher PROPERTY INTERPROCEDURAL_OPTIMIZATION True) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DLTO") + # set_source_files_properties(main.cpp PROPERTIES COMPILE_FLAGS "-DLTO") + else() + message(STATUS "IPO / LTO not supported: <${error}>") + endif() + + target_link_libraries(SMHasher SMHasherSupport ${HIGHWAY_LIB} ${BLAKE3_LIB} ++ libahash_c.a + ${CMAKE_THREAD_LIBS_INIT}) + + # add_executable( bittest bittest.cpp ) + # + # target_link_libraries( bittest SMHasherSupport ${CMAKE_THREAD_LIBS_INIT} ) + + if(NOT (CMAKE_CROSSCOMPILING)) + enable_testing() + add_test(VerifyAll SMHasher --test=VerifyAll) + add_test(Sanity SMHasher --test=Sanity) +diff --git a/Hashes.h b/Hashes.h +index f795403..036b49b 100644 +--- a/Hashes.h ++++ b/Hashes.h +@@ -14,20 +14,21 @@ + #include "metrohash/metrohash64.h" + #include "metrohash/metrohash128.h" + #include "cmetrohash.h" + #include "opt_cmetrohash.h" + + #if defined(__SSE4_2__) && defined(__x86_64__) + #include "metrohash/metrohash64crc.h" + #include "metrohash/metrohash128crc.h" + #endif + ++#include "ahash.h" + #include "fasthash.h" + #include "jody_hash32.h" + #include "jody_hash64.h" + + // objsize: 0-0x113 = 276 + #include "tifuhash.h" + // objsize: 5f0-85f = 623 + #include "floppsyhash.h" + + #include "vmac.h" +@@ -353,20 +354,24 @@ inline void cmetrohash64_2_test ( const void * key, int len, uint32_t seed, void + } + #endif + + inline void fasthash32_test ( const void * key, int len, uint32_t seed, void * out ) { + *(uint32_t*)out = fasthash32(key, (size_t) len, seed); + } + #ifdef HAVE_INT64 + inline void fasthash64_test ( const void * key, int len, uint32_t seed, void * out ) { + *(uint64_t*)out = fasthash64(key, (size_t) len, (uint64_t)seed); + } ++ ++inline void ahash64_test ( const void * key, int len, uint32_t seed, void * out ) { ++ *(uint64_t*)out = ahash64(key, (size_t) len, (uint64_t)seed); ++} + #endif + + // objsize 0-778: 1912 + void mum_hash_test(const void * key, int len, uint32_t seed, void * out); + + inline void mum_low_test ( const void * key, int len, uint32_t seed, void * out ) { + uint64_t result; + mum_hash_test(key, len, seed, &result); + *(uint32_t*)out = (uint32_t)result; + } +diff --git a/ahash.h b/ahash.h +new file mode 100644 +index 0000000..2ed416d +--- /dev/null ++++ b/ahash.h +@@ -0,0 +1,24 @@ ++ ++#ifndef _AHASH_H ++#define _AHASH_H ++ ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/** ++ * Ahash - 64-bit implementation of aHash ++ * @buf: data buffer ++ * @len: data size ++ * @seed: the seed ++ */ ++ uint64_t ahash64(const void *buf, size_t len, uint64_t seed); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +\ No newline at end of file +diff --git a/main.cpp b/main.cpp +index f742fbf..c221f7d 100644 +--- a/main.cpp ++++ b/main.cpp +@@ -434,20 +434,21 @@ HashInfo g_hashes[] = + { t1ha0_ia32aes_avx1_test, 64, 0xF07C4DA5, "t1ha0_aes_avx1", "Fast Positive Hash (machine-specific, requires AES-NI & AVX)", GOOD }, + # endif /* __AVX__ */ + # if defined(__AVX2__) + { t1ha0_ia32aes_avx2_test, 64, 0x8B38C599, "t1ha0_aes_avx2", "Fast Positive Hash (machine-specific, requires AES-NI & AVX2)", GOOD }, + # endif /* __AVX2__ */ + #endif /* T1HA0_AESNI_AVAILABLE */ + { xxh3_test, 64, 0x39CD9E4A, "xxh3", "xxHash v3, 64-bit", GOOD }, + { xxh3low_test, 32, 0xFAE8467B, "xxh3low", "xxHash v3, 64-bit, low 32-bits part", GOOD }, + { xxh128_test, 128, 0xEB61B3A0, "xxh128", "xxHash v3, 128-bit", GOOD }, + { xxh128low_test, 64, 0x54D1CC70, "xxh128low", "xxHash v3, 128-bit, low 64-bits part", GOOD }, ++ { ahash64_test, 64, 0x00000000, "ahash64", "ahash 64bit", GOOD }, //Expected value set to zero because aHash does not adhere to a fixed output. + #ifdef HAVE_BIT32 + { wyhash32_test, 32, 0x09DE8066, "wyhash32", "wyhash (32-bit)", GOOD }, + #else + { wyhash32low, 32, 0x9241B8A3, "wyhash32low", "wyhash lower 32bit", GOOD }, + #endif + #ifdef HAVE_INT64 + { wyhash_test, 64, 0x7C62138D, "wyhash", "wyhash (64-bit)", GOOD }, + #endif + + }; diff --git a/third_party/rust/ahash/smhasher/ahashOutput.txt b/third_party/rust/ahash/smhasher/ahashOutput.txt new file mode 100644 index 0000000000..3bc122bfec --- /dev/null +++ b/third_party/rust/ahash/smhasher/ahashOutput.txt @@ -0,0 +1,1516 @@ +------------------------------------------------------------------------------- +--- Testing ahash64 "ahash 64bit" GOOD + +[[[ Sanity Tests ]]] + +Verification value 0x84A46E17 ....... SKIP (self- or unseeded) +Running sanity check 1 .......... PASS +Running AppendedZeroesTest .......... PASS + +[[[ Speed Tests ]]] + +Bulk speed test - 262144-byte keys +Alignment 7 - 8.351 bytes/cycle - 23891.85 MiB/sec @ 3 ghz +Alignment 6 - 8.327 bytes/cycle - 23823.64 MiB/sec @ 3 ghz +Alignment 5 - 8.312 bytes/cycle - 23780.76 MiB/sec @ 3 ghz +Alignment 4 - 8.309 bytes/cycle - 23772.79 MiB/sec @ 3 ghz +Alignment 3 - 8.315 bytes/cycle - 23790.37 MiB/sec @ 3 ghz +Alignment 2 - 8.339 bytes/cycle - 23858.92 MiB/sec @ 3 ghz +Alignment 1 - 8.320 bytes/cycle - 23804.48 MiB/sec @ 3 ghz +Alignment 0 - 8.364 bytes/cycle - 23930.33 MiB/sec @ 3 ghz +Average - 8.330 bytes/cycle - 23831.64 MiB/sec @ 3 ghz + +Small key speed test - 1-byte keys - 11.98 cycles/hash +Small key speed test - 2-byte keys - 12.95 cycles/hash +Small key speed test - 3-byte keys - 13.00 cycles/hash +Small key speed test - 4-byte keys - 14.00 cycles/hash +Small key speed test - 5-byte keys - 14.28 cycles/hash +Small key speed test - 6-byte keys - 13.98 cycles/hash +Small key speed test - 7-byte keys - 14.24 cycles/hash +Small key speed test - 8-byte keys - 14.28 cycles/hash +Small key speed test - 9-byte keys - 14.18 cycles/hash +Small key speed test - 10-byte keys - 14.29 cycles/hash +Small key speed test - 11-byte keys - 14.12 cycles/hash +Small key speed test - 12-byte keys - 14.00 cycles/hash +Small key speed test - 13-byte keys - 14.00 cycles/hash +Small key speed test - 14-byte keys - 14.00 cycles/hash +Small key speed test - 15-byte keys - 14.23 cycles/hash +Small key speed test - 16-byte keys - 14.00 cycles/hash +Small key speed test - 17-byte keys - 16.05 cycles/hash +Small key speed test - 18-byte keys - 16.14 cycles/hash +Small key speed test - 19-byte keys - 16.00 cycles/hash +Small key speed test - 20-byte keys - 16.17 cycles/hash +Small key speed test - 21-byte keys - 16.00 cycles/hash +Small key speed test - 22-byte keys - 16.07 cycles/hash +Small key speed test - 23-byte keys - 16.13 cycles/hash +Small key speed test - 24-byte keys - 15.99 cycles/hash +Small key speed test - 25-byte keys - 16.12 cycles/hash +Small key speed test - 26-byte keys - 15.99 cycles/hash +Small key speed test - 27-byte keys - 16.00 cycles/hash +Small key speed test - 28-byte keys - 16.30 cycles/hash +Small key speed test - 29-byte keys - 18.11 cycles/hash +Small key speed test - 30-byte keys - 18.52 cycles/hash +Small key speed test - 31-byte keys - 17.98 cycles/hash +Average 15.132 cycles/hash + +[[[ 'Hashmap' Speed Tests ]]] + +std::unordered_map +Init std HashMapTest: 270.009 cycles/op (102401 inserts, 1% deletions) +Running std HashMapTest: 120.593 cycles/op (3.6 stdv) + +greg7mdp/parallel-hashmap +Init fast HashMapTest: 110.896 cycles/op (102401 inserts, 1% deletions) +Running fast HashMapTest: 81.841 cycles/op (0.1 stdv) ....... PASS + +[[[ Avalanche Tests ]]] + +Testing 24-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.702667% +Testing 32-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.670667% +Testing 40-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.608667% +Testing 48-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.670667% +Testing 56-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.670000% +Testing 64-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.663333% +Testing 72-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.780667% +Testing 80-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.716000% +Testing 96-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.621333% +Testing 112-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.642667% +Testing 128-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.629333% +Testing 160-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.747333% +Testing 512-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.757333% +Testing 1024-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.820000% + +[[[ Keyset 'Sparse' Tests ]]] + +Keyset 'Sparse' - 16-bit keys with up to 9 bits set - 50643 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 0.3, actual 1 (3.35x) (1) ! +Testing collisions (high 19-25 bits) - Worst is 24 bits: 90/76 (1.18x) +Testing collisions (high 12-bit) - Expected 46547.0, actual 46547 (1.00x) +Testing collisions (high 8-bit) - Expected 50387.0, actual 50387 (1.00x) +Testing collisions (low 32-bit) - Expected 0.3, actual 0 (0.00x) +Testing collisions (low 19-25 bits) - Worst is 25 bits: 39/38 (1.02x) +Testing collisions (low 12-bit) - Expected 46547.0, actual 46547 (1.00x) +Testing collisions (low 8-bit) - Expected 50387.0, actual 50387 (1.00x) +Testing distribution - Worst bias is the 13-bit window at bit 55 - 0.572% + +Keyset 'Sparse' - 24-bit keys with up to 8 bits set - 1271626 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 188.2, actual 174 (0.92x) +Testing collisions (high 24-35 bits) - Worst is 26 bits: 12182/12047 (1.01x) +Testing collisions (high 12-bit) - Expected 1267530.0, actual 1267530 (1.00x) +Testing collisions (high 8-bit) - Expected 1271370.0, actual 1271370 (1.00x) +Testing collisions (low 32-bit) - Expected 188.2, actual 201 (1.07x) (13) +Testing collisions (low 24-35 bits) - Worst is 32 bits: 201/188 (1.07x) +Testing collisions (low 12-bit) - Expected 1267530.0, actual 1267530 (1.00x) +Testing collisions (low 8-bit) - Expected 1271370.0, actual 1271370 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 53 - 0.082% + +Keyset 'Sparse' - 32-bit keys with up to 7 bits set - 4514873 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 2373.0, actual 2366 (1.00x) (-7) +Testing collisions (high 25-38 bits) - Worst is 30 bits: 9627/9492 (1.01x) +Testing collisions (high 12-bit) - Expected 4510777.0, actual 4510777 (1.00x) +Testing collisions (high 8-bit) - Expected 4514617.0, actual 4514617 (1.00x) +Testing collisions (low 32-bit) - Expected 2373.0, actual 2295 (0.97x) +Testing collisions (low 25-38 bits) - Worst is 30 bits: 9493/9492 (1.00x) +Testing collisions (low 12-bit) - Expected 4510777.0, actual 4510777 (1.00x) +Testing collisions (low 8-bit) - Expected 4514617.0, actual 4514617 (1.00x) +Testing distribution - Worst bias is the 19-bit window at bit 12 - 0.048% + +Keyset 'Sparse' - 40-bit keys with up to 6 bits set - 4598479 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 2461.7, actual 2561 (1.04x) (100) +Testing collisions (high 25-38 bits) - Worst is 32 bits: 2561/2461 (1.04x) +Testing collisions (high 12-bit) - Expected 4594383.0, actual 4594383 (1.00x) +Testing collisions (high 8-bit) - Expected 4598223.0, actual 4598223 (1.00x) +Testing collisions (low 32-bit) - Expected 2461.7, actual 2444 (0.99x) (-17) +Testing collisions (low 25-38 bits) - Worst is 35 bits: 323/307 (1.05x) +Testing collisions (low 12-bit) - Expected 4594383.0, actual 4594383 (1.00x) +Testing collisions (low 8-bit) - Expected 4598223.0, actual 4598223 (1.00x) +Testing distribution - Worst bias is the 19-bit window at bit 2 - 0.056% + +Keyset 'Sparse' - 48-bit keys with up to 6 bits set - 14196869 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 23463.6, actual 23431 (1.00x) (-32) +Testing collisions (high 27-42 bits) - Worst is 37 bits: 747/733 (1.02x) +Testing collisions (high 12-bit) - Expected 14192773.0, actual 14192773 (1.00x) +Testing collisions (high 8-bit) - Expected 14196613.0, actual 14196613 (1.00x) +Testing collisions (low 32-bit) - Expected 23463.6, actual 23284 (0.99x) (-179) +Testing collisions (low 27-42 bits) - Worst is 41 bits: 51/45 (1.11x) +Testing collisions (low 12-bit) - Expected 14192773.0, actual 14192773 (1.00x) +Testing collisions (low 8-bit) - Expected 14196613.0, actual 14196613 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 44 - 0.029% + +Keyset 'Sparse' - 56-bit keys with up to 5 bits set - 4216423 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 2069.7, actual 2053 (0.99x) (-16) +Testing collisions (high 25-38 bits) - Worst is 31 bits: 4136/4139 (1.00x) +Testing collisions (high 12-bit) - Expected 4212327.0, actual 4212327 (1.00x) +Testing collisions (high 8-bit) - Expected 4216167.0, actual 4216167 (1.00x) +Testing collisions (low 32-bit) - Expected 2069.7, actual 2058 (0.99x) (-11) +Testing collisions (low 25-38 bits) - Worst is 30 bits: 8320/8278 (1.00x) +Testing collisions (low 12-bit) - Expected 4212327.0, actual 4212327 (1.00x) +Testing collisions (low 8-bit) - Expected 4216167.0, actual 4216167 (1.00x) +Testing distribution - Worst bias is the 18-bit window at bit 26 - 0.051% + +Keyset 'Sparse' - 64-bit keys with up to 5 bits set - 8303633 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8026.9, actual 8024 (1.00x) (-2) +Testing collisions (high 26-40 bits) - Worst is 37 bits: 267/250 (1.06x) +Testing collisions (high 12-bit) - Expected 8299537.0, actual 8299537 (1.00x) +Testing collisions (high 8-bit) - Expected 8303377.0, actual 8303377 (1.00x) +Testing collisions (low 32-bit) - Expected 8026.9, actual 8006 (1.00x) (-20) +Testing collisions (low 26-40 bits) - Worst is 40 bits: 34/31 (1.08x) +Testing collisions (low 12-bit) - Expected 8299537.0, actual 8299537 (1.00x) +Testing collisions (low 8-bit) - Expected 8303377.0, actual 8303377 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 25 - 0.041% + +Keyset 'Sparse' - 72-bit keys with up to 5 bits set - 15082603 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 26482.7, actual 26443 (1.00x) (-39) +Testing collisions (high 27-42 bits) - Worst is 42 bits: 33/25 (1.28x) +Testing collisions (high 12-bit) - Expected 15078507.0, actual 15078507 (1.00x) +Testing collisions (high 8-bit) - Expected 15082347.0, actual 15082347 (1.00x) +Testing collisions (low 32-bit) - Expected 26482.7, actual 26586 (1.00x) (104) +Testing collisions (low 27-42 bits) - Worst is 42 bits: 29/25 (1.12x) +Testing collisions (low 12-bit) - Expected 15078507.0, actual 15078507 (1.00x) +Testing collisions (low 8-bit) - Expected 15082347.0, actual 15082347 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 31 - 0.033% + +Keyset 'Sparse' - 96-bit keys with up to 4 bits set - 3469497 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1401.3, actual 1457 (1.04x) (56) +Testing collisions (high 25-38 bits) - Worst is 32 bits: 1457/1401 (1.04x) +Testing collisions (high 12-bit) - Expected 3465401.0, actual 3465401 (1.00x) +Testing collisions (high 8-bit) - Expected 3469241.0, actual 3469241 (1.00x) +Testing collisions (low 32-bit) - Expected 1401.3, actual 1390 (0.99x) (-11) +Testing collisions (low 25-38 bits) - Worst is 38 bits: 26/21 (1.19x) +Testing collisions (low 12-bit) - Expected 3465401.0, actual 3465401 (1.00x) +Testing collisions (low 8-bit) - Expected 3469241.0, actual 3469241 (1.00x) +Testing distribution - Worst bias is the 19-bit window at bit 52 - 0.070% + +Keyset 'Sparse' - 160-bit keys with up to 4 bits set - 26977161 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 84723.3, actual 84650 (1.00x) (-73) +Testing collisions (high 28-44 bits) - Worst is 40 bits: 336/330 (1.02x) +Testing collisions (high 12-bit) - Expected 26973065.0, actual 26973065 (1.00x) +Testing collisions (high 8-bit) - Expected 26976905.0, actual 26976905 (1.00x) +Testing collisions (low 32-bit) - Expected 84723.3, actual 84029 (0.99x) (-694) +Testing collisions (low 28-44 bits) - Worst is 37 bits: 2744/2647 (1.04x) +Testing collisions (low 12-bit) - Expected 26973065.0, actual 26973065 (1.00x) +Testing collisions (low 8-bit) - Expected 26976905.0, actual 26976905 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 57 - 0.013% + +Keyset 'Sparse' - 256-bit keys with up to 3 bits set - 2796417 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 910.4, actual 903 (0.99x) (-7) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 29/28 (1.02x) +Testing collisions (high 12-bit) - Expected 2792321.0, actual 2792321 (1.00x) +Testing collisions (high 8-bit) - Expected 2796161.0, actual 2796161 (1.00x) +Testing collisions (low 32-bit) - Expected 910.4, actual 855 (0.94x) +Testing collisions (low 25-37 bits) - Worst is 28 bits: 14588/14565 (1.00x) +Testing collisions (low 12-bit) - Expected 2792321.0, actual 2792321 (1.00x) +Testing collisions (low 8-bit) - Expected 2796161.0, actual 2796161 (1.00x) +Testing distribution - Worst bias is the 19-bit window at bit 7 - 0.112% + +Keyset 'Sparse' - 512-bit keys with up to 3 bits set - 22370049 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 58256.4, actual 58578 (1.01x) (322) +Testing collisions (high 28-43 bits) - Worst is 43 bits: 32/28 (1.12x) +Testing collisions (high 12-bit) - Expected 22365953.0, actual 22365953 (1.00x) +Testing collisions (high 8-bit) - Expected 22369793.0, actual 22369793 (1.00x) +Testing collisions (low 32-bit) - Expected 58256.4, actual 58543 (1.00x) (287) +Testing collisions (low 28-43 bits) - Worst is 36 bits: 3712/3641 (1.02x) +Testing collisions (low 12-bit) - Expected 22365953.0, actual 22365953 (1.00x) +Testing collisions (low 8-bit) - Expected 22369793.0, actual 22369793 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 43 - 0.015% + +Keyset 'Sparse' - 1024-bit keys with up to 2 bits set - 524801 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 32.1, actual 35 (1.09x) (3) +Testing collisions (high 22-32 bits) - Worst is 32 bits: 35/32 (1.09x) +Testing collisions (high 12-bit) - Expected 520705.0, actual 520705 (1.00x) +Testing collisions (high 8-bit) - Expected 524545.0, actual 524545 (1.00x) +Testing collisions (low 32-bit) - Expected 32.1, actual 38 (1.19x) (6) +Testing collisions (low 22-32 bits) - Worst is 32 bits: 38/32 (1.19x) +Testing collisions (low 12-bit) - Expected 520705.0, actual 520705 (1.00x) +Testing collisions (low 8-bit) - Expected 524545.0, actual 524545 (1.00x) +Testing distribution - Worst bias is the 16-bit window at bit 17 - 0.142% + +Keyset 'Sparse' - 2048-bit keys with up to 2 bits set - 2098177 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.5, actual 488 (0.95x) +Testing collisions (high 24-36 bits) - Worst is 30 bits: 2050/2050 (1.00x) +Testing collisions (high 12-bit) - Expected 2094081.0, actual 2094081 (1.00x) +Testing collisions (high 8-bit) - Expected 2097921.0, actual 2097921 (1.00x) +Testing collisions (low 32-bit) - Expected 512.5, actual 523 (1.02x) (11) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 147/128 (1.15x) +Testing collisions (low 12-bit) - Expected 2094081.0, actual 2094081 (1.00x) +Testing collisions (low 8-bit) - Expected 2097921.0, actual 2097921 (1.00x) +Testing distribution - Worst bias is the 18-bit window at bit 57 - 0.080% + + +[[[ Keyset 'Permutation' Tests ]]] + +Combination Lowbits Tests: +Keyset 'Combination' - up to 7 blocks from a set of 8 - 2396744 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 668.7, actual 645 (0.96x) +Testing collisions (high 25-37 bits) - Worst is 35 bits: 94/83 (1.12x) +Testing collisions (high 12-bit) - Expected 2392648.0, actual 2392648 (1.00x) +Testing collisions (high 8-bit) - Expected 2396488.0, actual 2396488 (1.00x) +Testing collisions (low 32-bit) - Expected 668.7, actual 693 (1.04x) (25) +Testing collisions (low 25-37 bits) - Worst is 35 bits: 98/83 (1.17x) +Testing collisions (low 12-bit) - Expected 2392648.0, actual 2392648 (1.00x) +Testing collisions (low 8-bit) - Expected 2396488.0, actual 2396488 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 45 - 0.076% + + +Combination Highbits Tests +Keyset 'Combination' - up to 7 blocks from a set of 8 - 2396744 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 668.7, actual 682 (1.02x) (14) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 26/20 (1.24x) +Testing collisions (high 12-bit) - Expected 2392648.0, actual 2392648 (1.00x) +Testing collisions (high 8-bit) - Expected 2396488.0, actual 2396488 (1.00x) +Testing collisions (low 32-bit) - Expected 668.7, actual 690 (1.03x) (22) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 47/41 (1.12x) +Testing collisions (low 12-bit) - Expected 2392648.0, actual 2392648 (1.00x) +Testing collisions (low 8-bit) - Expected 2396488.0, actual 2396488 (1.00x) +Testing distribution - Worst bias is the 18-bit window at bit 13 - 0.059% + + +Combination Hi-Lo Tests: +Keyset 'Combination' - up to 6 blocks from a set of 15 - 12204240 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 17339.3, actual 17195 (0.99x) (-144) +Testing collisions (high 27-41 bits) - Worst is 40 bits: 72/67 (1.06x) +Testing collisions (high 12-bit) - Expected 12200144.0, actual 12200144 (1.00x) +Testing collisions (high 8-bit) - Expected 12203984.0, actual 12203984 (1.00x) +Testing collisions (low 32-bit) - Expected 17339.3, actual 17096 (0.99x) (-243) +Testing collisions (low 27-41 bits) - Worst is 41 bits: 36/33 (1.06x) +Testing collisions (low 12-bit) - Expected 12200144.0, actual 12200144 (1.00x) +Testing collisions (low 8-bit) - Expected 12203984.0, actual 12203984 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 48 - 0.026% + + +Combination 0x8000000 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8190 (1.00x) (-1) +Testing collisions (high 26-40 bits) - Worst is 40 bits: 43/31 (1.34x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8149 (0.99x) (-42) +Testing collisions (low 26-40 bits) - Worst is 37 bits: 272/255 (1.06x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 54 - 0.052% + + +Combination 0x0000001 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8262 (1.01x) (71) +Testing collisions (high 26-40 bits) - Worst is 39 bits: 71/63 (1.11x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8167 (1.00x) (-24) +Testing collisions (low 26-40 bits) - Worst is 34 bits: 2066/2047 (1.01x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 2 - 0.049% + + +Combination 0x800000000000000 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8224 (1.00x) (33) +Testing collisions (high 26-40 bits) - Worst is 40 bits: 38/31 (1.19x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8182 (1.00x) (-9) +Testing collisions (low 26-40 bits) - Worst is 30 bits: 32790/32767 (1.00x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 37 - 0.045% + + +Combination 0x000000000000001 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8173 (1.00x) (-18) +Testing collisions (high 26-40 bits) - Worst is 34 bits: 2054/2047 (1.00x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8313 (1.01x) (122) +Testing collisions (low 26-40 bits) - Worst is 40 bits: 37/31 (1.16x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 20 - 0.037% + + +Combination 16-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8316 (1.02x) (125) +Testing collisions (high 26-40 bits) - Worst is 36 bits: 545/511 (1.06x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8278 (1.01x) (87) +Testing collisions (low 26-40 bits) - Worst is 40 bits: 36/31 (1.13x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 22 - 0.041% + + +Combination 16-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8106 (0.99x) (-85) +Testing collisions (high 26-40 bits) - Worst is 38 bits: 142/127 (1.11x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8250 (1.01x) (59) +Testing collisions (low 26-40 bits) - Worst is 39 bits: 75/63 (1.17x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 28 - 0.039% + + +Combination 32-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8337 (1.02x) (146) +Testing collisions (high 26-40 bits) - Worst is 40 bits: 35/31 (1.09x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8248 (1.01x) (57) +Testing collisions (low 26-40 bits) - Worst is 40 bits: 37/31 (1.16x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 25 - 0.038% + + +Combination 32-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8227 (1.00x) (36) +Testing collisions (high 26-40 bits) - Worst is 35 bits: 1047/1023 (1.02x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8191 (1.00x) +Testing collisions (low 26-40 bits) - Worst is 39 bits: 73/63 (1.14x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 35 - 0.043% + + +Combination 64-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8221 (1.00x) (30) +Testing collisions (high 26-40 bits) - Worst is 39 bits: 73/63 (1.14x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8060 (0.98x) (-131) +Testing collisions (low 26-40 bits) - Worst is 29 bits: 65173/65535 (0.99x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 8 - 0.060% + + +Combination 64-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8254 (1.01x) (63) +Testing collisions (high 26-40 bits) - Worst is 38 bits: 137/127 (1.07x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8101 (0.99x) (-90) +Testing collisions (low 26-40 bits) - Worst is 37 bits: 260/255 (1.02x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 41 - 0.040% + + +Combination 128-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8141 (0.99x) (-50) +Testing collisions (high 26-40 bits) - Worst is 40 bits: 33/31 (1.03x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8340 (1.02x) (149) +Testing collisions (low 26-40 bits) - Worst is 40 bits: 38/31 (1.19x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 39 - 0.062% + + +Combination 128-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 8192.0, actual 8309 (1.01x) (118) +Testing collisions (high 26-40 bits) - Worst is 37 bits: 275/255 (1.07x) +Testing collisions (high 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (high 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing collisions (low 32-bit) - Expected 8192.0, actual 8176 (1.00x) (-15) +Testing collisions (low 26-40 bits) - Worst is 36 bits: 561/511 (1.10x) +Testing collisions (low 12-bit) - Expected 8384510.0, actual 8384510 (1.00x) +Testing collisions (low 8-bit) - Expected 8388350.0, actual 8388350 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 17 - 0.032% + + +[[[ Keyset 'Window' Tests ]]] + +Keyset 'Window' - 32-bit key, 25-bit window - 32 tests, 33554432 keys per test +Window at 0 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 1 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 2 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 3 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 4 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 5 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 6 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 7 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 8 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 9 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 10 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 11 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 12 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 13 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 14 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 15 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 16 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 17 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 18 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 19 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 20 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 21 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 22 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 23 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 24 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 25 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 26 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 27 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 28 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 29 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 30 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 31 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 32 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) + +[[[ Keyset 'Cyclic' Tests ]]] + +Keyset 'Cyclic' - 8 cycles of 8 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 116.4, actual 119 (1.02x) (3) +Testing collisions (high 23-34 bits) - Worst is 32 bits: 119/116 (1.02x) +Testing collisions (high 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (high 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing collisions (low 32-bit) - Expected 116.4, actual 118 (1.01x) (2) +Testing collisions (low 23-34 bits) - Worst is 30 bits: 476/465 (1.02x) +Testing collisions (low 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (low 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 44 - 0.134% + +Keyset 'Cyclic' - 8 cycles of 9 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 116.4, actual 127 (1.09x) (11) +Testing collisions (high 23-34 bits) - Worst is 31 bits: 262/232 (1.13x) +Testing collisions (high 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (high 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing collisions (low 32-bit) - Expected 116.4, actual 112 (0.96x) +Testing collisions (low 23-34 bits) - Worst is 34 bits: 33/29 (1.13x) +Testing collisions (low 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (low 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 32 - 0.141% + +Keyset 'Cyclic' - 8 cycles of 10 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 116.4, actual 131 (1.13x) (15) +Testing collisions (high 23-34 bits) - Worst is 34 bits: 33/29 (1.13x) +Testing collisions (high 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (high 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing collisions (low 32-bit) - Expected 116.4, actual 109 (0.94x) +Testing collisions (low 23-34 bits) - Worst is 34 bits: 37/29 (1.27x) +Testing collisions (low 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (low 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 16 - 0.122% + +Keyset 'Cyclic' - 8 cycles of 11 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 116.4, actual 104 (0.89x) +Testing collisions (high 23-34 bits) - Worst is 34 bits: 31/29 (1.07x) +Testing collisions (high 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (high 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing collisions (low 32-bit) - Expected 116.4, actual 124 (1.07x) (8) +Testing collisions (low 23-34 bits) - Worst is 34 bits: 34/29 (1.17x) +Testing collisions (low 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (low 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 47 - 0.137% + +Keyset 'Cyclic' - 8 cycles of 12 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 116.4, actual 113 (0.97x) +Testing collisions (high 23-34 bits) - Worst is 28 bits: 1877/1862 (1.01x) +Testing collisions (high 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (high 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing collisions (low 32-bit) - Expected 116.4, actual 113 (0.97x) +Testing collisions (low 23-34 bits) - Worst is 34 bits: 38/29 (1.31x) +Testing collisions (low 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (low 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 53 - 0.109% + +Keyset 'Cyclic' - 8 cycles of 16 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 116.4, actual 122 (1.05x) (6) +Testing collisions (high 23-34 bits) - Worst is 34 bits: 31/29 (1.07x) +Testing collisions (high 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (high 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing collisions (low 32-bit) - Expected 116.4, actual 112 (0.96x) +Testing collisions (low 23-34 bits) - Worst is 33 bits: 62/58 (1.07x) +Testing collisions (low 12-bit) - Expected 995904.0, actual 995904 (1.00x) +Testing collisions (low 8-bit) - Expected 999744.0, actual 999744 (1.00x) +Testing distribution - Worst bias is the 17-bit window at bit 37 - 0.086% + + +[[[ Keyset 'TwoBytes' Tests ]]] + +Keyset 'TwoBytes' - up-to-4-byte keys, 652545 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 49.6, actual 61 (1.23x) (12) +Testing collisions (high 23-33 bits) - Worst is 33 bits: 31/24 (1.25x) +Testing collisions (high 12-bit) - Expected 648449.0, actual 648449 (1.00x) +Testing collisions (high 8-bit) - Expected 652289.0, actual 652289 (1.00x) +Testing collisions (low 32-bit) - Expected 49.6, actual 53 (1.07x) (4) +Testing collisions (low 23-33 bits) - Worst is 32 bits: 53/49 (1.07x) +Testing collisions (low 12-bit) - Expected 648449.0, actual 648449 (1.00x) +Testing collisions (low 8-bit) - Expected 652289.0, actual 652289 (1.00x) +Testing distribution - Worst bias is the 15-bit window at bit 54 - 0.125% + +Keyset 'TwoBytes' - up-to-8-byte keys, 5471025 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 3484.6, actual 3456 (0.99x) (-28) +Testing collisions (high 26-39 bits) - Worst is 34 bits: 878/871 (1.01x) +Testing collisions (high 12-bit) - Expected 5466929.0, actual 5466929 (1.00x) +Testing collisions (high 8-bit) - Expected 5470769.0, actual 5470769 (1.00x) +Testing collisions (low 32-bit) - Expected 3484.6, actual 3430 (0.98x) (-54) +Testing collisions (low 26-39 bits) - Worst is 35 bits: 441/435 (1.01x) +Testing collisions (low 12-bit) - Expected 5466929.0, actual 5466929 (1.00x) +Testing collisions (low 8-bit) - Expected 5470769.0, actual 5470769 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 30 - 0.072% + +Keyset 'TwoBytes' - up-to-12-byte keys, 18616785 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 40347.8, actual 40130 (0.99x) (-217) +Testing collisions (high 27-42 bits) - Worst is 32 bits: 40130/40347 (0.99x) +Testing collisions (high 12-bit) - Expected 18612689.0, actual 18612689 (1.00x) +Testing collisions (high 8-bit) - Expected 18616529.0, actual 18616529 (1.00x) +Testing collisions (low 32-bit) - Expected 40347.8, actual 40032 (0.99x) (-315) +Testing collisions (low 27-42 bits) - Worst is 42 bits: 46/39 (1.17x) +Testing collisions (low 12-bit) - Expected 18612689.0, actual 18612689 (1.00x) +Testing collisions (low 8-bit) - Expected 18616529.0, actual 18616529 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 40 - 0.023% + +Keyset 'TwoBytes' - up-to-16-byte keys, 44251425 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 227963.2, actual 226681 (0.99x) (-1282) +Testing collisions (high 29-45 bits) - Worst is 45 bits: 31/27 (1.11x) +Testing collisions (high 12-bit) - Expected 44247329.0, actual 44247329 (1.00x) +Testing collisions (high 8-bit) - Expected 44251169.0, actual 44251169 (1.00x) +Testing collisions (low 32-bit) - Expected 227963.2, actual 227005 (1.00x) (-958) +Testing collisions (low 29-45 bits) - Worst is 43 bits: 130/111 (1.17x) +Testing collisions (low 12-bit) - Expected 44247329.0, actual 44247329 (1.00x) +Testing collisions (low 8-bit) - Expected 44251169.0, actual 44251169 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 44 - 0.009% + +Keyset 'TwoBytes' - up-to-20-byte keys, 86536545 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 871784.7, actual 865470 (0.99x) (-6314) +Testing collisions (high 30-47 bits) - Worst is 41 bits: 1794/1702 (1.05x) +Testing collisions (high 12-bit) - Expected 86532449.0, actual 86532449 (1.00x) +Testing collisions (high 8-bit) - Expected 86536289.0, actual 86536289 (1.00x) +Testing collisions (low 32-bit) - Expected 871784.7, actual 867660 (1.00x) (-4124) +Testing collisions (low 30-47 bits) - Worst is 43 bits: 452/425 (1.06x) +Testing collisions (low 12-bit) - Expected 86532449.0, actual 86532449 (1.00x) +Testing collisions (low 8-bit) - Expected 86536289.0, actual 86536289 (1.00x) +Testing distribution - Worst bias is the 19-bit window at bit 46 - 0.004% + + +[[[ Keyset 'Text' Tests ]]] + +Keyset 'Text' - keys of form "FooXXXXBar" - 14776336 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 25418.1, actual 25256 (0.99x) (-162) +Testing collisions (high 27-42 bits) - Worst is 42 bits: 30/24 (1.21x) +Testing collisions (high 12-bit) - Expected 14772240.0, actual 14772240 (1.00x) +Testing collisions (high 8-bit) - Expected 14776080.0, actual 14776080 (1.00x) +Testing collisions (low 32-bit) - Expected 25418.1, actual 25375 (1.00x) (-43) +Testing collisions (low 27-42 bits) - Worst is 40 bits: 112/99 (1.13x) +Testing collisions (low 12-bit) - Expected 14772240.0, actual 14772240 (1.00x) +Testing collisions (low 8-bit) - Expected 14776080.0, actual 14776080 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 61 - 0.024% + +Keyset 'Text' - keys of form "FooBarXXXX" - 14776336 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 25418.1, actual 25878 (1.02x) (460) +Testing collisions (high 27-42 bits) - Worst is 33 bits: 12976/12709 (1.02x) +Testing collisions (high 12-bit) - Expected 14772240.0, actual 14772240 (1.00x) +Testing collisions (high 8-bit) - Expected 14776080.0, actual 14776080 (1.00x) +Testing collisions (low 32-bit) - Expected 25418.1, actual 25540 (1.00x) (122) +Testing collisions (low 27-42 bits) - Worst is 38 bits: 416/397 (1.05x) +Testing collisions (low 12-bit) - Expected 14772240.0, actual 14772240 (1.00x) +Testing collisions (low 8-bit) - Expected 14776080.0, actual 14776080 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 27 - 0.023% + +Keyset 'Text' - keys of form "XXXXFooBar" - 14776336 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 25418.1, actual 25266 (0.99x) (-152) +Testing collisions (high 27-42 bits) - Worst is 34 bits: 6369/6354 (1.00x) +Testing collisions (high 12-bit) - Expected 14772240.0, actual 14772240 (1.00x) +Testing collisions (high 8-bit) - Expected 14776080.0, actual 14776080 (1.00x) +Testing collisions (low 32-bit) - Expected 25418.1, actual 25162 (0.99x) (-256) +Testing collisions (low 27-42 bits) - Worst is 40 bits: 101/99 (1.02x) +Testing collisions (low 12-bit) - Expected 14772240.0, actual 14772240 (1.00x) +Testing collisions (low 8-bit) - Expected 14776080.0, actual 14776080 (1.00x) +Testing distribution - Worst bias is the 20-bit window at bit 10 - 0.016% + +Keyset 'Words' - 4000000 random keys of len 6-16 from alnum charset +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1862.6, actual 1923 (1.03x) (61) +Testing collisions (high 25-38 bits) - Worst is 37 bits: 65/58 (1.12x) +Testing collisions (high 12-bit) - Expected 3995904.0, actual 3995904 (1.00x) +Testing collisions (high 8-bit) - Expected 3999744.0, actual 3999744 (1.00x) +Testing collisions (low 32-bit) - Expected 1862.6, actual 1876 (1.01x) (14) +Testing collisions (low 25-38 bits) - Worst is 32 bits: 1876/1862 (1.01x) +Testing collisions (low 12-bit) - Expected 3995904.0, actual 3995904 (1.00x) +Testing collisions (low 8-bit) - Expected 3999744.0, actual 3999744 (1.00x) +Testing distribution - Worst bias is the 19-bit window at bit 1 - 0.075% + +Keyset 'Words' - 4000000 random keys of len 6-16 from password charset +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1862.6, actual 1884 (1.01x) (22) +Testing collisions (high 25-38 bits) - Worst is 35 bits: 253/232 (1.09x) +Testing collisions (high 12-bit) - Expected 3995904.0, actual 3995904 (1.00x) +Testing collisions (high 8-bit) - Expected 3999744.0, actual 3999744 (1.00x) +Testing collisions (low 32-bit) - Expected 1862.6, actual 1858 (1.00x) (-4) +Testing collisions (low 25-38 bits) - Worst is 36 bits: 140/116 (1.20x) +Testing collisions (low 12-bit) - Expected 3995904.0, actual 3995904 (1.00x) +Testing collisions (low 8-bit) - Expected 3999744.0, actual 3999744 (1.00x) +Testing distribution - Worst bias is the 18-bit window at bit 47 - 0.053% + +Keyset 'Words' - 102401 dict words +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1.2, actual 1 (0.82x) +Testing collisions (high 20-27 bits) - Worst is 24 bits: 333/312 (1.07x) +Testing collisions (high 12-bit) - Expected 98305.0, actual 98305 (1.00x) +Testing collisions (high 8-bit) - Expected 102145.0, actual 102145 (1.00x) +Testing collisions (low 32-bit) - Expected 1.2, actual 1 (0.82x) +Testing collisions (low 20-27 bits) - Worst is 22 bits: 1284/1250 (1.03x) +Testing collisions (low 12-bit) - Expected 98305.0, actual 98305 (1.00x) +Testing collisions (low 8-bit) - Expected 102145.0, actual 102145 (1.00x) +Testing distribution - Worst bias is the 14-bit window at bit 28 - 0.409% + + +[[[ Keyset 'Zeroes' Tests ]]] + +Keyset 'Zeroes' - 204800 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 4.9, actual 6 (1.23x) (2) +Testing collisions (high 21-29 bits) - Worst is 28 bits: 82/78 (1.05x) +Testing collisions (high 12-bit) - Expected 200704.0, actual 200704 (1.00x) +Testing collisions (high 8-bit) - Expected 204544.0, actual 204544 (1.00x) +Testing collisions (low 32-bit) - Expected 4.9, actual 5 (1.02x) (1) +Testing collisions (low 21-29 bits) - Worst is 29 bits: 40/39 (1.02x) +Testing collisions (low 12-bit) - Expected 200704.0, actual 200704 (1.00x) +Testing collisions (low 8-bit) - Expected 204544.0, actual 204544 (1.00x) +Testing distribution - Worst bias is the 14-bit window at bit 50 - 0.267% + + +[[[ Keyset 'Seed' Tests ]]] + +Keyset 'Seed' - 5000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 2910.4, actual 2988 (1.03x) (78) +Testing collisions (high 26-39 bits) - Worst is 39 bits: 27/22 (1.19x) +Testing collisions (high 12-bit) - Expected 4995904.0, actual 4995904 (1.00x) +Testing collisions (high 8-bit) - Expected 4999744.0, actual 4999744 (1.00x) +Testing collisions (low 32-bit) - Expected 2910.4, actual 3040 (1.04x) (130) +Testing collisions (low 26-39 bits) - Worst is 33 bits: 1528/1455 (1.05x) +Testing collisions (low 12-bit) - Expected 4995904.0, actual 4995904 (1.00x) +Testing collisions (low 8-bit) - Expected 4999744.0, actual 4999744 (1.00x) +Testing distribution - Worst bias is the 19-bit window at bit 8 - 0.045% + + +[[[ Keyset 'PerlinNoise' Tests ]]] + +Testing 16777216 coordinates (L2) : +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 32768.0, actual 32864 (1.00x) (97) +Testing collisions (high 27-42 bits) - Worst is 38 bits: 548/511 (1.07x) +Testing collisions (high 12-bit) - Expected 16773120.0, actual 16773120 (1.00x) +Testing collisions (high 8-bit) - Expected 16776960.0, actual 16776960 (1.00x) +Testing collisions (low 32-bit) - Expected 32768.0, actual 32219 (0.98x) (-548) +Testing collisions (low 27-42 bits) - Worst is 42 bits: 44/31 (1.38x) +Testing collisions (low 12-bit) - Expected 16773120.0, actual 16773120 (1.00x) +Testing collisions (low 8-bit) - Expected 16776960.0, actual 16776960 (1.00x) + + +[[[ Diff 'Differential' Tests ]]] + +Testing 8303632 up-to-5-bit differentials in 64-bit keys -> 64 bit hashes. +1000 reps, 8303632000 total tests, expecting 0.00 random collisions.......... +0 total collisions, of which 0 single collisions were ignored + +Testing 11017632 up-to-4-bit differentials in 128-bit keys -> 64 bit hashes. +1000 reps, 11017632000 total tests, expecting 0.00 random collisions.......... +0 total collisions, of which 0 single collisions were ignored + +Testing 2796416 up-to-3-bit differentials in 256-bit keys -> 64 bit hashes. +1000 reps, 2796416000 total tests, expecting 0.00 random collisions.......... +0 total collisions, of which 0 single collisions were ignored + + +[[[ DiffDist 'Differential Distribution' Tests ]]] + +Testing bit 0 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 503 (0.98x) (-8) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 37/31 (1.16x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 499 (0.97x) +Testing collisions (low 24-36 bits) - Worst is 31 bits: 1028/1023 (1.00x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 1 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 550 (1.07x) (39) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 147/127 (1.15x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 504 (0.98x) (-7) +Testing collisions (low 24-36 bits) - Worst is 26 bits: 32606/32767 (1.00x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 2 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 490 (0.96x) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 67/63 (1.05x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 522 (1.02x) (11) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 37/31 (1.16x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 3 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 483 (0.94x) +Testing collisions (high 24-36 bits) - Worst is 28 bits: 8201/8191 (1.00x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 519 (1.01x) (8) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 86/63 (1.34x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 4 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 476 (0.93x) +Testing collisions (high 24-36 bits) - Worst is 30 bits: 2026/2047 (0.99x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 511 (1.00x) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 145/127 (1.13x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 5 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 497 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 29 bits: 4206/4095 (1.03x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 488 (0.95x) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 141/127 (1.10x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 6 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 506 (0.99x) (-5) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 137/127 (1.07x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 512 (1.00x) (1) +Testing collisions (low 24-36 bits) - Worst is 28 bits: 8292/8191 (1.01x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 7 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 481 (0.94x) +Testing collisions (high 24-36 bits) - Worst is 30 bits: 2011/2047 (0.98x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 551 (1.08x) (40) +Testing collisions (low 24-36 bits) - Worst is 32 bits: 551/511 (1.08x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 8 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 550 (1.07x) (39) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 47/31 (1.47x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 499 (0.97x) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 139/127 (1.09x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 9 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 493 (0.96x) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 66/63 (1.03x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 537 (1.05x) (26) +Testing collisions (low 24-36 bits) - Worst is 33 bits: 299/255 (1.17x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 10 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 555 (1.08x) (44) +Testing collisions (high 24-36 bits) - Worst is 33 bits: 281/255 (1.10x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 495 (0.97x) +Testing collisions (low 24-36 bits) - Worst is 31 bits: 1048/1023 (1.02x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 11 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 459 (0.90x) +Testing collisions (high 24-36 bits) - Worst is 29 bits: 4100/4095 (1.00x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 488 (0.95x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 42/31 (1.31x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 12 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 491 (0.96x) +Testing collisions (high 24-36 bits) - Worst is 29 bits: 4167/4095 (1.02x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 480 (0.94x) +Testing collisions (low 24-36 bits) - Worst is 30 bits: 2089/2047 (1.02x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 13 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 497 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 132/127 (1.03x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 553 (1.08x) (42) +Testing collisions (low 24-36 bits) - Worst is 32 bits: 553/511 (1.08x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 14 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 538 (1.05x) (27) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 34/31 (1.06x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 508 (0.99x) (-3) +Testing collisions (low 24-36 bits) - Worst is 33 bits: 271/255 (1.06x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 15 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 534 (1.04x) (23) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 36/31 (1.13x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 530 (1.04x) (19) +Testing collisions (low 24-36 bits) - Worst is 33 bits: 267/255 (1.04x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 16 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 481 (0.94x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 33/31 (1.03x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 498 (0.97x) +Testing collisions (low 24-36 bits) - Worst is 29 bits: 4135/4095 (1.01x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 17 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 526 (1.03x) (15) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 80/63 (1.25x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 477 (0.93x) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 64/63 (1.00x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 18 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 483 (0.94x) +Testing collisions (high 24-36 bits) - Worst is 28 bits: 8326/8191 (1.02x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 490 (0.96x) +Testing collisions (low 24-36 bits) - Worst is 29 bits: 4095/4095 (1.00x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 19 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 492 (0.96x) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 136/127 (1.06x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 529 (1.03x) (18) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 79/63 (1.23x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 20 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 494 (0.96x) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 67/63 (1.05x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 511 (1.00x) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 137/127 (1.07x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 21 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 494 (0.96x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 45/31 (1.41x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 523 (1.02x) (12) +Testing collisions (low 24-36 bits) - Worst is 30 bits: 2124/2047 (1.04x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 22 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 497 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 129/127 (1.01x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 499 (0.97x) +Testing collisions (low 24-36 bits) - Worst is 33 bits: 258/255 (1.01x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 23 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 520 (1.02x) (9) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 37/31 (1.16x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 515 (1.01x) (4) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 39/31 (1.22x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 24 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 496 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 37/31 (1.16x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 486 (0.95x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 36/31 (1.13x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 25 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 515 (1.01x) (4) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 35/31 (1.09x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 511 (1.00x) +Testing collisions (low 24-36 bits) - Worst is 28 bits: 8241/8191 (1.01x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 26 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 525 (1.03x) (14) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 88/63 (1.38x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 500 (0.98x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 43/31 (1.34x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 27 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 496 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 70/63 (1.09x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 516 (1.01x) (5) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 38/31 (1.19x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 28 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 523 (1.02x) (12) +Testing collisions (high 24-36 bits) - Worst is 32 bits: 523/511 (1.02x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 512 (1.00x) (1) +Testing collisions (low 24-36 bits) - Worst is 30 bits: 2100/2047 (1.03x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 29 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 534 (1.04x) (23) +Testing collisions (high 24-36 bits) - Worst is 32 bits: 534/511 (1.04x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 494 (0.96x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 34/31 (1.06x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 30 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 505 (0.99x) (-6) +Testing collisions (high 24-36 bits) - Worst is 29 bits: 4057/4095 (0.99x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 499 (0.97x) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 68/63 (1.06x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 31 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 526 (1.03x) (15) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 36/31 (1.13x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 517 (1.01x) (6) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 40/31 (1.25x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 32 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 467 (0.91x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 41/31 (1.28x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 512 (1.00x) (1) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 142/127 (1.11x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 33 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 518 (1.01x) (7) +Testing collisions (high 24-36 bits) - Worst is 33 bits: 267/255 (1.04x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 518 (1.01x) (7) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 40/31 (1.25x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 34 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 540 (1.05x) (29) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 49/31 (1.53x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 556 (1.09x) (45) +Testing collisions (low 24-36 bits) - Worst is 32 bits: 556/511 (1.09x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 35 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 486 (0.95x) +Testing collisions (high 24-36 bits) - Worst is 30 bits: 2057/2047 (1.00x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 504 (0.98x) (-7) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 33/31 (1.03x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 36 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 514 (1.00x) (3) +Testing collisions (high 24-36 bits) - Worst is 31 bits: 1035/1023 (1.01x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 462 (0.90x) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 64/63 (1.00x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 37 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 496 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 65/63 (1.02x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 506 (0.99x) (-5) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 73/63 (1.14x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 38 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 489 (0.96x) +Testing collisions (high 24-36 bits) - Worst is 30 bits: 2065/2047 (1.01x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 490 (0.96x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 39/31 (1.22x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 39 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 539 (1.05x) (28) +Testing collisions (high 24-36 bits) - Worst is 33 bits: 271/255 (1.06x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 514 (1.00x) (3) +Testing collisions (low 24-36 bits) - Worst is 32 bits: 514/511 (1.00x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 40 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 484 (0.95x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 36/31 (1.13x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 515 (1.01x) (4) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 37/31 (1.16x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 41 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 505 (0.99x) (-6) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 137/127 (1.07x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 520 (1.02x) (9) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 67/63 (1.05x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 42 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 506 (0.99x) (-5) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 90/63 (1.41x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 566 (1.11x) (55) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 39/31 (1.22x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 43 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 541 (1.06x) (30) +Testing collisions (high 24-36 bits) - Worst is 32 bits: 541/511 (1.06x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 480 (0.94x) +Testing collisions (low 24-36 bits) - Worst is 27 bits: 16264/16383 (0.99x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 44 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 474 (0.93x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 38/31 (1.19x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 496 (0.97x) +Testing collisions (low 24-36 bits) - Worst is 30 bits: 2097/2047 (1.02x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 45 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 497 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 65/63 (1.02x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 557 (1.09x) (46) +Testing collisions (low 24-36 bits) - Worst is 32 bits: 557/511 (1.09x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 46 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 514 (1.00x) (3) +Testing collisions (high 24-36 bits) - Worst is 32 bits: 514/511 (1.00x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 540 (1.05x) (29) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 43/31 (1.34x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 47 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 520 (1.02x) (9) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 39/31 (1.22x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 540 (1.05x) (29) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 81/63 (1.27x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 48 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 533 (1.04x) (22) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 75/63 (1.17x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 513 (1.00x) (2) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 40/31 (1.25x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 49 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 522 (1.02x) (11) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 37/31 (1.16x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 491 (0.96x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 40/31 (1.25x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 50 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 527 (1.03x) (16) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 35/31 (1.09x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 545 (1.06x) (34) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 168/127 (1.31x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 51 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 505 (0.99x) (-6) +Testing collisions (high 24-36 bits) - Worst is 33 bits: 280/255 (1.09x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 492 (0.96x) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 74/63 (1.16x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 52 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 468 (0.91x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 36/31 (1.13x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 509 (0.99x) (-2) +Testing collisions (low 24-36 bits) - Worst is 31 bits: 1028/1023 (1.00x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 53 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 495 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 28 bits: 8270/8191 (1.01x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 486 (0.95x) +Testing collisions (low 24-36 bits) - Worst is 29 bits: 4190/4095 (1.02x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 54 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 513 (1.00x) (2) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 77/63 (1.20x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 477 (0.93x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 39/31 (1.22x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 55 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 509 (0.99x) (-2) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 71/63 (1.11x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 527 (1.03x) (16) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 34/31 (1.06x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 56 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 487 (0.95x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 39/31 (1.22x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 485 (0.95x) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 41/31 (1.28x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 57 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 485 (0.95x) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 41/31 (1.28x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 545 (1.06x) (34) +Testing collisions (low 24-36 bits) - Worst is 35 bits: 73/63 (1.14x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 58 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 509 (0.99x) (-2) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 135/127 (1.05x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 511 (1.00x) +Testing collisions (low 24-36 bits) - Worst is 30 bits: 2106/2047 (1.03x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 59 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 509 (0.99x) (-2) +Testing collisions (high 24-36 bits) - Worst is 31 bits: 1032/1023 (1.01x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 505 (0.99x) (-6) +Testing collisions (low 24-36 bits) - Worst is 29 bits: 4237/4095 (1.03x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 60 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 499 (0.97x) +Testing collisions (high 24-36 bits) - Worst is 28 bits: 8290/8191 (1.01x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 526 (1.03x) (15) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 41/31 (1.28x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 61 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 534 (1.04x) (23) +Testing collisions (high 24-36 bits) - Worst is 34 bits: 134/127 (1.05x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 508 (0.99x) (-3) +Testing collisions (low 24-36 bits) - Worst is 31 bits: 1035/1023 (1.01x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 62 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 530 (1.04x) (19) +Testing collisions (high 24-36 bits) - Worst is 32 bits: 530/511 (1.04x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 517 (1.01x) (6) +Testing collisions (low 24-36 bits) - Worst is 36 bits: 42/31 (1.31x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + +Testing bit 63 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 512.0, actual 521 (1.02x) (10) +Testing collisions (high 24-36 bits) - Worst is 36 bits: 43/31 (1.34x) +Testing collisions (high 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (high 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) +Testing collisions (low 32-bit) - Expected 512.0, actual 472 (0.92x) +Testing collisions (low 24-36 bits) - Worst is 28 bits: 8141/8191 (0.99x) +Testing collisions (low 12-bit) - Expected 2093056.0, actual 2093056 (1.00x) +Testing collisions (low 8-bit) - Expected 2096896.0, actual 2096896 (1.00x) + + +[[[ MomentChi2 Tests ]]] + +Analyze hashes produced from a serie of linearly increasing numbers of 32-bit, using a step of 3 ... +Target values to approximate : 38918200.000000 - 410450.000000 +Popcount 1 stats : 38919121.989370 - 410434.062934 +Popcount 0 stats : 38918028.259881 - 410423.768513 +MomentChi2 for bits 1 : 1.03555 +MomentChi2 for bits 0 : 0.0359308 + +Derivative stats (transition from 2 consecutive values) : +Popcount 1 stats : 38919523.956012 - 410475.202934 +Popcount 0 stats : 38918557.696824 - 410478.877341 +MomentChi2 for deriv b1 : 2.13522 +MomentChi2 for deriv b0 : 0.155856 + + Great !! + + +[[[ Prng Tests ]]] + +Generating 33554432 random numbers : +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 131072.0, actual 130471 (1.00x) (-600) +Testing collisions (high 28-44 bits) - Worst is 42 bits: 130/127 (1.02x) +Testing collisions (high 12-bit) - Expected 33550336.0, actual 33550336 (1.00x) +Testing collisions (high 8-bit) - Expected 33554176.0, actual 33554176 (1.00x) +Testing collisions (low 32-bit) - Expected 131072.0, actual 130871 (1.00x) (-200) +Testing collisions (low 28-44 bits) - Worst is 43 bits: 70/63 (1.09x) +Testing collisions (low 12-bit) - Expected 33550336.0, actual 33550336 (1.00x) +Testing collisions (low 8-bit) - Expected 33554176.0, actual 33554176 (1.00x) + + +Input vcode 0x00000001, Output vcode 0x00000001, Result vcode 0x00000001 +Verification value is 0x00000001 - Testing took 584.720469 seconds +------------------------------------------------------------------------------- diff --git a/third_party/rust/ahash/smhasher/clone_smhasher.sh b/third_party/rust/ahash/smhasher/clone_smhasher.sh new file mode 100644 index 0000000000..e3290d2df1 --- /dev/null +++ b/third_party/rust/ahash/smhasher/clone_smhasher.sh @@ -0,0 +1 @@ +git clone https://github.com/rurban/smhasher.git && cd smhasher && git apply ../0001-Add-support-for-aHash.patch diff --git a/third_party/rust/ahash/smhasher/fallbackOutput.txt b/third_party/rust/ahash/smhasher/fallbackOutput.txt new file mode 100644 index 0000000000..cb324c5da3 --- /dev/null +++ b/third_party/rust/ahash/smhasher/fallbackOutput.txt @@ -0,0 +1,1467 @@ +------------------------------------------------------------------------------- +--- Testing ahash64 "ahash 64bit" GOOD + +[[[ Sanity Tests ]]] + +Verification value 0x52EC0BA4 ....... SKIP (self- or unseeded) +Running sanity check 1 .......... PASS +Running AppendedZeroesTest .......... PASS + +[[[ Speed Tests ]]] + +Bulk speed test - 262144-byte keys +Alignment 7 - 8.506 bytes/cycle - 24336.28 MiB/sec @ 3 ghz +Alignment 6 - 8.505 bytes/cycle - 24333.38 MiB/sec @ 3 ghz +Alignment 5 - 8.500 bytes/cycle - 24317.30 MiB/sec @ 3 ghz +Alignment 4 - 8.491 bytes/cycle - 24294.09 MiB/sec @ 3 ghz +Alignment 3 - 8.491 bytes/cycle - 24293.90 MiB/sec @ 3 ghz +Alignment 2 - 8.492 bytes/cycle - 24296.22 MiB/sec @ 3 ghz +Alignment 1 - 8.508 bytes/cycle - 24340.25 MiB/sec @ 3 ghz +Alignment 0 - 8.748 bytes/cycle - 25028.73 MiB/sec @ 3 ghz +Average - 8.530 bytes/cycle - 24405.02 MiB/sec @ 3 ghz + +Small key speed test - 1-byte keys - 14.97 cycles/hash +Small key speed test - 2-byte keys - 15.00 cycles/hash +Small key speed test - 3-byte keys - 15.00 cycles/hash +Small key speed test - 4-byte keys - 15.00 cycles/hash +Small key speed test - 5-byte keys - 16.00 cycles/hash +Small key speed test - 6-byte keys - 16.00 cycles/hash +Small key speed test - 7-byte keys - 16.11 cycles/hash +Small key speed test - 8-byte keys - 15.00 cycles/hash +Small key speed test - 9-byte keys - 19.04 cycles/hash +Small key speed test - 10-byte keys - 19.70 cycles/hash +Small key speed test - 11-byte keys - 19.43 cycles/hash +Small key speed test - 12-byte keys - 19.54 cycles/hash +Small key speed test - 13-byte keys - 19.65 cycles/hash +Small key speed test - 14-byte keys - 19.45 cycles/hash +Small key speed test - 15-byte keys - 19.00 cycles/hash +Small key speed test - 16-byte keys - 19.45 cycles/hash +Small key speed test - 17-byte keys - 19.84 cycles/hash +Small key speed test - 18-byte keys - 19.65 cycles/hash +Small key speed test - 19-byte keys - 19.36 cycles/hash +Small key speed test - 20-byte keys - 19.74 cycles/hash +Small key speed test - 21-byte keys - 19.56 cycles/hash +Small key speed test - 22-byte keys - 20.11 cycles/hash +Small key speed test - 23-byte keys - 20.08 cycles/hash +Small key speed test - 24-byte keys - 20.29 cycles/hash +Small key speed test - 25-byte keys - 20.55 cycles/hash +Small key speed test - 26-byte keys - 20.42 cycles/hash +Small key speed test - 27-byte keys - 20.43 cycles/hash +Small key speed test - 28-byte keys - 20.37 cycles/hash +Small key speed test - 29-byte keys - 20.42 cycles/hash +Small key speed test - 30-byte keys - 20.42 cycles/hash +Small key speed test - 31-byte keys - 20.37 cycles/hash +Average 18.708 cycles/hash + +[[[ 'Hashmap' Speed Tests ]]] + +std::unordered_map +Init std HashMapTest: 295.723 cycles/op (102401 inserts, 1% deletions) +Running std HashMapTest: 124.234 cycles/op (1.7 stdv) + +greg7mdp/parallel-hashmap +Init fast HashMapTest: 112.031 cycles/op (102401 inserts, 1% deletions) +Running fast HashMapTest: 85.002 cycles/op (2.1 stdv) ....... PASS + +[[[ Avalanche Tests ]]] + +Testing 24-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.700000% +Testing 32-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.628000% +Testing 40-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.628667% +Testing 48-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.662000% +Testing 56-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.699333% +Testing 64-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.665333% +Testing 72-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.630667% +Testing 80-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.692000% +Testing 96-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.774000% +Testing 112-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.730667% +Testing 128-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.794000% +Testing 160-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.702000% +Testing 512-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.763333% +Testing 1024-bit keys -> 64-bit hashes, 300000 reps worst bias is 0.816667% + +[[[ Keyset 'Sparse' Tests ]]] + +Keyset 'Sparse' - 16-bit keys with up to 9 bits set - 50643 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 0.6, actual 0 (0.00x) +Testing collisions (high 19-26 bits) - Worst is 22 bits: 320/611 (0.52x) +Testing collisions (high 12-bit) - Expected 50643.0, actual 46547 (0.92x) +Testing collisions (high 8-bit) - Expected 50643.0, actual 50387 (0.99x) (-256) +Testing collisions (low 32-bit) - Expected 0.6, actual 1 (1.67x) (1) +Testing collisions (low 19-26 bits) - Worst is 20 bits: 1168/2445 (0.48x) +Testing collisions (low 12-bit) - Expected 50643.0, actual 46547 (0.92x) +Testing collisions (low 8-bit) - Expected 50643.0, actual 50387 (0.99x) (-256) +Testing distribution - Worst bias is the 13-bit window at bit 4 - 0.462% + +Keyset 'Sparse' - 24-bit keys with up to 8 bits set - 1271626 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 376.5, actual 180 (0.48x) +Testing collisions (high 24-36 bits) - Worst is 35 bits: 26/47 (0.55x) +Testing collisions (high 12-bit) - Expected 1271626.0, actual 1267530 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 1271626.0, actual 1271370 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 376.5, actual 184 (0.49x) +Testing collisions (low 24-36 bits) - Worst is 34 bits: 52/94 (0.55x) +Testing collisions (low 12-bit) - Expected 1271626.0, actual 1267530 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 1271626.0, actual 1271370 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 8 - 0.085% + +Keyset 'Sparse' - 32-bit keys with up to 7 bits set - 4514873 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 4746.0, actual 2412 (0.51x) +Testing collisions (high 26-39 bits) - Worst is 39 bits: 24/37 (0.65x) +Testing collisions (high 12-bit) - Expected 4514873.0, actual 4510777 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 4514873.0, actual 4514617 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 4746.0, actual 2445 (0.52x) +Testing collisions (low 26-39 bits) - Worst is 34 bits: 630/1186 (0.53x) +Testing collisions (low 12-bit) - Expected 4514873.0, actual 4510777 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 4514873.0, actual 4514617 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 47 - 0.045% + +Keyset 'Sparse' - 40-bit keys with up to 6 bits set - 4598479 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 4923.4, actual 2402 (0.49x) +Testing collisions (high 26-39 bits) - Worst is 34 bits: 639/1230 (0.52x) +Testing collisions (high 12-bit) - Expected 4598479.0, actual 4594383 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 4598479.0, actual 4598223 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 4923.4, actual 2444 (0.50x) +Testing collisions (low 26-39 bits) - Worst is 39 bits: 22/38 (0.57x) +Testing collisions (low 12-bit) - Expected 4598479.0, actual 4594383 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 4598479.0, actual 4598223 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 62 - 0.044% + +Keyset 'Sparse' - 48-bit keys with up to 6 bits set - 14196869 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 46927.3, actual 23533 (0.50x) +Testing collisions (high 28-43 bits) - Worst is 43 bits: 19/22 (0.83x) +Testing collisions (high 12-bit) - Expected 14196869.0, actual 14192773 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 14196869.0, actual 14196613 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 46927.3, actual 23338 (0.50x) +Testing collisions (low 28-43 bits) - Worst is 35 bits: 2947/5865 (0.50x) +Testing collisions (low 12-bit) - Expected 14196869.0, actual 14192773 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 14196869.0, actual 14196613 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 45 - 0.021% + +Keyset 'Sparse' - 56-bit keys with up to 5 bits set - 4216423 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 4139.3, actual 2065 (0.50x) +Testing collisions (high 26-39 bits) - Worst is 39 bits: 22/32 (0.68x) +Testing collisions (high 12-bit) - Expected 4216423.0, actual 4212327 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 4216423.0, actual 4216167 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 4139.3, actual 1999 (0.48x) +Testing collisions (low 26-39 bits) - Worst is 31 bits: 4110/8278 (0.50x) +Testing collisions (low 12-bit) - Expected 4216423.0, actual 4212327 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 4216423.0, actual 4216167 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 26 - 0.049% + +Keyset 'Sparse' - 64-bit keys with up to 5 bits set - 8303633 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16053.7, actual 7972 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 40 bits: 39/62 (0.62x) +Testing collisions (high 12-bit) - Expected 8303633.0, actual 8299537 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8303633.0, actual 8303377 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16053.7, actual 7866 (0.49x) +Testing collisions (low 27-41 bits) - Worst is 40 bits: 36/62 (0.57x) +Testing collisions (low 12-bit) - Expected 8303633.0, actual 8299537 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8303633.0, actual 8303377 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 61 - 0.047% + +Keyset 'Sparse' - 72-bit keys with up to 5 bits set - 15082603 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 52965.5, actual 26424 (0.50x) +Testing collisions (high 28-43 bits) - Worst is 42 bits: 32/51 (0.62x) +Testing collisions (high 12-bit) - Expected 15082603.0, actual 15078507 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 15082603.0, actual 15082347 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 52965.5, actual 26433 (0.50x) +Testing collisions (low 28-43 bits) - Worst is 42 bits: 34/51 (0.66x) +Testing collisions (low 12-bit) - Expected 15082603.0, actual 15078507 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 15082603.0, actual 15082347 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 59 - 0.022% + +Keyset 'Sparse' - 96-bit keys with up to 4 bits set - 3469497 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 2802.7, actual 1406 (0.50x) +Testing collisions (high 26-39 bits) - Worst is 33 bits: 706/1401 (0.50x) +Testing collisions (high 12-bit) - Expected 3469497.0, actual 3465401 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 3469497.0, actual 3469241 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 2802.7, actual 1374 (0.49x) +Testing collisions (low 26-39 bits) - Worst is 37 bits: 44/87 (0.50x) +Testing collisions (low 12-bit) - Expected 3469497.0, actual 3465401 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 3469497.0, actual 3469241 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 5 - 0.066% + +Keyset 'Sparse' - 160-bit keys with up to 4 bits set - 26977161 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 169446.5, actual 84730 (0.50x) +Testing collisions (high 29-45 bits) - Worst is 45 bits: 14/20 (0.68x) +Testing collisions (high 12-bit) - Expected 26977161.0, actual 26973065 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 26977161.0, actual 26976905 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 169446.5, actual 84408 (0.50x) +Testing collisions (low 29-45 bits) - Worst is 36 bits: 5329/10590 (0.50x) +Testing collisions (low 12-bit) - Expected 26977161.0, actual 26973065 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 26977161.0, actual 26976905 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 20 - 0.010% + +Keyset 'Sparse' - 256-bit keys with up to 3 bits set - 2796417 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1820.7, actual 908 (0.50x) +Testing collisions (high 25-38 bits) - Worst is 35 bits: 118/227 (0.52x) +Testing collisions (high 12-bit) - Expected 2796417.0, actual 2792321 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2796417.0, actual 2796161 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1820.7, actual 921 (0.51x) +Testing collisions (low 25-38 bits) - Worst is 38 bits: 18/28 (0.63x) +Testing collisions (low 12-bit) - Expected 2796417.0, actual 2792321 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2796417.0, actual 2796161 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 8 - 0.067% + +Keyset 'Sparse' - 512-bit keys with up to 3 bits set - 22370049 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 116512.9, actual 58015 (0.50x) +Testing collisions (high 28-44 bits) - Worst is 44 bits: 19/28 (0.67x) +Testing collisions (high 12-bit) - Expected 22370049.0, actual 22365953 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 22370049.0, actual 22369793 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 116512.9, actual 58134 (0.50x) +Testing collisions (low 28-44 bits) - Worst is 40 bits: 241/455 (0.53x) +Testing collisions (low 12-bit) - Expected 22370049.0, actual 22365953 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 22370049.0, actual 22369793 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 62 - 0.016% + +Keyset 'Sparse' - 1024-bit keys with up to 2 bits set - 524801 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 64.1, actual 30 (0.47x) +Testing collisions (high 23-33 bits) - Worst is 33 bits: 21/32 (0.65x) +Testing collisions (high 12-bit) - Expected 524801.0, actual 520705 (0.99x) (-4096) +Testing collisions (high 8-bit) - Expected 524801.0, actual 524545 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 64.1, actual 37 (0.58x) +Testing collisions (low 23-33 bits) - Worst is 33 bits: 23/32 (0.72x) +Testing collisions (low 12-bit) - Expected 524801.0, actual 520705 (0.99x) (-4096) +Testing collisions (low 8-bit) - Expected 524801.0, actual 524545 (1.00x) (-256) +Testing distribution - Worst bias is the 16-bit window at bit 54 - 0.182% + +Keyset 'Sparse' - 2048-bit keys with up to 2 bits set - 2098177 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1025.0, actual 529 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 22/32 (0.69x) +Testing collisions (high 12-bit) - Expected 2098177.0, actual 2094081 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2098177.0, actual 2097921 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1025.0, actual 525 (0.51x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 24/32 (0.75x) +Testing collisions (low 12-bit) - Expected 2098177.0, actual 2094081 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2098177.0, actual 2097921 (1.00x) (-256) +Testing distribution - Worst bias is the 18-bit window at bit 4 - 0.088% + + +[[[ Keyset 'Permutation' Tests ]]] + +Combination Lowbits Tests: +Keyset 'Combination' - up to 7 blocks from a set of 8 - 2396744 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1337.5, actual 659 (0.49x) +Testing collisions (high 25-38 bits) - Worst is 36 bits: 55/83 (0.66x) +Testing collisions (high 12-bit) - Expected 2396744.0, actual 2392648 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2396744.0, actual 2396488 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1337.5, actual 692 (0.52x) +Testing collisions (low 25-38 bits) - Worst is 38 bits: 13/20 (0.62x) +Testing collisions (low 12-bit) - Expected 2396744.0, actual 2392648 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2396744.0, actual 2396488 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 8 - 0.049% + + +Combination Highbits Tests +Keyset 'Combination' - up to 7 blocks from a set of 8 - 2396744 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1337.5, actual 668 (0.50x) +Testing collisions (high 25-38 bits) - Worst is 34 bits: 175/334 (0.52x) +Testing collisions (high 12-bit) - Expected 2396744.0, actual 2392648 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2396744.0, actual 2396488 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1337.5, actual 675 (0.50x) +Testing collisions (low 25-38 bits) - Worst is 36 bits: 54/83 (0.65x) +Testing collisions (low 12-bit) - Expected 2396744.0, actual 2392648 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2396744.0, actual 2396488 (1.00x) (-256) +Testing distribution - Worst bias is the 18-bit window at bit 5 - 0.074% + + +Combination Hi-Lo Tests: +Keyset 'Combination' - up to 6 blocks from a set of 15 - 12204240 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 34678.6, actual 17094 (0.49x) +Testing collisions (high 27-42 bits) - Worst is 36 bits: 1095/2167 (0.51x) +Testing collisions (high 12-bit) - Expected 12204240.0, actual 12200144 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 12204240.0, actual 12203984 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 34678.6, actual 17320 (0.50x) +Testing collisions (low 27-42 bits) - Worst is 40 bits: 75/135 (0.55x) +Testing collisions (low 12-bit) - Expected 12204240.0, actual 12200144 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 12204240.0, actual 12203984 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 12 - 0.032% + + +Combination 0x8000000 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8224 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 33 bits: 4198/8191 (0.51x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8166 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 36 bits: 529/1023 (0.52x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 26 - 0.040% + + +Combination 0x0000001 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8221 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 38 bits: 139/255 (0.54x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8070 (0.49x) +Testing collisions (low 27-41 bits) - Worst is 37 bits: 273/511 (0.53x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 33 - 0.045% + + +Combination 0x800000000000000 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8143 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 41 bits: 20/31 (0.63x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8230 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 38 bits: 144/255 (0.56x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 54 - 0.035% + + +Combination 0x000000000000001 Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8039 (0.49x) +Testing collisions (high 27-41 bits) - Worst is 41 bits: 17/31 (0.53x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8271 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 41 bits: 20/31 (0.63x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 40 - 0.045% + + +Combination 16-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8194 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 33 bits: 4138/8191 (0.51x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8163 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 41 bits: 20/31 (0.63x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 9 - 0.037% + + +Combination 16-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8063 (0.49x) +Testing collisions (high 27-41 bits) - Worst is 41 bits: 18/31 (0.56x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8241 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 39 bits: 91/127 (0.71x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 18 - 0.035% + + +Combination 32-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 7942 (0.48x) +Testing collisions (high 27-41 bits) - Worst is 41 bits: 17/31 (0.53x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8191 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 41 bits: 17/31 (0.53x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 5 - 0.038% + + +Combination 32-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8218 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 39 bits: 71/127 (0.55x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8144 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 30 bits: 32683/65535 (0.50x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 42 - 0.039% + + +Combination 64-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8140 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 40 bits: 39/63 (0.61x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8127 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 40 bits: 34/63 (0.53x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 53 - 0.042% + + +Combination 64-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8178 (0.50x) +Testing collisions (high 27-41 bits) - Worst is 40 bits: 46/63 (0.72x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8354 (0.51x) +Testing collisions (low 27-41 bits) - Worst is 38 bits: 136/255 (0.53x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 26 - 0.038% + + +Combination 128-bytes [0-1] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8384 (0.51x) +Testing collisions (high 27-41 bits) - Worst is 32 bits: 8384/16383 (0.51x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8287 (0.51x) +Testing collisions (low 27-41 bits) - Worst is 33 bits: 4188/8191 (0.51x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 23 - 0.037% + + +Combination 128-bytes [0-last] Tests: +Keyset 'Combination' - up to 22 blocks from a set of 2 - 8388606 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 16384.0, actual 8104 (0.49x) +Testing collisions (high 27-41 bits) - Worst is 34 bits: 2045/4095 (0.50x) +Testing collisions (high 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 16384.0, actual 8263 (0.50x) +Testing collisions (low 27-41 bits) - Worst is 41 bits: 19/31 (0.59x) +Testing collisions (low 12-bit) - Expected 8388606.0, actual 8384510 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 8388606.0, actual 8388350 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 16 - 0.040% + + +[[[ Keyset 'Window' Tests ]]] + +Keyset 'Window' - 32-bit key, 25-bit window - 32 tests, 33554432 keys per test +Window at 0 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 1 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 2 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 3 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 4 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 5 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 6 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 7 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 8 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 9 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 10 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 11 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 12 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 13 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 14 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 15 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 16 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 17 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 18 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 19 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 20 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 21 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 22 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 23 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 24 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 25 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 26 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 27 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 28 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 29 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 30 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 31 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Window at 32 - Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) + +[[[ Keyset 'Cyclic' Tests ]]] + +Keyset 'Cyclic' - 8 cycles of 8 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 232.8, actual 107 (0.46x) +Testing collisions (high 24-35 bits) - Worst is 34 bits: 38/58 (0.65x) +Testing collisions (high 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 232.8, actual 100 (0.43x) +Testing collisions (low 24-35 bits) - Worst is 27 bits: 3707/7450 (0.50x) +Testing collisions (low 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 63 - 0.088% + +Keyset 'Cyclic' - 8 cycles of 9 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 232.8, actual 106 (0.46x) +Testing collisions (high 24-35 bits) - Worst is 26 bits: 7405/14901 (0.50x) +Testing collisions (high 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 232.8, actual 126 (0.54x) +Testing collisions (low 24-35 bits) - Worst is 35 bits: 18/29 (0.62x) +Testing collisions (low 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 55 - 0.099% + +Keyset 'Cyclic' - 8 cycles of 10 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 232.8, actual 127 (0.55x) +Testing collisions (high 24-35 bits) - Worst is 33 bits: 66/116 (0.57x) +Testing collisions (high 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 232.8, actual 104 (0.45x) +Testing collisions (low 24-35 bits) - Worst is 27 bits: 3807/7450 (0.51x) +Testing collisions (low 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 7 - 0.136% + +Keyset 'Cyclic' - 8 cycles of 11 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 232.8, actual 118 (0.51x) +Testing collisions (high 24-35 bits) - Worst is 34 bits: 33/58 (0.57x) +Testing collisions (high 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 232.8, actual 128 (0.55x) +Testing collisions (low 24-35 bits) - Worst is 32 bits: 128/232 (0.55x) +Testing collisions (low 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 38 - 0.115% + +Keyset 'Cyclic' - 8 cycles of 12 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 232.8, actual 91 (0.39x) +Testing collisions (high 24-35 bits) - Worst is 27 bits: 3813/7450 (0.51x) +Testing collisions (high 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 232.8, actual 102 (0.44x) +Testing collisions (low 24-35 bits) - Worst is 25 bits: 14959/29802 (0.50x) +Testing collisions (low 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 63 - 0.130% + +Keyset 'Cyclic' - 8 cycles of 16 bytes - 1000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 232.8, actual 122 (0.52x) +Testing collisions (high 24-35 bits) - Worst is 35 bits: 17/29 (0.58x) +Testing collisions (high 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 232.8, actual 116 (0.50x) +Testing collisions (low 24-35 bits) - Worst is 33 bits: 61/116 (0.52x) +Testing collisions (low 12-bit) - Expected 1000000.0, actual 995904 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 1000000.0, actual 999744 (1.00x) (-256) +Testing distribution - Worst bias is the 17-bit window at bit 19 - 0.122% + + +[[[ Keyset 'TwoBytes' Tests ]]] + +Keyset 'TwoBytes' - up-to-4-byte keys, 652545 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 99.1, actual 47 (0.47x) +Testing collisions (high 23-34 bits) - Worst is 34 bits: 16/24 (0.65x) +Testing collisions (high 12-bit) - Expected 652545.0, actual 648449 (0.99x) (-4096) +Testing collisions (high 8-bit) - Expected 652545.0, actual 652289 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 99.1, actual 46 (0.46x) +Testing collisions (low 23-34 bits) - Worst is 33 bits: 28/49 (0.56x) +Testing collisions (low 12-bit) - Expected 652545.0, actual 648449 (0.99x) (-4096) +Testing collisions (low 8-bit) - Expected 652545.0, actual 652289 (1.00x) (-256) +Testing distribution - Worst bias is the 16-bit window at bit 34 - 0.138% + +Keyset 'TwoBytes' - up-to-8-byte keys, 5471025 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 6969.1, actual 3548 (0.51x) +Testing collisions (high 26-40 bits) - Worst is 40 bits: 15/27 (0.55x) +Testing collisions (high 12-bit) - Expected 5471025.0, actual 5466929 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 5471025.0, actual 5470769 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 6969.1, actual 3378 (0.48x) +Testing collisions (low 26-40 bits) - Worst is 39 bits: 34/54 (0.62x) +Testing collisions (low 12-bit) - Expected 5471025.0, actual 5466929 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 5471025.0, actual 5470769 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 12 - 0.056% + +Keyset 'TwoBytes' - up-to-12-byte keys, 18616785 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 80695.5, actual 40607 (0.50x) +Testing collisions (high 28-43 bits) - Worst is 42 bits: 42/78 (0.53x) +Testing collisions (high 12-bit) - Expected 18616785.0, actual 18612689 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 18616785.0, actual 18616529 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 80695.5, actual 40085 (0.50x) +Testing collisions (low 28-43 bits) - Worst is 36 bits: 2521/5043 (0.50x) +Testing collisions (low 12-bit) - Expected 18616785.0, actual 18612689 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 18616785.0, actual 18616529 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 56 - 0.012% + +Keyset 'TwoBytes' - up-to-16-byte keys, 44251425 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 455926.3, actual 227080 (0.50x) +Testing collisions (high 29-46 bits) - Worst is 46 bits: 15/27 (0.54x) +Testing collisions (high 12-bit) - Expected 44251425.0, actual 44247329 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 44251425.0, actual 44251169 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 455926.3, actual 226684 (0.50x) +Testing collisions (low 29-46 bits) - Worst is 33 bits: 113923/227963 (0.50x) +Testing collisions (low 12-bit) - Expected 44251425.0, actual 44247329 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 44251425.0, actual 44251169 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 0 - 0.007% + +Keyset 'TwoBytes' - up-to-20-byte keys, 86536545 total keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1743569.4, actual 866241 (0.50x) +Testing collisions (high 30-48 bits) - Worst is 36 bits: 54556/108973 (0.50x) +Testing collisions (high 12-bit) - Expected 86536545.0, actual 86532449 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 86536545.0, actual 86536289 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1743569.4, actual 865870 (0.50x) +Testing collisions (low 30-48 bits) - Worst is 37 bits: 27421/54486 (0.50x) +Testing collisions (low 12-bit) - Expected 86536545.0, actual 86532449 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 86536545.0, actual 86536289 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 5 - 0.005% + + +[[[ Keyset 'Text' Tests ]]] + +Keyset 'Text' - keys of form "Foo[XXXX]Bar" - 14776336 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 50836.3, actual 25649 (0.50x) +Testing collisions (high 28-43 bits) - Worst is 34 bits: 6513/12709 (0.51x) +Testing collisions (high 12-bit) - Expected 14776336.0, actual 14772240 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 14776336.0, actual 14776080 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 50836.3, actual 25314 (0.50x) +Testing collisions (low 28-43 bits) - Worst is 40 bits: 108/198 (0.54x) +Testing collisions (low 12-bit) - Expected 14776336.0, actual 14772240 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 14776336.0, actual 14776080 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 35 - 0.020% + +Keyset 'Text' - keys of form "FooBar[XXXX]" - 14776336 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 50836.3, actual 25522 (0.50x) +Testing collisions (high 28-43 bits) - Worst is 43 bits: 15/24 (0.60x) +Testing collisions (high 12-bit) - Expected 14776336.0, actual 14772240 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 14776336.0, actual 14776080 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 50836.3, actual 25294 (0.50x) +Testing collisions (low 28-43 bits) - Worst is 41 bits: 61/99 (0.61x) +Testing collisions (low 12-bit) - Expected 14776336.0, actual 14772240 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 14776336.0, actual 14776080 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 37 - 0.017% + +Keyset 'Text' - keys of form "[XXXX]FooBar" - 14776336 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 50836.3, actual 25439 (0.50x) +Testing collisions (high 28-43 bits) - Worst is 38 bits: 416/794 (0.52x) +Testing collisions (high 12-bit) - Expected 14776336.0, actual 14772240 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 14776336.0, actual 14776080 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 50836.3, actual 25310 (0.50x) +Testing collisions (low 28-43 bits) - Worst is 42 bits: 32/49 (0.64x) +Testing collisions (low 12-bit) - Expected 14776336.0, actual 14772240 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 14776336.0, actual 14776080 (1.00x) (-256) +Testing distribution - Worst bias is the 20-bit window at bit 2 - 0.025% + + +[[[ Keyset 'Zeroes' Tests ]]] + +Keyset 'Zeroes' - 204800 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 9.8, actual 5 (0.51x) +Testing collisions (high 21-30 bits) - Worst is 29 bits: 41/78 (0.52x) +Testing collisions (high 12-bit) - Expected 204800.0, actual 200704 (0.98x) +Testing collisions (high 8-bit) - Expected 204800.0, actual 204544 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 9.8, actual 4 (0.41x) +Testing collisions (low 21-30 bits) - Worst is 25 bits: 643/1249 (0.51x) +Testing collisions (low 12-bit) - Expected 204800.0, actual 200704 (0.98x) +Testing collisions (low 8-bit) - Expected 204800.0, actual 204544 (1.00x) (-256) +Testing distribution - Worst bias is the 15-bit window at bit 14 - 0.281% + + +[[[ Keyset 'Seed' Tests ]]] + +Keyset 'Seed' - 5000000 keys +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 5820.8, actual 2880 (0.49x) +Testing collisions (high 26-40 bits) - Worst is 37 bits: 105/181 (0.58x) +Testing collisions (high 12-bit) - Expected 5000000.0, actual 4995904 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 5000000.0, actual 4999744 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 5820.8, actual 2957 (0.51x) +Testing collisions (low 26-40 bits) - Worst is 33 bits: 1494/2910 (0.51x) +Testing collisions (low 12-bit) - Expected 5000000.0, actual 4995904 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 5000000.0, actual 4999744 (1.00x) (-256) +Testing distribution - Worst bias is the 19-bit window at bit 59 - 0.046% + + +[[[ Keyset 'PerlinNoise' Tests ]]] + +Testing 16777216 coordinates (L2) : +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 65536.0, actual 32715 (0.50x) +Testing collisions (high 28-43 bits) - Worst is 42 bits: 46/63 (0.72x) +Testing collisions (high 12-bit) - Expected 16777216.0, actual 16773120 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 16777216.0, actual 16776960 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 65536.0, actual 32752 (0.50x) +Testing collisions (low 28-43 bits) - Worst is 41 bits: 69/127 (0.54x) +Testing collisions (low 12-bit) - Expected 16777216.0, actual 16773120 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 16777216.0, actual 16776960 (1.00x) (-256) + + +[[[ Diff 'Differential' Tests ]]] + +Testing 8303632 up-to-5-bit differentials in 64-bit keys -> 64 bit hashes. +1000 reps, 8303632000 total tests, expecting 0.00 random collisions.......... +0 total collisions, of which 0 single collisions were ignored + +Testing 11017632 up-to-4-bit differentials in 128-bit keys -> 64 bit hashes. +1000 reps, 11017632000 total tests, expecting 0.00 random collisions.......... +0 total collisions, of which 0 single collisions were ignored + +Testing 2796416 up-to-3-bit differentials in 256-bit keys -> 64 bit hashes. +1000 reps, 2796416000 total tests, expecting 0.00 random collisions.......... +0 total collisions, of which 0 single collisions were ignored + + +[[[ DiffDist 'Differential Distribution' Tests ]]] + +Testing bit 0 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 516 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 470 (0.46x) +Testing collisions (low 25-37 bits) - Worst is 28 bits: 8112/16383 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 1 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 514 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 24/31 (0.75x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 507 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 2 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 536 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 135/255 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 479 (0.47x) +Testing collisions (low 25-37 bits) - Worst is 29 bits: 4068/8191 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 3 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 535 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 535/1023 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 533 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 31 bits: 1106/2047 (0.54x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 4 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 519 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 17/31 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 513 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 33 bits: 274/511 (0.54x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 5 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 520 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 520/1023 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 527 (0.51x) +Testing collisions (low 25-37 bits) - Worst is 33 bits: 269/511 (0.53x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 6 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 519 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 41/63 (0.64x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 518 (0.51x) +Testing collisions (low 25-37 bits) - Worst is 29 bits: 4236/8191 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 7 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 463 (0.45x) +Testing collisions (high 25-37 bits) - Worst is 28 bits: 8190/16383 (0.50x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 497 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 34 bits: 134/255 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 8 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 513 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 21/31 (0.66x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 470 (0.46x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 32/63 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 9 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 527 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 37/63 (0.58x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 541 (0.53x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 10 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 516 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 142/255 (0.55x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 470 (0.46x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 11 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 500 (0.49x) +Testing collisions (high 25-37 bits) - Worst is 31 bits: 1038/2047 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 526 (0.51x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 34/63 (0.53x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 12 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 503 (0.49x) +Testing collisions (high 25-37 bits) - Worst is 35 bits: 83/127 (0.65x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 479 (0.47x) +Testing collisions (low 25-37 bits) - Worst is 35 bits: 68/127 (0.53x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 13 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 515 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 40/63 (0.63x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 468 (0.46x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 33/63 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 14 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 465 (0.45x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 33/63 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 549 (0.54x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 20/31 (0.63x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 15 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 523 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 35/63 (0.55x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 537 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 35 bits: 71/127 (0.55x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 16 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 517 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 484 (0.47x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 25/31 (0.78x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 17 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 504 (0.49x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 18/31 (0.56x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 487 (0.48x) +Testing collisions (low 25-37 bits) - Worst is 35 bits: 68/127 (0.53x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 18 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 534 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 33 bits: 280/511 (0.55x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 519 (0.51x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 22/31 (0.69x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 19 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 571 (0.56x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 22/31 (0.69x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 493 (0.48x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 33/63 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 20 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 536 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 33 bits: 272/511 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 536 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 23/31 (0.72x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 21 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 464 (0.45x) +Testing collisions (high 25-37 bits) - Worst is 35 bits: 67/127 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 536 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 35 bits: 81/127 (0.63x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 22 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 508 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 131/255 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 482 (0.47x) +Testing collisions (low 25-37 bits) - Worst is 30 bits: 2054/4095 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 23 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 492 (0.48x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 20/31 (0.63x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 493 (0.48x) +Testing collisions (low 25-37 bits) - Worst is 28 bits: 8176/16383 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 24 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 518 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 30 bits: 2102/4095 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 463 (0.45x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 33/63 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 25 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 532 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 532/1023 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 514 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 31 bits: 1032/2047 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 26 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 479 (0.47x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 40/63 (0.63x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 532 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 33 bits: 269/511 (0.53x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 27 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 511 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 33 bits: 272/511 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 450 (0.44x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 18/31 (0.56x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 28 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 538 (0.53x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 138/255 (0.54x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 520 (0.51x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 29 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 525 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 525/1023 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 516 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 23/31 (0.72x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 30 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 483 (0.47x) +Testing collisions (high 25-37 bits) - Worst is 35 bits: 66/127 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 512 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 30 bits: 2100/4095 (0.51x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 31 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 503 (0.49x) +Testing collisions (high 25-37 bits) - Worst is 27 bits: 16180/32767 (0.49x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 514 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 21/31 (0.66x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 32 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 557 (0.54x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 21/31 (0.66x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 502 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 30 bits: 2087/4095 (0.51x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 33 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 494 (0.48x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 17/31 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 481 (0.47x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 37/63 (0.58x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 34 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 520 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 22/31 (0.69x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 500 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 21/31 (0.66x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 35 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 526 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 526/1023 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 507 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 34 bits: 134/255 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 36 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 530 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 20/31 (0.63x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 503 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 31 bits: 1034/2047 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 37 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 489 (0.48x) +Testing collisions (high 25-37 bits) - Worst is 35 bits: 67/127 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 482 (0.47x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 36/63 (0.56x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 38 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 521 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 33 bits: 273/511 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 498 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 30 bits: 2041/4095 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 39 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 483 (0.47x) +Testing collisions (high 25-37 bits) - Worst is 35 bits: 72/127 (0.56x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 529 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 20/31 (0.63x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 40 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 489 (0.48x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 33/63 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 499 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 29 bits: 4246/8191 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 41 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 536 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 137/255 (0.54x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 543 (0.53x) +Testing collisions (low 25-37 bits) - Worst is 33 bits: 281/511 (0.55x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 42 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 513 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 31 bits: 1082/2047 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 494 (0.48x) +Testing collisions (low 25-37 bits) - Worst is 34 bits: 131/255 (0.51x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 43 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 495 (0.48x) +Testing collisions (high 25-37 bits) - Worst is 29 bits: 4158/8191 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 473 (0.46x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 16/31 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 44 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 479 (0.47x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 541 (0.53x) +Testing collisions (low 25-37 bits) - Worst is 35 bits: 74/127 (0.58x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 45 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 531 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 531/1023 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 513 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 18/31 (0.56x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 46 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 531 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 37/63 (0.58x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 510 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 17/31 (0.53x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 47 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 502 (0.49x) +Testing collisions (high 25-37 bits) - Worst is 28 bits: 8325/16383 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 529 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 39/63 (0.61x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 48 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 512 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 35 bits: 69/127 (0.54x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 495 (0.48x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 33/63 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 49 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 551 (0.54x) +Testing collisions (high 25-37 bits) - Worst is 36 bits: 38/63 (0.59x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 470 (0.46x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 37/63 (0.58x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 50 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 483 (0.47x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 22/31 (0.69x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 512 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 31 bits: 1030/2047 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 51 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 520 (0.51x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 17/31 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 510 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 31 bits: 1040/2047 (0.51x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 52 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 531 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 531/1023 (0.52x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 534 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 22/31 (0.69x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 53 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 546 (0.53x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 26/31 (0.81x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 483 (0.47x) +Testing collisions (low 25-37 bits) - Worst is 35 bits: 65/127 (0.51x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 54 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 488 (0.48x) +Testing collisions (high 25-37 bits) - Worst is 29 bits: 4102/8191 (0.50x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 501 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 21/31 (0.66x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 55 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 509 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 130/255 (0.51x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 493 (0.48x) +Testing collisions (low 25-37 bits) - Worst is 34 bits: 136/255 (0.53x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 56 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 528 (0.52x) +Testing collisions (high 25-37 bits) - Worst is 33 bits: 274/511 (0.54x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 551 (0.54x) +Testing collisions (low 25-37 bits) - Worst is 32 bits: 551/1023 (0.54x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 57 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 556 (0.54x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 157/255 (0.61x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 489 (0.48x) +Testing collisions (low 25-37 bits) - Worst is 30 bits: 2047/4095 (0.50x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 58 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 467 (0.46x) +Testing collisions (high 25-37 bits) - Worst is 37 bits: 18/31 (0.56x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 523 (0.51x) +Testing collisions (low 25-37 bits) - Worst is 36 bits: 39/63 (0.61x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 59 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 497 (0.49x) +Testing collisions (high 25-37 bits) - Worst is 30 bits: 2031/4095 (0.50x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 530 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 33 bits: 278/511 (0.54x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 60 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 510 (0.50x) +Testing collisions (high 25-37 bits) - Worst is 28 bits: 8176/16383 (0.50x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 517 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 19/31 (0.59x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 61 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 496 (0.48x) +Testing collisions (high 25-37 bits) - Worst is 30 bits: 2041/4095 (0.50x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 529 (0.52x) +Testing collisions (low 25-37 bits) - Worst is 32 bits: 529/1023 (0.52x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 62 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 552 (0.54x) +Testing collisions (high 25-37 bits) - Worst is 32 bits: 552/1023 (0.54x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 507 (0.50x) +Testing collisions (low 25-37 bits) - Worst is 37 bits: 18/31 (0.56x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + +Testing bit 63 +Testing collisions ( 64-bit) - Expected 0.0, actual 0 (0.00x) +Testing collisions (high 32-bit) - Expected 1024.0, actual 484 (0.47x) +Testing collisions (high 25-37 bits) - Worst is 34 bits: 135/255 (0.53x) +Testing collisions (high 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (high 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) +Testing collisions (low 32-bit) - Expected 1024.0, actual 500 (0.49x) +Testing collisions (low 25-37 bits) - Worst is 33 bits: 277/511 (0.54x) +Testing collisions (low 12-bit) - Expected 2097152.0, actual 2093056 (1.00x) (-4096) +Testing collisions (low 8-bit) - Expected 2097152.0, actual 2096896 (1.00x) (-256) + + +[[[ MomentChi2 Tests ]]] + +Analyze hashes produced from a serie of linearly increasing numbers of 32-bit, using a step of 3 ... +Target values to approximate : 38918200.000000 - 410450.000000 +Popcount 1 stats : 38918484.206651 - 410464.360454 +Popcount 0 stats : 38919365.145760 - 410461.861348 +MomentChi2 for bits 1 : 0.0983945 +MomentChi2 for bits 0 : 1.65373 + +Derivative stats (transition from 2 consecutive values) : +Popcount 1 stats : 38917342.700616 - 410405.257542 +Popcount 0 stats : 38919729.298852 - 410467.929221 +MomentChi2 for deriv b1 : 0.895362 +MomentChi2 for deriv b0 : 2.84895 + + Great !! + + + +Input vcode 0x00000001, Output vcode 0x00000001, Result vcode 0x00000001 +Verification value is 0x00000001 - Testing took 934.304636 seconds +------------------------------------------------------------------------------- diff --git a/third_party/rust/ahash/src/aes_hash.rs b/third_party/rust/ahash/src/aes_hash.rs new file mode 100644 index 0000000000..b62edba470 --- /dev/null +++ b/third_party/rust/ahash/src/aes_hash.rs @@ -0,0 +1,291 @@ +use crate::convert::*; +use crate::operations::*; +#[cfg(feature = "specialize")] +use crate::HasherExt; +use core::hash::Hasher; + +/// A `Hasher` for hashing an arbitrary stream of bytes. +/// +/// Instances of [`AHasher`] represent state that is updated while hashing data. +/// +/// Each method updates the internal state based on the new data provided. Once +/// all of the data has been provided, the resulting hash can be obtained by calling +/// `finish()` +/// +/// [Clone] is also provided in case you wish to calculate hashes for two different items that +/// start with the same data. +/// +#[derive(Debug, Clone)] +pub struct AHasher { + enc: u128, + sum: u128, + key: u128, +} + +impl AHasher { + /// Creates a new hasher keyed to the provided keys. + /// + /// Normally hashers are created via `AHasher::default()` for fixed keys or `RandomState::new()` for randomly + /// generated keys and `RandomState::with_seeds(a,b)` for seeds that are set and can be reused. All of these work at + /// map creation time (and hence don't have any overhead on a per-item bais). + /// + /// This method directly creates the hasher instance and performs no transformation on the provided seeds. This may + /// be useful where a HashBuilder is not desired, such as for testing purposes. + /// + /// # Example + /// + /// ``` + /// use std::hash::Hasher; + /// use ahash::AHasher; + /// + /// let mut hasher = AHasher::new_with_keys(1234, 5678); + /// + /// hasher.write_u32(1989); + /// hasher.write_u8(11); + /// hasher.write_u8(9); + /// hasher.write(b"Huh?"); + /// + /// println!("Hash is {:x}!", hasher.finish()); + /// ``` + #[inline] + pub fn new_with_keys(key1: u128, key2: u128) -> Self { + Self { + enc: key1, + sum: key2, + key: key1 ^ key2, + } + } + + #[cfg(test)] + pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher { + use crate::random_state::scramble_keys; + let (k1, k2, k3, k4) = scramble_keys(key1, key2); + AHasher { + enc: [k1, k2].convert(), + sum: [k3, k4].convert(), + key: add_by_64s([k1, k2], [k3, k4]).convert(), + } + } + + #[inline(always)] + fn add_in_length(&mut self, length: u64) { + //This will be scrambled by the next AES round. + let mut enc: [u64; 2] = self.enc.convert(); + enc[0] = enc[0].wrapping_add(length); + self.enc = enc.convert(); + } + + #[inline(always)] + fn hash_in(&mut self, new_value: u128) { + self.enc = aesenc(self.enc, new_value); + self.sum = shuffle_and_add(self.sum, new_value); + } + + #[inline(always)] + fn hash_in_2(&mut self, v1: u128, v2: u128) { + self.enc = aesenc(self.enc, v1); + self.sum = shuffle_and_add(self.sum, v1); + self.enc = aesenc(self.enc, v2); + self.sum = shuffle_and_add(self.sum, v2); + } +} + +#[cfg(feature = "specialize")] +impl HasherExt for AHasher { + #[inline] + fn hash_u64(self, value: u64) -> u64 { + let mask = self.sum as u64; + let rot = (self.enc & 64) as u32; + folded_multiply(value ^ mask, crate::fallback_hash::MULTIPLE).rotate_left(rot) + } + + #[inline] + fn short_finish(&self) -> u64 { + let buffer: [u64; 2] = self.enc.convert(); + folded_multiply(buffer[0], buffer[1]) + } +} + +/// Provides methods to hash all of the primitive types. +impl Hasher for AHasher { + #[inline] + fn write_u8(&mut self, i: u8) { + self.write_u64(i as u64); + } + + #[inline] + fn write_u16(&mut self, i: u16) { + self.write_u64(i as u64); + } + + #[inline] + fn write_u32(&mut self, i: u32) { + self.write_u64(i as u64); + } + + #[inline] + fn write_u128(&mut self, i: u128) { + self.hash_in(i); + } + + #[inline] + fn write_usize(&mut self, i: usize) { + self.write_u64(i as u64); + } + + #[inline] + fn write_u64(&mut self, i: u64) { + self.write_u128(i as u128); + } + + #[inline] + #[allow(clippy::collapsible_if)] + fn write(&mut self, input: &[u8]) { + let mut data = input; + let length = data.len(); + self.add_in_length(length as u64); + //A 'binary search' on sizes reduces the number of comparisons. + if data.len() < 8 { + let value: [u64; 2] = if data.len() >= 2 { + if data.len() >= 4 { + //len 4-8 + [data.read_u32().0 as u64, data.read_last_u32() as u64] + } else { + //len 2-3 + [data.read_u16().0 as u64, data[data.len() - 1] as u64] + } + } else { + if data.len() > 0 { + [data[0] as u64, 0] + } else { + [0, 0] + } + }; + self.hash_in(value.convert()); + } else { + if data.len() > 32 { + if data.len() > 64 { + let tail = data.read_last_u128x4(); + let mut current: [u128; 4] = [self.key; 4]; + current[0] = aesenc(current[0], tail[0]); + current[1] = aesenc(current[1], tail[1]); + current[2] = aesenc(current[2], tail[2]); + current[3] = aesenc(current[3], tail[3]); + let mut sum: [u128; 2] = [self.key, self.key]; + sum[0] = add_by_64s(sum[0].convert(), tail[0].convert()).convert(); + sum[1] = add_by_64s(sum[1].convert(), tail[1].convert()).convert(); + sum[0] = shuffle_and_add(sum[0], tail[2]); + sum[1] = shuffle_and_add(sum[1], tail[3]); + while data.len() > 64 { + let (blocks, rest) = data.read_u128x4(); + current[0] = aesenc(current[0], blocks[0]); + current[1] = aesenc(current[1], blocks[1]); + current[2] = aesenc(current[2], blocks[2]); + current[3] = aesenc(current[3], blocks[3]); + sum[0] = shuffle_and_add(sum[0], blocks[0]); + sum[1] = shuffle_and_add(sum[1], blocks[1]); + sum[0] = shuffle_and_add(sum[0], blocks[2]); + sum[1] = shuffle_and_add(sum[1], blocks[3]); + data = rest; + } + self.hash_in_2(aesenc(current[0], current[1]), aesenc(current[2], current[3])); + self.hash_in(add_by_64s(sum[0].convert(), sum[1].convert()).convert()); + } else { + //len 33-64 + let (head, _) = data.read_u128x2(); + let tail = data.read_last_u128x2(); + self.hash_in_2(head[0], head[1]); + self.hash_in_2(tail[0], tail[1]); + } + } else { + if data.len() > 16 { + //len 17-32 + self.hash_in_2(data.read_u128().0, data.read_last_u128()); + } else { + //len 9-16 + let value: [u64; 2] = [data.read_u64().0, data.read_last_u64()]; + self.hash_in(value.convert()); + } + } + } + } + #[inline] + fn finish(&self) -> u64 { + let combined = aesdec(self.sum, self.enc); + let result: [u64; 2] = aesenc(aesenc(combined, self.key), combined).convert(); + result[0] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::convert::Convert; + use crate::operations::aesenc; + use crate::RandomState; + use std::hash::{BuildHasher, Hasher}; + #[test] + fn test_sanity() { + let mut hasher = RandomState::with_seeds(192837465, 1234567890).build_hasher(); + hasher.write_u64(0); + let h1 = hasher.finish(); + hasher.write(&[1, 0, 0, 0, 0, 0, 0, 0]); + let h2 = hasher.finish(); + assert_ne!(h1, h2); + } + + #[cfg(feature = "compile-time-rng")] + #[test] + fn test_builder() { + use std::collections::HashMap; + use std::hash::BuildHasherDefault; + + let mut map = HashMap::>::default(); + map.insert(1, 3); + } + + #[cfg(feature = "compile-time-rng")] + #[test] + fn test_default() { + let hasher_a = AHasher::default(); + let a_enc: [u64; 2] = hasher_a.enc.convert(); + let a_sum: [u64; 2] = hasher_a.sum.convert(); + assert_ne!(0, a_enc[0]); + assert_ne!(0, a_enc[1]); + assert_ne!(0, a_sum[0]); + assert_ne!(0, a_sum[1]); + assert_ne!(a_enc[0], a_enc[1]); + assert_ne!(a_sum[0], a_sum[1]); + assert_ne!(a_enc[0], a_sum[0]); + assert_ne!(a_enc[1], a_sum[1]); + let hasher_b = AHasher::default(); + let b_enc: [u64; 2] = hasher_b.enc.convert(); + let b_sum: [u64; 2] = hasher_b.sum.convert(); + assert_eq!(a_enc[0], b_enc[0]); + assert_eq!(a_enc[1], b_enc[1]); + assert_eq!(a_sum[0], b_sum[0]); + assert_eq!(a_sum[1], b_sum[1]); + } + + #[test] + fn test_hash() { + let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d]; + let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210]; + result = aesenc(value.convert(), result.convert()).convert(); + result = aesenc(result.convert(), result.convert()).convert(); + let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d]; + let value2: [u64; 2] = [1, 0xFEDCBA9876543210]; + result2 = aesenc(value2.convert(), result2.convert()).convert(); + result2 = aesenc(result2.convert(), result.convert()).convert(); + let result: [u8; 16] = result.convert(); + let result2: [u8; 16] = result2.convert(); + assert_ne!(hex::encode(result), hex::encode(result2)); + } + + #[test] + fn test_conversion() { + let input: &[u8] = "dddddddd".as_bytes(); + let bytes: u64 = as_array!(input, 8).convert(); + assert_eq!(bytes, 0x6464646464646464); + } +} diff --git a/third_party/rust/ahash/src/convert.rs b/third_party/rust/ahash/src/convert.rs new file mode 100644 index 0000000000..435c03c492 --- /dev/null +++ b/third_party/rust/ahash/src/convert.rs @@ -0,0 +1,172 @@ +pub(crate) trait Convert { + fn convert(self) -> To; +} + +macro_rules! convert { + ($a:ty, $b:ty) => { + impl Convert<$b> for $a { + #[inline(always)] + fn convert(self) -> $b { + unsafe { + let mut result: $b = core::mem::zeroed(); + core::ptr::copy_nonoverlapping( + &self as *const $a as *const u8, + &mut result as *mut $b as *mut u8, + core::mem::size_of::<$b>(), + ); + return result; + } + } + } + impl Convert<$a> for $b { + #[inline(always)] + fn convert(self) -> $a { + unsafe { + let mut result: $a = core::mem::zeroed(); + core::ptr::copy_nonoverlapping( + &self as *const $b as *const u8, + &mut result as *mut $a as *mut u8, + core::mem::size_of::<$a>(), + ); + return result; + } + } + } + }; +} + +convert!([u128; 4], [u64; 8]); +convert!([u128; 4], [u32; 16]); +convert!([u128; 4], [u16; 32]); +convert!([u128; 4], [u8; 64]); +convert!([u128; 2], [u64; 4]); +convert!([u128; 2], [u32; 8]); +convert!([u128; 2], [u16; 16]); +convert!([u128; 2], [u8; 32]); +convert!(u128, [u64; 2]); +convert!(u128, [u32; 4]); +convert!(u128, [u16; 8]); +convert!(u128, [u8; 16]); +convert!([u64; 2], [u32; 4]); +convert!([u64; 2], [u16; 8]); +convert!([u64; 2], [u8; 16]); +convert!([u32; 4], [u16; 8]); +convert!([u32; 4], [u8; 16]); +convert!([u16; 8], [u8; 16]); +convert!(u64, [u32; 2]); +convert!(u64, [u16; 4]); +convert!(u64, [u8; 8]); +convert!([u32; 2], [u16; 4]); +convert!([u32; 2], [u8; 8]); +convert!(u32, [u16; 2]); +convert!(u32, [u8; 4]); +convert!([u16; 2], [u8; 4]); +convert!(u16, [u8; 2]); + +convert!([f64; 2], [u8; 16]); +convert!([f32; 4], [u8; 16]); +convert!(f64, [u8; 8]); +convert!([f32; 2], [u8; 8]); +convert!(f32, [u8; 4]); + +macro_rules! as_array { + ($input:expr, $len:expr) => {{ + { + #[inline(always)] + fn as_array(slice: &[T]) -> &[T; $len] { + assert_eq!(slice.len(), $len); + unsafe { &*(slice.as_ptr() as *const [_; $len]) } + } + as_array($input) + } + }}; +} + +pub(crate) trait ReadFromSlice { + fn read_u16(&self) -> (u16, &[u8]); + fn read_u32(&self) -> (u32, &[u8]); + fn read_u64(&self) -> (u64, &[u8]); + fn read_u128(&self) -> (u128, &[u8]); + fn read_u128x2(&self) -> ([u128; 2], &[u8]); + fn read_u128x4(&self) -> ([u128; 4], &[u8]); + fn read_last_u16(&self) -> u16; + fn read_last_u32(&self) -> u32; + fn read_last_u64(&self) -> u64; + fn read_last_u128(&self) -> u128; + fn read_last_u128x2(&self) -> [u128; 2]; + fn read_last_u128x4(&self) -> [u128; 4]; +} + +impl ReadFromSlice for [u8] { + #[inline(always)] + fn read_u16(&self) -> (u16, &[u8]) { + let (value, rest) = self.split_at(2); + (as_array!(value, 2).convert(), rest) + } + + #[inline(always)] + fn read_u32(&self) -> (u32, &[u8]) { + let (value, rest) = self.split_at(4); + (as_array!(value, 4).convert(), rest) + } + + #[inline(always)] + fn read_u64(&self) -> (u64, &[u8]) { + let (value, rest) = self.split_at(8); + (as_array!(value, 8).convert(), rest) + } + + #[inline(always)] + fn read_u128(&self) -> (u128, &[u8]) { + let (value, rest) = self.split_at(16); + (as_array!(value, 16).convert(), rest) + } + + #[inline(always)] + fn read_u128x2(&self) -> ([u128; 2], &[u8]) { + let (value, rest) = self.split_at(32); + (as_array!(value, 32).convert(), rest) + } + + #[inline(always)] + fn read_u128x4(&self) -> ([u128; 4], &[u8]) { + let (value, rest) = self.split_at(64); + (as_array!(value, 64).convert(), rest) + } + + #[inline(always)] + fn read_last_u16(&self) -> u16 { + let (_, value) = self.split_at(self.len() - 2); + as_array!(value, 2).convert() + } + + #[inline(always)] + fn read_last_u32(&self) -> u32 { + let (_, value) = self.split_at(self.len() - 4); + as_array!(value, 4).convert() + } + + #[inline(always)] + fn read_last_u64(&self) -> u64 { + let (_, value) = self.split_at(self.len() - 8); + as_array!(value, 8).convert() + } + + #[inline(always)] + fn read_last_u128(&self) -> u128 { + let (_, value) = self.split_at(self.len() - 16); + as_array!(value, 16).convert() + } + + #[inline(always)] + fn read_last_u128x2(&self) -> [u128; 2] { + let (_, value) = self.split_at(self.len() - 32); + as_array!(value, 32).convert() + } + + #[inline(always)] + fn read_last_u128x4(&self) -> [u128; 4] { + let (_, value) = self.split_at(self.len() - 64); + as_array!(value, 64).convert() + } +} diff --git a/third_party/rust/ahash/src/fallback_hash.rs b/third_party/rust/ahash/src/fallback_hash.rs new file mode 100644 index 0000000000..a43b1c1b12 --- /dev/null +++ b/third_party/rust/ahash/src/fallback_hash.rs @@ -0,0 +1,223 @@ +use crate::convert::*; +use crate::operations::folded_multiply; +#[cfg(feature = "specialize")] +use crate::HasherExt; +use core::hash::Hasher; + +///This constant come from Kunth's prng (Empirically it works better than those from splitmix32). +pub(crate) const MULTIPLE: u64 = 6364136223846793005; +const ROT: u32 = 23; //17 + +/// A `Hasher` for hashing an arbitrary stream of bytes. +/// +/// Instances of [`AHasher`] represent state that is updated while hashing data. +/// +/// Each method updates the internal state based on the new data provided. Once +/// all of the data has been provided, the resulting hash can be obtained by calling +/// `finish()` +/// +/// [Clone] is also provided in case you wish to calculate hashes for two different items that +/// start with the same data. +/// +#[derive(Debug, Clone)] +pub struct AHasher { + buffer: u64, + pad: u64, + extra_keys: [u64; 2], +} + +impl AHasher { + /// Creates a new hasher keyed to the provided key. + #[inline] + #[allow(dead_code)] // Is not called if non-fallback hash is used. + pub fn new_with_keys(key1: u128, key2: u128) -> AHasher { + AHasher { + buffer: key1 as u64, + pad: key2 as u64, + extra_keys: (key1 ^ key2).convert(), + } + } + + #[cfg(test)] + #[allow(dead_code)] // Is not called if non-fallback hash is used. + pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher { + use crate::random_state::scramble_keys; + let (k1, k2, k3, k4) = scramble_keys(key1, key2); + AHasher { + buffer: k1, + pad: k2, + extra_keys: [k3, k4], + } + } + + /// This update function has the goal of updating the buffer with a single multiply + /// FxHash does this but is vulnerable to attack. To avoid this input needs to be masked to with an + /// unpredictable value. Other hashes such as murmurhash have taken this approach but were found vulnerable + /// to attack. The attack was based on the idea of reversing the pre-mixing (Which is necessarily + /// reversible otherwise bits would be lost) then placing a difference in the highest bit before the + /// multiply used to mix the data. Because a multiply can never affect the bits to the right of it, a + /// subsequent update that also differed in this bit could result in a predictable collision. + /// + /// This version avoids this vulnerability while still only using a single multiply. It takes advantage + /// of the fact that when a 64 bit multiply is performed the upper 64 bits are usually computed and thrown + /// away. Instead it creates two 128 bit values where the upper 64 bits are zeros and multiplies them. + /// (The compiler is smart enough to turn this into a 64 bit multiplication in the assembly) + /// Then the upper bits are xored with the lower bits to produce a single 64 bit result. + /// + /// To understand why this is a good scrambling function it helps to understand multiply-with-carry PRNGs: + /// https://en.wikipedia.org/wiki/Multiply-with-carry_pseudorandom_number_generator + /// If the multiple is chosen well, this creates a long period, decent quality PRNG. + /// Notice that this function is equivalent to this except the `buffer`/`state` is being xored with each + /// new block of data. In the event that data is all zeros, it is exactly equivalent to a MWC PRNG. + /// + /// This is impervious to attack because every bit buffer at the end is dependent on every bit in + /// `new_data ^ buffer`. For example suppose two inputs differed in only the 5th bit. Then when the + /// multiplication is performed the `result` will differ in bits 5-69. More specifically it will differ by + /// 2^5 * MULTIPLE. However in the next step bits 65-128 are turned into a separate 64 bit value. So the + /// differing bits will be in the lower 6 bits of this value. The two intermediate values that differ in + /// bits 5-63 and in bits 0-5 respectively get added together. Producing an output that differs in every + /// bit. The addition carries in the multiplication and at the end additionally mean that the even if an + /// attacker somehow knew part of (but not all) the contents of the buffer before hand, + /// they would not be able to predict any of the bits in the buffer at the end. + #[inline(always)] + fn update(&mut self, new_data: u64) { + self.buffer = folded_multiply(new_data ^ self.buffer, MULTIPLE); + } + + /// Similar to the above this function performs an update using a "folded multiply". + /// However it takes in 128 bits of data instead of 64. Both halves must be masked. + /// + /// This makes it impossible for an attacker to place a single bit difference between + /// two blocks so as to cancel each other. + /// + /// However this is not sufficient. to prevent (a,b) from hashing the same as (b,a) the buffer itself must + /// be updated between calls in a way that does not commute. To achieve this XOR and Rotate are used. + /// Add followed by xor is not the same as xor followed by add, and rotate ensures that the same out bits + /// can't be changed by the same set of input bits. To cancel this sequence with subsequent input would require + /// knowing the keys. + #[inline(always)] + fn large_update(&mut self, new_data: u128) { + let block: [u64; 2] = new_data.convert(); + let combined = folded_multiply(block[0] ^ self.extra_keys[0], block[1] ^ self.extra_keys[1]); + self.buffer = (combined.wrapping_add(self.buffer) ^ self.pad).rotate_left(ROT); + } +} + +#[cfg(feature = "specialize")] +impl HasherExt for AHasher { + #[inline] + fn hash_u64(self, value: u64) -> u64 { + let rot = (self.pad & 64) as u32; + folded_multiply(value ^ self.buffer, MULTIPLE).rotate_left(rot) + } + + #[inline] + fn short_finish(&self) -> u64 { + self.buffer.wrapping_add(self.pad) + } +} + +/// Provides methods to hash all of the primitive types. +impl Hasher for AHasher { + #[inline] + fn write_u8(&mut self, i: u8) { + self.update(i as u64); + } + + #[inline] + fn write_u16(&mut self, i: u16) { + self.update(i as u64); + } + + #[inline] + fn write_u32(&mut self, i: u32) { + self.update(i as u64); + } + + #[inline] + fn write_u64(&mut self, i: u64) { + self.update(i as u64); + } + + #[inline] + fn write_u128(&mut self, i: u128) { + let data: [u64; 2] = i.convert(); + self.update(data[0]); + self.update(data[1]); + } + + #[inline] + fn write_usize(&mut self, i: usize) { + self.write_u64(i as u64); + } + + #[inline] + #[allow(clippy::collapsible_if)] + fn write(&mut self, input: &[u8]) { + let mut data = input; + let length = data.len() as u64; + //Needs to be an add rather than an xor because otherwise it could be canceled with carefully formed input. + self.buffer = self.buffer.wrapping_add(length).wrapping_mul(MULTIPLE); + //A 'binary search' on sizes reduces the number of comparisons. + if data.len() > 8 { + if data.len() > 16 { + let tail = data.read_last_u128(); + self.large_update(tail); + while data.len() > 16 { + let (block, rest) = data.read_u128(); + self.large_update(block); + data = rest; + } + } else { + self.large_update([data.read_u64().0, data.read_last_u64()].convert()); + } + } else { + if data.len() >= 2 { + if data.len() >= 4 { + let block = [data.read_u32().0 as u64, data.read_last_u32() as u64]; + self.large_update(block.convert()); + } else { + let value = [data.read_u16().0 as u32, data[data.len() - 1] as u32]; + self.update(value.convert()); + } + } else { + if data.len() > 0 { + self.update(data[0] as u64); + } + } + } + } + #[inline] + fn finish(&self) -> u64 { + let rot = (self.buffer & 63) as u32; + folded_multiply(self.buffer, self.pad).rotate_left(rot) + } +} + +#[cfg(test)] +mod tests { + use crate::convert::Convert; + use crate::fallback_hash::*; + + #[test] + fn test_hash() { + let mut hasher = AHasher::new_with_keys(0, 0); + let value: u64 = 1 << 32; + hasher.update(value); + let result = hasher.buffer; + let mut hasher = AHasher::new_with_keys(0, 0); + let value2: u64 = 1; + hasher.update(value2); + let result2 = hasher.buffer; + let result: [u8; 8] = result.convert(); + let result2: [u8; 8] = result2.convert(); + assert_ne!(hex::encode(result), hex::encode(result2)); + } + + #[test] + fn test_conversion() { + let input: &[u8] = "dddddddd".as_bytes(); + let bytes: u64 = as_array!(input, 8).convert(); + assert_eq!(bytes, 0x6464646464646464); + } +} diff --git a/third_party/rust/ahash/src/hash_map.rs b/third_party/rust/ahash/src/hash_map.rs new file mode 100644 index 0000000000..362ac8551a --- /dev/null +++ b/third_party/rust/ahash/src/hash_map.rs @@ -0,0 +1,177 @@ +use std::borrow::Borrow; +use std::collections::{hash_map, HashMap}; +use std::fmt::{self, Debug}; +use std::hash::{BuildHasher, Hash}; +use std::iter::FromIterator; +use std::ops::{Deref, DerefMut, Index}; +use std::panic::UnwindSafe; + +/// A [`HashMap`](std::collections::HashMap) using [`RandomState`](crate::RandomState) to hash the items. +/// Requires the `std` feature to be enabled. +#[derive(Clone)] +pub struct AHashMap(HashMap); + +impl AHashMap +where + K: Hash + Eq, + S: BuildHasher + Default, +{ + pub fn new() -> Self { + AHashMap(HashMap::with_hasher(S::default())) + } + + pub fn with_capacity(capacity: usize) -> Self { + AHashMap(HashMap::with_capacity_and_hasher(capacity, S::default())) + } +} + +impl AHashMap +where + K: Hash + Eq, + S: BuildHasher, +{ + pub fn with_hasher(hash_builder: S) -> Self { + AHashMap(HashMap::with_hasher(hash_builder)) + } + + pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self { + AHashMap(HashMap::with_capacity_and_hasher(capacity, hash_builder)) + } +} + +impl Deref for AHashMap { + type Target = HashMap; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for AHashMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl UnwindSafe for AHashMap +where + K: UnwindSafe, + V: UnwindSafe, +{ +} + +impl PartialEq for AHashMap +where + K: Eq + Hash, + V: PartialEq, + S: BuildHasher, +{ + fn eq(&self, other: &AHashMap) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for AHashMap +where + K: Eq + Hash, + V: Eq, + S: BuildHasher, +{ +} + +impl Index<&Q> for AHashMap +where + K: Eq + Hash + Borrow, + Q: Eq + Hash, + S: BuildHasher, +{ + type Output = V; + + /// Returns a reference to the value corresponding to the supplied key. + /// + /// # Panics + /// + /// Panics if the key is not present in the `HashMap`. + #[inline] + fn index(&self, key: &Q) -> &V { + self.0.index(key) + } +} + +impl Debug for AHashMap +where + K: Eq + Hash + Debug, + V: Debug, + S: BuildHasher, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(fmt) + } +} + +impl FromIterator<(K, V)> for AHashMap +where + K: Eq + Hash, + S: BuildHasher + Default, +{ + fn from_iter>(iter: T) -> Self { + AHashMap(HashMap::from_iter(iter)) + } +} + +impl<'a, K, V, S> IntoIterator for &'a AHashMap { + type Item = (&'a K, &'a V); + type IntoIter = hash_map::Iter<'a, K, V>; + fn into_iter(self) -> Self::IntoIter { + (&self.0).iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut AHashMap { + type Item = (&'a K, &'a mut V); + type IntoIter = hash_map::IterMut<'a, K, V>; + fn into_iter(self) -> Self::IntoIter { + (&mut self.0).iter_mut() + } +} + +impl IntoIterator for AHashMap { + type Item = (K, V); + type IntoIter = hash_map::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Extend<(K, V)> for AHashMap +where + K: Eq + Hash, + S: BuildHasher, +{ + #[inline] + fn extend>(&mut self, iter: T) { + self.0.extend(iter) + } +} + +impl<'a, K, V, S> Extend<(&'a K, &'a V)> for AHashMap +where + K: Eq + Hash + Copy + 'a, + V: Copy + 'a, + S: BuildHasher, +{ + #[inline] + fn extend>(&mut self, iter: T) { + self.0.extend(iter) + } +} + +impl Default for AHashMap +where + K: Eq + Hash, + S: BuildHasher + Default, +{ + #[inline] + fn default() -> AHashMap { + AHashMap::with_hasher(Default::default()) + } +} diff --git a/third_party/rust/ahash/src/hash_quality_test.rs b/third_party/rust/ahash/src/hash_quality_test.rs new file mode 100644 index 0000000000..1a69562daa --- /dev/null +++ b/third_party/rust/ahash/src/hash_quality_test.rs @@ -0,0 +1,451 @@ +use crate::{CallHasher, HasherExt}; +use core::hash::{Hash, Hasher}; +use std::collections::HashMap; + +fn assert_sufficiently_different(a: u64, b: u64, tolerance: i32) { + let (same_byte_count, same_nibble_count) = count_same_bytes_and_nibbles(a, b); + assert!(same_byte_count <= tolerance, "{:x} vs {:x}: {:}", a, b, same_byte_count); + assert!( + same_nibble_count <= tolerance * 3, + "{:x} vs {:x}: {:}", + a, + b, + same_nibble_count + ); + let flipped_bits = (a ^ b).count_ones(); + assert!( + flipped_bits > 12 && flipped_bits < 52, + "{:x} and {:x}: {:}", + a, + b, + flipped_bits + ); + for rotate in 0..64 { + let flipped_bits2 = (a ^ (b.rotate_left(rotate))).count_ones(); + assert!( + flipped_bits2 > 10 && flipped_bits2 < 54, + "{:x} and {:x}: {:}", + a, + b.rotate_left(rotate), + flipped_bits2 + ); + } +} + +fn count_same_bytes_and_nibbles(a: u64, b: u64) -> (i32, i32) { + let mut same_byte_count = 0; + let mut same_nibble_count = 0; + for byte in 0..8 { + let ba = (a >> (8 * byte)) as u8; + let bb = (b >> (8 * byte)) as u8; + if ba == bb { + same_byte_count += 1; + } + if ba & 0xF0u8 == bb & 0xF0u8 { + same_nibble_count += 1; + } + if ba & 0x0Fu8 == bb & 0x0Fu8 { + same_nibble_count += 1; + } + } + (same_byte_count, same_nibble_count) +} + +fn gen_combinations(options: &[u32; 8], depth: u32, so_far: Vec, combinations: &mut Vec>) { + if depth == 0 { + return; + } + for option in options { + let mut next = so_far.clone(); + next.push(*option); + combinations.push(next.clone()); + gen_combinations(options, depth - 1, next, combinations); + } +} + +fn test_no_full_collisions(gen_hash: impl Fn() -> T) { + let options: [u32; 8] = [ + 0x00000000, 0x20000000, 0x40000000, 0x60000000, 0x80000000, 0xA0000000, 0xC0000000, 0xE0000000, + ]; + let mut combinations = Vec::new(); + gen_combinations(&options, 7, Vec::new(), &mut combinations); + let mut map: HashMap> = HashMap::new(); + for combination in combinations { + let array = unsafe { + let (begin, middle, end) = combination.align_to::(); + assert_eq!(0, begin.len()); + assert_eq!(0, end.len()); + middle.to_vec() + }; + let mut hasher = gen_hash(); + hasher.write(&array); + let hash = hasher.finish(); + if let Some(value) = map.get(&hash) { + assert_eq!( + value, &array, + "Found a collision between {:x?} and {:x?}", + value, &array + ); + } else { + map.insert(hash, array); + } + } + assert_eq!(2396744, map.len()); +} + +fn test_keys_change_output(constructor: impl Fn(u64, u64) -> T) { + let mut a = constructor(1, 1); + let mut b = constructor(1, 2); + let mut c = constructor(2, 1); + let mut d = constructor(2, 2); + "test".hash(&mut a); + "test".hash(&mut b); + "test".hash(&mut c); + "test".hash(&mut d); + assert_sufficiently_different(a.finish(), b.finish(), 1); + assert_sufficiently_different(a.finish(), c.finish(), 1); + assert_sufficiently_different(a.finish(), d.finish(), 1); + assert_sufficiently_different(b.finish(), c.finish(), 1); + assert_sufficiently_different(b.finish(), d.finish(), 1); + assert_sufficiently_different(c.finish(), d.finish(), 1); +} + +fn test_input_affect_every_byte(constructor: impl Fn(u64, u64) -> T) { + let base = 0.get_hash(constructor(0, 0)); + for shift in 0..16 { + let mut alternitives = vec![]; + for v in 0..256 { + let input = (v as u128) << (shift * 8); + let hasher = constructor(0, 0); + alternitives.push(input.get_hash(hasher)); + } + assert_each_byte_differs(base, alternitives); + } +} + +///Ensures that for every bit in the output there is some value for each byte in the key that flips it. +fn test_keys_affect_every_byte(item: H, constructor: impl Fn(u64, u64) -> T) { + let base = item.get_hash(constructor(0, 0)); + for shift in 0..8 { + let mut alternitives1 = vec![]; + let mut alternitives2 = vec![]; + for v in 0..256 { + let input = (v as u64) << (shift * 8); + let hasher1 = constructor(input, 0); + let hasher2 = constructor(0, input); + let h1 = item.get_hash(hasher1); + let h2 = item.get_hash(hasher2); + alternitives1.push(h1); + alternitives2.push(h2); + } + assert_each_byte_differs(base, alternitives1); + assert_each_byte_differs(base, alternitives2); + } +} + +fn assert_each_byte_differs(base: u64, alternitives: Vec) { + let mut changed_bits = 0_u64; + for alternitive in alternitives { + changed_bits |= base ^ alternitive + } + assert_eq!(core::u64::MAX, changed_bits, "Bits changed: {:x}", changed_bits); +} + +fn test_finish_is_consistent(constructor: impl Fn(u64, u64) -> T) { + let mut hasher = constructor(1, 2); + "Foo".hash(&mut hasher); + let a = hasher.finish(); + let b = hasher.finish(); + assert_eq!(a, b); +} + +fn test_single_key_bit_flip(constructor: impl Fn(u64, u64) -> T) { + for bit in 0..64 { + let mut a = constructor(0, 0); + let mut b = constructor(0, 1 << bit); + let mut c = constructor(1 << bit, 0); + "1234".hash(&mut a); + "1234".hash(&mut b); + "1234".hash(&mut c); + assert_sufficiently_different(a.finish(), b.finish(), 2); + assert_sufficiently_different(a.finish(), c.finish(), 2); + assert_sufficiently_different(b.finish(), c.finish(), 2); + let mut a = constructor(0, 0); + let mut b = constructor(0, 1 << bit); + let mut c = constructor(1 << bit, 0); + "12345678".hash(&mut a); + "12345678".hash(&mut b); + "12345678".hash(&mut c); + assert_sufficiently_different(a.finish(), b.finish(), 2); + assert_sufficiently_different(a.finish(), c.finish(), 2); + assert_sufficiently_different(b.finish(), c.finish(), 2); + let mut a = constructor(0, 0); + let mut b = constructor(0, 1 << bit); + let mut c = constructor(1 << bit, 0); + "1234567812345678".hash(&mut a); + "1234567812345678".hash(&mut b); + "1234567812345678".hash(&mut c); + assert_sufficiently_different(a.finish(), b.finish(), 2); + assert_sufficiently_different(a.finish(), c.finish(), 2); + assert_sufficiently_different(b.finish(), c.finish(), 2); + } +} + +fn test_all_bytes_matter(hasher: impl Fn() -> T) { + let mut item = vec![0; 256]; + let base_hash = hash(&item, &hasher); + for pos in 0..256 { + item[pos] = 255; + let hash = hash(&item, &hasher); + assert_ne!(base_hash, hash, "Position {} did not affect output", pos); + item[pos] = 0; + } +} + +fn test_no_pair_collisions(hasher: impl Fn() -> T) { + let base = [0_u64, 0_u64]; + let base_hash = hash(&base, &hasher); + for bitpos1 in 0..64 { + let a = 1_u64 << bitpos1; + for bitpos2 in 0..bitpos1 { + let b = 1_u64 << bitpos2; + let aa = hash(&[a, a], &hasher); + let ab = hash(&[a, b], &hasher); + let ba = hash(&[b, a], &hasher); + let bb = hash(&[b, b], &hasher); + assert_sufficiently_different(base_hash, aa, 3); + assert_sufficiently_different(base_hash, ab, 3); + assert_sufficiently_different(base_hash, ba, 3); + assert_sufficiently_different(base_hash, bb, 3); + assert_sufficiently_different(aa, ab, 3); + assert_sufficiently_different(ab, ba, 3); + assert_sufficiently_different(ba, bb, 3); + assert_sufficiently_different(aa, ba, 3); + assert_sufficiently_different(ab, bb, 3); + assert_sufficiently_different(aa, bb, 3); + } + } +} + +fn hash(b: &H, hasher: &dyn Fn() -> T) -> u64 { + b.get_hash(hasher()) +} + +fn test_single_bit_flip(hasher: impl Fn() -> T) { + let size = 32; + let compare_value = hash(&0u32, &hasher); + for pos in 0..size { + let test_value = hash(&(1u32 << pos), &hasher); + assert_sufficiently_different(compare_value, test_value, 2); + } + let size = 64; + let compare_value = hash(&0u64, &hasher); + for pos in 0..size { + let test_value = hash(&(1u64 << pos), &hasher); + assert_sufficiently_different(compare_value, test_value, 2); + } + let size = 128; + let compare_value = hash(&0u128, &hasher); + for pos in 0..size { + let test_value = hash(&(1u128 << pos), &hasher); + assert_sufficiently_different(compare_value, test_value, 2); + } +} + +fn test_padding_doesnot_collide(hasher: impl Fn() -> T) { + for c in 0..128u8 { + for string in ["", "\0", "\x01", "1234", "12345678", "1234567812345678"].iter() { + let mut short = hasher(); + string.hash(&mut short); + let value = short.finish(); + let mut padded = string.to_string(); + for num in 1..=128 { + let mut long = hasher(); + padded.push(c as char); + padded.hash(&mut long); + let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish()); + assert!( + same_bytes <= 3, + format!("{} bytes of {} -> {:x} vs {:x}", num, c, value, long.finish()) + ); + assert!( + same_nibbles <= 8, + format!("{} bytes of {} -> {:x} vs {:x}", num, c, value, long.finish()) + ); + let flipped_bits = (value ^ long.finish()).count_ones(); + assert!(flipped_bits > 10); + } + if string.len() > 0 { + let mut padded = string[1..].to_string(); + padded.push(c as char); + for num in 2..=128 { + let mut long = hasher(); + padded.push(c as char); + padded.hash(&mut long); + let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish()); + assert!( + same_bytes <= 3, + format!( + "string {:?} + {} bytes of {} -> {:x} vs {:x}", + string, + num, + c, + value, + long.finish() + ) + ); + assert!( + same_nibbles <= 8, + format!( + "string {:?} + {} bytes of {} -> {:x} vs {:x}", + string, + num, + c, + value, + long.finish() + ) + ); + let flipped_bits = (value ^ long.finish()).count_ones(); + assert!(flipped_bits > 10); + } + } + } + } +} + +#[cfg(test)] +mod fallback_tests { + use crate::fallback_hash::*; + use crate::hash_quality_test::*; + + #[test] + fn fallback_single_bit_flip() { + test_single_bit_flip(|| AHasher::test_with_keys(0, 0)) + } + + #[test] + fn fallback_single_key_bit_flip() { + test_single_key_bit_flip(AHasher::test_with_keys) + } + + #[test] + fn fallback_all_bytes_matter() { + test_all_bytes_matter(|| AHasher::test_with_keys(0, 0)); + } + + #[test] + fn fallback_test_no_pair_collisions() { + test_no_pair_collisions(|| AHasher::test_with_keys(0, 0)); + } + + #[test] + fn fallback_test_no_full_collisions() { + test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890)); + } + + #[test] + fn fallback_keys_change_output() { + test_keys_change_output(AHasher::test_with_keys); + } + + #[test] + fn fallback_input_affect_every_byte() { + test_input_affect_every_byte(AHasher::test_with_keys); + } + + #[test] + fn fallback_keys_affect_every_byte() { + test_keys_affect_every_byte(0, AHasher::test_with_keys); + test_keys_affect_every_byte("", AHasher::test_with_keys); + test_keys_affect_every_byte((0, 0), AHasher::test_with_keys); + } + + #[test] + fn fallback_finish_is_consistant() { + test_finish_is_consistent(AHasher::test_with_keys) + } + + #[test] + fn fallback_padding_doesnot_collide() { + test_padding_doesnot_collide(|| AHasher::test_with_keys(0, 0)); + test_padding_doesnot_collide(|| AHasher::test_with_keys(0, 1)); + test_padding_doesnot_collide(|| AHasher::test_with_keys(1, 0)); + test_padding_doesnot_collide(|| AHasher::test_with_keys(1, 1)); + } +} + +///Basic sanity tests of the cypto properties of aHash. +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))] +#[cfg(test)] +mod aes_tests { + use crate::aes_hash::*; + use crate::hash_quality_test::*; + use std::hash::{Hash, Hasher}; + + const BAD_KEY: u64 = 0x5252_5252_5252_5252; //This encrypts to 0. + const BAD_KEY2: u64 = 0x6363_6363_6363_6363; //This decrypts to 0. + + #[test] + fn test_single_bit_in_byte() { + let mut hasher1 = AHasher::new_with_keys(0, 0); + 8_u32.hash(&mut hasher1); + let mut hasher2 = AHasher::new_with_keys(0, 0); + 0_u32.hash(&mut hasher2); + assert_sufficiently_different(hasher1.finish(), hasher2.finish(), 1); + } + + #[test] + fn aes_single_bit_flip() { + test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); + test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + } + + #[test] + fn aes_single_key_bit_flip() { + test_single_key_bit_flip(|k1, k2| AHasher::test_with_keys(k1, k2)) + } + + #[test] + fn aes_all_bytes_matter() { + test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); + test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + } + + #[test] + fn aes_test_no_pair_collisions() { + test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); + test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + } + + #[test] + fn ase_test_no_full_collisions() { + test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890)); + } + + #[test] + fn aes_keys_change_output() { + test_keys_change_output(AHasher::test_with_keys); + } + + #[test] + fn aes_input_affect_every_byte() { + test_input_affect_every_byte(AHasher::test_with_keys); + } + + #[test] + fn aes_keys_affect_every_byte() { + test_keys_affect_every_byte(0, AHasher::test_with_keys); + test_keys_affect_every_byte("", AHasher::test_with_keys); + test_keys_affect_every_byte((0, 0), AHasher::test_with_keys); + } + #[test] + fn aes_finish_is_consistant() { + test_finish_is_consistent(AHasher::test_with_keys) + } + + #[test] + fn aes_padding_doesnot_collide() { + test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY)); + test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)); + } +} diff --git a/third_party/rust/ahash/src/hash_set.rs b/third_party/rust/ahash/src/hash_set.rs new file mode 100644 index 0000000000..2950ec9282 --- /dev/null +++ b/third_party/rust/ahash/src/hash_set.rs @@ -0,0 +1,267 @@ +use std::collections::{hash_set, HashSet}; +use std::fmt::{self, Debug}; +use std::hash::{BuildHasher, Hash}; +use std::iter::FromIterator; +use std::ops::{BitAnd, BitOr, BitXor, Deref, DerefMut, Sub}; + +/// A [`HashSet`](std::collections::HashSet) using [`RandomState`](crate::RandomState) to hash the items. +/// Requires the `std` feature to be enabled. +#[derive(Clone)] +pub struct AHashSet(HashSet); + +impl AHashSet +where + T: Hash + Eq, + S: BuildHasher + Default, +{ + pub fn new() -> Self { + AHashSet(HashSet::with_hasher(S::default())) + } + + pub fn with_capacity(capacity: usize) -> Self { + AHashSet(HashSet::with_capacity_and_hasher(capacity, S::default())) + } +} + +impl AHashSet +where + T: Hash + Eq, + S: BuildHasher, +{ + pub fn with_hasher(hash_builder: S) -> Self { + AHashSet(HashSet::with_hasher(hash_builder)) + } + + pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self { + AHashSet(HashSet::with_capacity_and_hasher(capacity, hash_builder)) + } +} + +impl Deref for AHashSet { + type Target = HashSet; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for AHashSet { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl PartialEq for AHashSet +where + T: Eq + Hash, + S: BuildHasher, +{ + fn eq(&self, other: &AHashSet) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for AHashSet +where + T: Eq + Hash, + S: BuildHasher, +{ +} + +impl BitOr<&AHashSet> for &AHashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + type Output = AHashSet; + + /// Returns the union of `self` and `rhs` as a new `AHashSet`. + /// + /// # Examples + /// + /// ``` + /// use ahash::AHashSet; + /// + /// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// let set = &a | &b; + /// + /// let mut i = 0; + /// let expected = [1, 2, 3, 4, 5]; + /// for x in &set { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn bitor(self, rhs: &AHashSet) -> AHashSet { + AHashSet(self.0.bitor(&rhs.0)) + } +} + +impl BitAnd<&AHashSet> for &AHashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + type Output = AHashSet; + + /// Returns the intersection of `self` and `rhs` as a new `AHashSet`. + /// + /// # Examples + /// + /// ``` + /// use ahash::AHashSet; + /// + /// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: AHashSet<_> = vec![2, 3, 4].into_iter().collect(); + /// + /// let set = &a & &b; + /// + /// let mut i = 0; + /// let expected = [2, 3]; + /// for x in &set { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn bitand(self, rhs: &AHashSet) -> AHashSet { + AHashSet(self.0.bitand(&rhs.0)) + } +} + +impl BitXor<&AHashSet> for &AHashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + type Output = AHashSet; + + /// Returns the symmetric difference of `self` and `rhs` as a new `AHashSet`. + /// + /// # Examples + /// + /// ``` + /// use ahash::AHashSet; + /// + /// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// let set = &a ^ &b; + /// + /// let mut i = 0; + /// let expected = [1, 2, 4, 5]; + /// for x in &set { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn bitxor(self, rhs: &AHashSet) -> AHashSet { + AHashSet(self.0.bitxor(&rhs.0)) + } +} + +impl Sub<&AHashSet> for &AHashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + type Output = AHashSet; + + /// Returns the difference of `self` and `rhs` as a new `AHashSet`. + /// + /// # Examples + /// + /// ``` + /// use ahash::AHashSet; + /// + /// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// let set = &a - &b; + /// + /// let mut i = 0; + /// let expected = [1, 2]; + /// for x in &set { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn sub(self, rhs: &AHashSet) -> AHashSet { + AHashSet(self.0.sub(&rhs.0)) + } +} + +impl Debug for AHashSet +where + T: Eq + Hash + Debug, + S: BuildHasher, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(fmt) + } +} + +impl FromIterator for AHashSet +where + T: Eq + Hash, + S: BuildHasher + Default, +{ + #[inline] + fn from_iter>(iter: I) -> AHashSet { + AHashSet(HashSet::from_iter(iter)) + } +} + +impl<'a, T, S> IntoIterator for &'a AHashSet { + type Item = &'a T; + type IntoIter = hash_set::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + (&self.0).iter() + } +} + +impl IntoIterator for AHashSet { + type Item = T; + type IntoIter = hash_set::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Extend for AHashSet +where + T: Eq + Hash, + S: BuildHasher, +{ + #[inline] + fn extend>(&mut self, iter: I) { + self.0.extend(iter) + } +} + +impl<'a, T, S> Extend<&'a T> for AHashSet +where + T: 'a + Eq + Hash + Copy, + S: BuildHasher, +{ + #[inline] + fn extend>(&mut self, iter: I) { + self.0.extend(iter) + } +} + +impl Default for AHashSet +where + T: Eq + Hash, + S: BuildHasher + Default, +{ + /// Creates an empty `AHashSet` with the `Default` value for the hasher. + #[inline] + fn default() -> AHashSet { + AHashSet(HashSet::default()) + } +} diff --git a/third_party/rust/ahash/src/lib.rs b/third_party/rust/ahash/src/lib.rs new file mode 100644 index 0000000000..542fa35e92 --- /dev/null +++ b/third_party/rust/ahash/src/lib.rs @@ -0,0 +1,203 @@ +//! # aHash +//! +//! This hashing algorithm is intended to be a high performance, (hardware specific), keyed hash function. +//! This can be seen as a DOS resistant alternative to `FxHash`, or a fast equivalent to `SipHash`. +//! It provides a high speed hash algorithm, but where the result is not predictable without knowing a Key. +//! This allows it to be used in a `HashMap` without allowing for the possibility that an malicious user can +//! induce a collision. +//! +//! # How aHash works +//! +//! aHash uses the hardware AES instruction on x86 processors to provide a keyed hash function. +//! aHash is not a cryptographically secure hash. +#![deny(clippy::correctness, clippy::complexity, clippy::perf)] +#![allow(clippy::pedantic, clippy::cast_lossless, clippy::unreadable_literal)] +#![cfg_attr(all(not(test), not(feature = "std")), no_std)] +#![cfg_attr(feature = "specialize", feature(specialization))] + +#[macro_use] +mod convert; + +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))] +mod aes_hash; +mod fallback_hash; +#[cfg(test)] +mod hash_quality_test; + +mod operations; +#[cfg(feature = "std")] +mod hash_map; +#[cfg(feature = "std")] +mod hash_set; +mod random_state; +mod specialize; + +#[cfg(feature = "compile-time-rng")] +use const_random::const_random; + +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))] +pub use crate::aes_hash::AHasher; + +#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri))))] +pub use crate::fallback_hash::AHasher; +pub use crate::random_state::RandomState; + +pub use crate::specialize::CallHasher; + +#[cfg(feature = "std")] +pub use crate::hash_map::AHashMap; +#[cfg(feature = "std")] +pub use crate::hash_set::AHashSet; +use core::hash::Hasher; + +/// Provides a default [Hasher] compile time generated constants for keys. +/// This is typically used in conjunction with [BuildHasherDefault] to create +/// [AHasher]s in order to hash the keys of the map. +/// +/// Generally it is preferable to use [RandomState] instead, so that different +/// hashmaps will have different keys. However if fixed keys are desireable this +/// may be used instead. +/// +/// # Example +/// ``` +/// use std::hash::BuildHasherDefault; +/// use ahash::{AHasher, RandomState}; +/// use std::collections::HashMap; +/// +/// let mut map: HashMap> = HashMap::default(); +/// map.insert(12, 34); +/// ``` +/// +/// [BuildHasherDefault]: std::hash::BuildHasherDefault +/// [Hasher]: std::hash::Hasher +/// [HashMap]: std::collections::HashMap +impl Default for AHasher { + + /// Constructs a new [AHasher] with compile time generated constants for keys if the + /// `compile-time-rng`feature is enabled. Otherwise the keys will be fixed constants. + /// This means the keys will be the same from one instance to another, + /// but different from build to the next. So if it is possible for a potential + /// attacker to have access to the compiled binary it would be better + /// to specify keys generated at runtime. + /// + /// # Examples + /// + /// ``` + /// use ahash::AHasher; + /// use std::hash::Hasher; + /// + /// let mut hasher_1 = AHasher::default(); + /// let mut hasher_2 = AHasher::default(); + /// + /// hasher_1.write_u32(1234); + /// hasher_2.write_u32(1234); + /// + /// assert_eq!(hasher_1.finish(), hasher_2.finish()); + /// ``` + #[inline] + #[cfg(feature = "compile-time-rng")] + fn default() -> AHasher { + AHasher::new_with_keys(const_random!(u128), const_random!(u128)) + } + + /// Constructs a new [AHasher] with compile time generated constants for keys if the + /// `compile-time-rng`feature is enabled. Otherwise the keys will be fixed constants. + /// This means the keys will be the same from one instance to another, + /// but different from build to the next. So if it is possible for a potential + /// attacker to have access to the compiled binary it would be better + /// to specify keys generated at runtime. + /// + /// # Examples + /// + /// ``` + /// use ahash::AHasher; + /// use std::hash::Hasher; + /// + /// let mut hasher_1 = AHasher::default(); + /// let mut hasher_2 = AHasher::default(); + /// + /// hasher_1.write_u32(1234); + /// hasher_2.write_u32(1234); + /// + /// assert_eq!(hasher_1.finish(), hasher_2.finish()); + /// ``` + #[inline] + #[cfg(not(feature = "compile-time-rng"))] + fn default() -> AHasher { + const K1: u128 = (random_state::INIT_SEED[0] as u128).wrapping_mul(random_state::MULTIPLE as u128); + const K2: u128 = (random_state::INIT_SEED[1] as u128).wrapping_mul(random_state::MULTIPLE as u128); + AHasher::new_with_keys(K1, K2) + } +} + +/// Used for specialization. (Sealed) +pub(crate) trait HasherExt: Hasher { + #[doc(hidden)] + fn hash_u64(self, value: u64) -> u64; + + #[doc(hidden)] + fn short_finish(&self) -> u64; +} + +impl HasherExt for T { + #[inline] + #[cfg(feature = "specialize")] + default fn hash_u64(self, value: u64) -> u64 { + value.get_hash(self) + } + #[inline] + #[cfg(not(feature = "specialize"))] + fn hash_u64(self, value: u64) -> u64 { + value.get_hash(self) + } + #[inline] + #[cfg(feature = "specialize")] + default fn short_finish(&self) -> u64 { + self.finish() + } + #[inline] + #[cfg(not(feature = "specialize"))] + fn short_finish(&self) -> u64 { + self.finish() + } +} + +// #[inline(never)] +// #[doc(hidden)] +// pub fn hash_test(input: &[u8]) -> u64 { +// let a = AHasher::new_with_keys(11111111111_u128, 2222222222_u128); +// input.get_hash(a) +// } + +#[cfg(test)] +mod test { + use crate::convert::Convert; + use crate::*; + use std::collections::HashMap; + + #[cfg(feature = "std")] + #[test] + fn test_default_builder() { + use core::hash::BuildHasherDefault; + + let mut map = HashMap::>::default(); + map.insert(1, 3); + } + #[test] + fn test_builder() { + let mut map = HashMap::::default(); + map.insert(1, 3); + } + + #[test] + fn test_conversion() { + let input: &[u8] = b"dddddddd"; + let bytes: u64 = as_array!(input, 8).convert(); + assert_eq!(bytes, 0x6464646464646464); + } + + #[test] + fn test_ahasher_construction() { + let _ = AHasher::new_with_keys(1234, 5678); + } +} diff --git a/third_party/rust/ahash/src/operations.rs b/third_party/rust/ahash/src/operations.rs new file mode 100644 index 0000000000..0646c446cd --- /dev/null +++ b/third_party/rust/ahash/src/operations.rs @@ -0,0 +1,277 @@ +use crate::convert::*; + +/// This is a constant with a lot of special properties found by automated search. +/// See the unit tests below. (Below are alternative values) +#[cfg(all(target_feature = "ssse3", not(miri)))] +const SHUFFLE_MASK: u128 = 0x020a0700_0c01030e_050f0d08_06090b04_u128; +//const SHUFFLE_MASK: u128 = 0x000d0702_0a040301_05080f0c_0e0b0609_u128; +//const SHUFFLE_MASK: u128 = 0x040A0700_030E0106_0D050F08_020B0C09_u128; + +pub(crate) const fn folded_multiply(s: u64, by: u64) -> u64 { + let result = (s as u128).wrapping_mul(by as u128); + ((result & 0xffff_ffff_ffff_ffff) as u64) ^ ((result >> 64) as u64) +} + +#[inline(always)] +pub(crate) fn shuffle(a: u128) -> u128 { + #[cfg(all(target_feature = "ssse3", not(miri)))] + { + use core::mem::transmute; + #[cfg(target_arch = "x86")] + use core::arch::x86::*; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::*; + unsafe { + transmute(_mm_shuffle_epi8(transmute(a), transmute(SHUFFLE_MASK))) + } + } + #[cfg(not(all(target_feature = "ssse3", not(miri))))] + { + a.swap_bytes() + } +} + +#[allow(unused)] //not used by fallback +#[inline(always)] +pub(crate) fn add_and_shuffle(a: u128, b: u128) -> u128 { + let sum = add_by_64s(a.convert(), b.convert()); + shuffle(sum.convert()) +} + +#[allow(unused)] //not used by fallbac +#[inline(always)] +pub(crate) fn shuffle_and_add(base: u128, to_add: u128) -> u128 { + let shuffled: [u64; 2] = shuffle(base).convert(); + add_by_64s(shuffled, to_add.convert()).convert() +} + +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri)))] +#[inline(always)] +pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] { + use core::mem::transmute; + unsafe { + #[cfg(target_arch = "x86")] + use core::arch::x86::*; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::*; + transmute(_mm_add_epi64(transmute(a), transmute(b))) + } +} + +#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri))))] +#[inline(always)] +pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] { + [a[0].wrapping_add(b[0]), a[1].wrapping_add(b[1])] +} + +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))] +#[allow(unused)] +#[inline(always)] +pub(crate) fn aesenc(value: u128, xor: u128) -> u128 { + #[cfg(target_arch = "x86")] + use core::arch::x86::*; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::*; + use core::mem::transmute; + unsafe { + let value = transmute(value); + transmute(_mm_aesenc_si128(value, transmute(xor))) + } +} +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))] +#[allow(unused)] +#[inline(always)] +pub(crate) fn aesdec(value: u128, xor: u128) -> u128 { + #[cfg(target_arch = "x86")] + use core::arch::x86::*; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::*; + use core::mem::transmute; + unsafe { + let value = transmute(value); + transmute(_mm_aesdec_si128(value, transmute(xor))) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::convert::Convert; + + // This is code to search for the shuffle constant + // + //thread_local! { static MASK: Cell = Cell::new(0); } + // + // fn shuffle(a: u128) -> u128 { + // use std::intrinsics::transmute; + // #[cfg(target_arch = "x86")] + // use core::arch::x86::*; + // #[cfg(target_arch = "x86_64")] + // use core::arch::x86_64::*; + // MASK.with(|mask| { + // unsafe { transmute(_mm_shuffle_epi8(transmute(a), transmute(mask.get()))) } + // }) + // } + // + // #[test] + // fn find_shuffle() { + // use rand::prelude::*; + // use SliceRandom; + // use std::panic; + // use std::io::Write; + // + // let mut value: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15]; + // let mut rand = thread_rng(); + // let mut successful_list = HashMap::new(); + // for _attempt in 0..10000000 { + // rand.shuffle(&mut value); + // let test_val = value.convert(); + // MASK.with(|mask| { + // mask.set(test_val); + // }); + // if let Ok(successful) = panic::catch_unwind(|| { + // test_shuffle_does_not_collide_with_aes(); + // test_shuffle_moves_high_bits(); + // test_shuffle_moves_every_value(); + // //test_shuffle_does_not_loop(); + // value + // }) { + // let successful: u128 = successful.convert(); + // successful_list.insert(successful, iters_before_loop()); + // } + // } + // let write_file = File::create("/tmp/output").unwrap(); + // let mut writer = BufWriter::new(&write_file); + // + // for success in successful_list { + // writeln!(writer, "Found successful: {:x?} - {:?}", success.0, success.1); + // } + // } + // + // fn iters_before_loop() -> u32 { + // let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF; + // let mut shuffled = shuffle(numbered); + // let mut count = 0; + // loop { + // // println!("{:>16x}", shuffled); + // if numbered == shuffled { + // break; + // } + // count += 1; + // shuffled = shuffle(shuffled); + // } + // count + // } + + #[cfg(all( + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "ssse3", + target_feature = "aes", + not(miri) + ))] + #[test] + fn test_shuffle_does_not_collide_with_aes() { + let mut value: [u8; 16] = [0; 16]; + let zero_mask_enc = aesenc(0, 0); + let zero_mask_dec = aesdec(0, 0); + for index in 0..16 { + value[index] = 1; + let excluded_positions_enc: [u8; 16] = aesenc(value.convert(), zero_mask_enc).convert(); + let excluded_positions_dec: [u8; 16] = aesdec(value.convert(), zero_mask_dec).convert(); + let actual_location: [u8; 16] = shuffle(value.convert()).convert(); + for pos in 0..16 { + if actual_location[pos] != 0 { + assert_eq!( + 0, excluded_positions_enc[pos], + "Forward Overlap between {:?} and {:?} at {}", + excluded_positions_enc, actual_location, index + ); + assert_eq!( + 0, excluded_positions_dec[pos], + "Reverse Overlap between {:?} and {:?} at {}", + excluded_positions_dec, actual_location, index + ); + } + } + value[index] = 0; + } + } + + #[test] + fn test_shuffle_contains_each_value() { + let value: [u8; 16] = 0x00010203_04050607_08090A0B_0C0D0E0F_u128.convert(); + let shuffled: [u8; 16] = shuffle(value.convert()).convert(); + for index in 0..16_u8 { + assert!(shuffled.contains(&index), "Value is missing {}", index); + } + } + + #[test] + fn test_shuffle_moves_every_value() { + let mut value: [u8; 16] = [0; 16]; + for index in 0..16 { + value[index] = 1; + let shuffled: [u8; 16] = shuffle(value.convert()).convert(); + assert_eq!(0, shuffled[index], "Value is not moved {}", index); + value[index] = 0; + } + } + + #[test] + fn test_shuffle_moves_high_bits() { + assert!( + shuffle(1) > (1_u128 << 80), + "Low bits must be moved to other half {:?} -> {:?}", + 0, + shuffle(1) + ); + + assert!( + shuffle(1_u128 << 58) >= (1_u128 << 64), + "High bits must be moved to other half {:?} -> {:?}", + 7, + shuffle(1_u128 << 58) + ); + assert!( + shuffle(1_u128 << 58) < (1_u128 << 112), + "High bits must not remain high {:?} -> {:?}", + 7, + shuffle(1_u128 << 58) + ); + assert!( + shuffle(1_u128 << 64) < (1_u128 << 64), + "Low bits must be moved to other half {:?} -> {:?}", + 8, + shuffle(1_u128 << 64) + ); + assert!( + shuffle(1_u128 << 64) >= (1_u128 << 16), + "Low bits must not remain low {:?} -> {:?}", + 8, + shuffle(1_u128 << 64) + ); + + assert!( + shuffle(1_u128 << 120) < (1_u128 << 50), + "High bits must be moved to low half {:?} -> {:?}", + 15, + shuffle(1_u128 << 120) + ); + } + + #[cfg(all( + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "ssse3", + not(miri) + ))] + #[test] + fn test_shuffle_does_not_loop() { + let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF; + let mut shuffled = shuffle(numbered); + for count in 0..100 { + // println!("{:>16x}", shuffled); + assert_ne!(numbered, shuffled, "Equal after {} vs {:x}", count, shuffled); + shuffled = shuffle(shuffled); + } + } +} diff --git a/third_party/rust/ahash/src/random_state.rs b/third_party/rust/ahash/src/random_state.rs new file mode 100644 index 0000000000..0936556c38 --- /dev/null +++ b/third_party/rust/ahash/src/random_state.rs @@ -0,0 +1,153 @@ +use crate::convert::Convert; +use crate::AHasher; +use core::fmt; +use core::hash::BuildHasher; +use core::sync::atomic::AtomicUsize; +use core::sync::atomic::Ordering; + +use crate::operations::folded_multiply; +#[cfg(all(feature = "compile-time-rng", not(test)))] +use const_random::const_random; + +///This constant come from Kunth's prng +pub(crate) const MULTIPLE: u64 = 6364136223846793005; +pub(crate) const INCREMENT: u64 = 1442695040888963407; + +// Const random provides randomized starting key with no runtime cost. +#[cfg(all(feature = "compile-time-rng", not(test)))] +pub(crate) const INIT_SEED: [u64; 2] = [const_random!(u64), const_random!(u64)]; + +#[cfg(any(not(feature = "compile-time-rng"), test))] +pub(crate) const INIT_SEED: [u64; 2] = [0x2360_ED05_1FC6_5DA4, 0x4385_DF64_9FCC_F645]; //From PCG-64 + +#[cfg(all(feature = "compile-time-rng", not(test)))] +static SEED: AtomicUsize = AtomicUsize::new(const_random!(u64) as usize); + +#[cfg(any(not(feature = "compile-time-rng"), test))] +static SEED: AtomicUsize = AtomicUsize::new(INCREMENT as usize); + +/// Provides a [Hasher] factory. This is typically used (e.g. by [HashMap]) to create +/// [AHasher]s in order to hash the keys of the map. See `build_hasher` below. +/// +/// [build_hasher]: ahash:: +/// [Hasher]: std::hash::Hasher +/// [BuildHasher]: std::hash::BuildHasher +/// [HashMap]: std::collections::HashMap +#[derive(Clone)] +pub struct RandomState { + pub(crate) k0: u64, + pub(crate) k1: u64, + pub(crate) k2: u64, + pub(crate) k3: u64, +} + +impl fmt::Debug for RandomState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("RandomState { .. }") + } +} + +impl RandomState { + #[inline] + pub fn new() -> RandomState { + //Using a self pointer. When running with ASLR this is a random value. + let previous = SEED.load(Ordering::Relaxed) as u64; + let stack_mem_loc = &previous as *const _ as u64; + //This is similar to the update function in the fallback. + //only one multiply is needed because memory locations are not under an attackers control. + let current_seed = previous + .wrapping_add(stack_mem_loc) + .wrapping_mul(MULTIPLE) + .rotate_right(31); + SEED.store(current_seed as usize, Ordering::Relaxed); + let (k0, k1, k2, k3) = scramble_keys(&SEED as *const _ as u64, current_seed); + RandomState { k0, k1, k2, k3 } + } + + /// Allows for explicitly setting the seeds to used. + pub const fn with_seeds(k0: u64, k1: u64) -> RandomState { + let (k0, k1, k2, k3) = scramble_keys(k0, k1); + RandomState { k0, k1, k2, k3 } + } +} + +/// This is based on the fallback hasher +#[inline] +pub(crate) const fn scramble_keys(a: u64, b: u64) -> (u64, u64, u64, u64) { + let k1 = folded_multiply(INIT_SEED[0] ^ a, MULTIPLE).wrapping_add(b); + let k2 = folded_multiply(INIT_SEED[0] ^ b, MULTIPLE).wrapping_add(a); + let k3 = folded_multiply(INIT_SEED[1] ^ a, MULTIPLE).wrapping_add(b); + let k4 = folded_multiply(INIT_SEED[1] ^ b, MULTIPLE).wrapping_add(a); + let combined = folded_multiply(a ^ b, MULTIPLE).wrapping_add(INCREMENT); + let rot1 = (combined & 63) as u32; + let rot2 = ((combined >> 16) & 63) as u32; + let rot3 = ((combined >> 32) & 63) as u32; + let rot4 = ((combined >> 48) & 63) as u32; + ( + k1.rotate_left(rot1), + k2.rotate_left(rot2), + k3.rotate_left(rot3), + k4.rotate_left(rot4), + ) +} + +impl Default for RandomState { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl BuildHasher for RandomState { + type Hasher = AHasher; + + /// Constructs a new [AHasher] with keys based on compile time generated constants** and the location + /// this object was constructed at in memory. This means that two different [BuildHasher]s will will generate + /// [AHasher]s that will return different hashcodes, but [Hasher]s created from the same [BuildHasher] + /// will generate the same hashes for the same input data. + /// + /// ** - only if the `compile-time-rng` feature is enabled. + /// + /// # Examples + /// + /// ``` + /// use ahash::{AHasher, RandomState}; + /// use std::hash::{Hasher, BuildHasher}; + /// + /// let build_hasher = RandomState::new(); + /// let mut hasher_1 = build_hasher.build_hasher(); + /// let mut hasher_2 = build_hasher.build_hasher(); + /// + /// hasher_1.write_u32(1234); + /// hasher_2.write_u32(1234); + /// + /// assert_eq!(hasher_1.finish(), hasher_2.finish()); + /// + /// let other_build_hasher = RandomState::new(); + /// let mut different_hasher = other_build_hasher.build_hasher(); + /// different_hasher.write_u32(1234); + /// assert_ne!(different_hasher.finish(), hasher_1.finish()); + /// ``` + /// [Hasher]: std::hash::Hasher + /// [BuildHasher]: std::hash::BuildHasher + /// [HashMap]: std::collections::HashMap + #[inline] + fn build_hasher(&self) -> AHasher { + AHasher::new_with_keys([self.k0, self.k1].convert(), [self.k2, self.k3].convert()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_const_rand_disabled() { + assert_eq!(INIT_SEED, [0x2360_ED05_1FC6_5DA4, 0x4385_DF64_9FCC_F645]); + } + + #[test] + fn test_with_seeds_const() { + const _CONST_RANDOM_STATE: RandomState = RandomState::with_seeds(17, 19); + } +} diff --git a/third_party/rust/ahash/src/specialize.rs b/third_party/rust/ahash/src/specialize.rs new file mode 100644 index 0000000000..2c0bc2d8af --- /dev/null +++ b/third_party/rust/ahash/src/specialize.rs @@ -0,0 +1,162 @@ +#[cfg(feature = "specialize")] +use crate::HasherExt; +use core::hash::Hash; +use core::hash::Hasher; + +/// Provides a way to get an optimized hasher for a given data type. +/// Rather than using a Hasher generically which can hash any value, this provides a way to get a specialized hash +/// for a specific type. So this may be faster for primitive types. It does however consume the hasher in the process. +/// #Example +/// ``` +/// use std::hash::BuildHasher; +/// use ahash::RandomState; +/// use ahash::CallHasher; +/// +/// let hash_builder = RandomState::new(); +/// //... +/// let value = 17; +/// let hash = value.get_hash(hash_builder.build_hasher()); +/// ``` +pub trait CallHasher: Hash { + fn get_hash(&self, hasher: H) -> u64; +} + +#[cfg(not(feature = "specialize"))] +impl CallHasher for T +where + T: Hash, +{ + #[inline] + fn get_hash(&self, mut hasher: H) -> u64 { + self.hash(&mut hasher); + hasher.finish() + } +} + +#[cfg(feature = "specialize")] +impl CallHasher for T +where + T: Hash, +{ + #[inline] + default fn get_hash(&self, mut hasher: H) -> u64 { + self.hash(&mut hasher); + hasher.finish() + } +} + +macro_rules! call_hasher_impl { + ($typ:ty) => { + #[cfg(feature = "specialize")] + impl CallHasher for $typ { + #[inline] + fn get_hash(&self, hasher: H) -> u64 { + hasher.hash_u64(*self as u64) + } + } + }; +} +call_hasher_impl!(u8); +call_hasher_impl!(u16); +call_hasher_impl!(u32); +call_hasher_impl!(u64); +call_hasher_impl!(i8); +call_hasher_impl!(i16); +call_hasher_impl!(i32); +call_hasher_impl!(i64); + +#[cfg(feature = "specialize")] +impl CallHasher for u128 { + #[inline] + fn get_hash(&self, mut hasher: H) -> u64 { + hasher.write_u128(*self); + hasher.short_finish() + } +} + +#[cfg(feature = "specialize")] +impl CallHasher for i128 { + #[inline] + fn get_hash(&self, mut hasher: H) -> u64 { + hasher.write_u128(*self as u128); + hasher.short_finish() + } +} + +#[cfg(feature = "specialize")] +impl CallHasher for [u8] { + #[inline] + fn get_hash(&self, mut hasher: H) -> u64 { + hasher.write(self); + hasher.finish() + } +} + +#[cfg(all(feature = "specialize", feature = "std"))] +impl CallHasher for Vec { + #[inline] + fn get_hash(&self, mut hasher: H) -> u64 { + hasher.write(self); + hasher.finish() + } +} + +#[cfg(feature = "specialize")] +impl CallHasher for str { + #[inline] + fn get_hash(&self, mut hasher: H) -> u64 { + hasher.write(self.as_bytes()); + hasher.finish() + } +} + +#[cfg(all(feature = "specialize", feature = "std"))] +impl CallHasher for String { + #[inline] + fn get_hash(&self, mut hasher: H) -> u64 { + hasher.write(self.as_bytes()); + hasher.finish() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::*; + + #[test] + #[cfg(feature = "specialize")] + pub fn test_specialized_invoked() { + let shortened = 0_u64.get_hash(AHasher::new_with_keys(1, 2)); + let mut hasher = AHasher::new_with_keys(1, 2); + 0_u64.hash(&mut hasher); + assert_ne!(hasher.finish(), shortened); + } + + /// Tests that some non-trivial transformation takes place. + #[test] + pub fn test_input_processed() { + let hasher = || AHasher::new_with_keys(3, 2); + assert_ne!(0, 0_u64.get_hash(hasher())); + assert_ne!(1, 0_u64.get_hash(hasher())); + assert_ne!(2, 0_u64.get_hash(hasher())); + assert_ne!(3, 0_u64.get_hash(hasher())); + assert_ne!(4, 0_u64.get_hash(hasher())); + assert_ne!(5, 0_u64.get_hash(hasher())); + + assert_ne!(0, 1_u64.get_hash(hasher())); + assert_ne!(1, 1_u64.get_hash(hasher())); + assert_ne!(2, 1_u64.get_hash(hasher())); + assert_ne!(3, 1_u64.get_hash(hasher())); + assert_ne!(4, 1_u64.get_hash(hasher())); + assert_ne!(5, 1_u64.get_hash(hasher())); + + let xored = 0_u64.get_hash(hasher()) ^ 1_u64.get_hash(hasher()); + assert_ne!(0, xored); + assert_ne!(1, xored); + assert_ne!(2, xored); + assert_ne!(3, xored); + assert_ne!(4, xored); + assert_ne!(5, xored); + } +} diff --git a/third_party/rust/ahash/tests/bench.rs b/third_party/rust/ahash/tests/bench.rs new file mode 100644 index 0000000000..c03a0f56ee --- /dev/null +++ b/third_party/rust/ahash/tests/bench.rs @@ -0,0 +1,224 @@ +use ahash::{AHasher, CallHasher}; +use criterion::*; +use fxhash::FxHasher; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes"))] +fn aeshash(b: &H) -> u64 { + let hasher = AHasher::default(); + b.get_hash(hasher) +} +#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes")))] +fn aeshash(_b: &H) -> u64 { + panic!("aes must be enabled") +} + +#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes")))] +fn fallbackhash(b: &H) -> u64 { + let hasher = AHasher::default(); + b.get_hash(hasher) +} +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes"))] +fn fallbackhash(_b: &H) -> u64 { + panic!("aes must be disabled") +} + +fn fnvhash(b: &H) -> u64 { + let mut hasher = fnv::FnvHasher::default(); + b.hash(&mut hasher); + hasher.finish() +} + +fn siphash(b: &H) -> u64 { + let mut hasher = DefaultHasher::default(); + b.hash(&mut hasher); + hasher.finish() +} + +fn fxhash(b: &H) -> u64 { + let mut hasher = FxHasher::default(); + b.hash(&mut hasher); + hasher.finish() +} + +fn seahash(b: &H) -> u64 { + let mut hasher = seahash::SeaHasher::default(); + b.hash(&mut hasher); + hasher.finish() +} + +const STRING_LENGTHS: [u32; 12] = [1, 3, 4, 7, 8, 15, 16, 24, 33, 68, 132, 1024]; + +fn gen_strings() -> Vec { + STRING_LENGTHS + .iter() + .map(|len| { + let mut string = String::default(); + for pos in 1..=*len { + let c = (48 + (pos % 10) as u8) as char; + string.push(c); + } + string + }) + .collect() +} + +const U8_VALUES: [u8; 1] = [123]; +const U16_VALUES: [u16; 1] = [1234]; +const U32_VALUES: [u32; 1] = [12345678]; +const U64_VALUES: [u64; 1] = [1234567890123456]; +const U128_VALUES: [u128; 1] = [12345678901234567890123456789012]; + +fn bench_ahash(c: &mut Criterion) { + c.bench( + "aeshash", + ParameterizedBenchmark::new("u8", |b, &s| b.iter(|| black_box(aeshash(s))), &U8_VALUES), + ); + c.bench( + "aeshash", + ParameterizedBenchmark::new("u16", |b, &s| b.iter(|| black_box(aeshash(s))), &U16_VALUES), + ); + c.bench( + "aeshash", + ParameterizedBenchmark::new("u32", |b, &s| b.iter(|| black_box(aeshash(s))), &U32_VALUES), + ); + c.bench( + "aeshash", + ParameterizedBenchmark::new("u64", |b, &s| b.iter(|| black_box(aeshash(s))), &U64_VALUES), + ); + c.bench( + "aeshash", + ParameterizedBenchmark::new("u128", |b, &s| b.iter(|| black_box(aeshash(s))), &U128_VALUES), + ); + c.bench( + "aeshash", + ParameterizedBenchmark::new("string", |b, s| b.iter(|| black_box(aeshash(s))), gen_strings()), + ); +} + +fn bench_fallback(c: &mut Criterion) { + c.bench( + "fallback", + ParameterizedBenchmark::new("u8", |b, &s| b.iter(|| black_box(fallbackhash(s))), &U8_VALUES), + ); + c.bench( + "fallback", + ParameterizedBenchmark::new("u16", |b, &s| b.iter(|| black_box(fallbackhash(s))), &U16_VALUES), + ); + c.bench( + "fallback", + ParameterizedBenchmark::new("u32", |b, &s| b.iter(|| black_box(fallbackhash(s))), &U32_VALUES), + ); + c.bench( + "fallback", + ParameterizedBenchmark::new("u64", |b, &s| b.iter(|| black_box(fallbackhash(s))), &U64_VALUES), + ); + c.bench( + "fallback", + ParameterizedBenchmark::new("u128", |b, &s| b.iter(|| black_box(fallbackhash(s))), &U128_VALUES), + ); + c.bench( + "fallback", + ParameterizedBenchmark::new("string", |b, s| b.iter(|| black_box(fallbackhash(s))), gen_strings()), + ); +} + +fn bench_fx(c: &mut Criterion) { + c.bench( + "fx", + ParameterizedBenchmark::new("u8", |b, &s| b.iter(|| black_box(fxhash(s))), &U8_VALUES), + ); + c.bench( + "fx", + ParameterizedBenchmark::new("u16", |b, &s| b.iter(|| black_box(fxhash(s))), &U16_VALUES), + ); + c.bench( + "fx", + ParameterizedBenchmark::new("u32", |b, &s| b.iter(|| black_box(fxhash(s))), &U32_VALUES), + ); + c.bench( + "fx", + ParameterizedBenchmark::new("u64", |b, &s| b.iter(|| black_box(fxhash(s))), &U64_VALUES), + ); + c.bench( + "fx", + ParameterizedBenchmark::new("u128", |b, &s| b.iter(|| black_box(fxhash(s))), &U128_VALUES), + ); + c.bench( + "fx", + ParameterizedBenchmark::new("string", |b, s| b.iter(|| black_box(fxhash(s))), gen_strings()), + ); +} + +fn bench_fnv(c: &mut Criterion) { + c.bench( + "fnv", + ParameterizedBenchmark::new("u8", |b, &s| b.iter(|| black_box(fnvhash(s))), &U8_VALUES), + ); + c.bench( + "fnv", + ParameterizedBenchmark::new("u16", |b, &s| b.iter(|| black_box(fnvhash(s))), &U16_VALUES), + ); + c.bench( + "fnv", + ParameterizedBenchmark::new("u32", |b, &s| b.iter(|| black_box(fnvhash(s))), &U32_VALUES), + ); + c.bench( + "fnv", + ParameterizedBenchmark::new("u64", |b, &s| b.iter(|| black_box(fnvhash(s))), &U64_VALUES), + ); + c.bench( + "fnv", + ParameterizedBenchmark::new("u128", |b, &s| b.iter(|| black_box(fnvhash(s))), &U128_VALUES), + ); + c.bench( + "fnv", + ParameterizedBenchmark::new("string", |b, s| b.iter(|| black_box(fnvhash(s))), gen_strings()), + ); +} + +fn bench_sea(c: &mut Criterion) { + c.bench( + "sea", + ParameterizedBenchmark::new("string", |b, s| b.iter(|| black_box(seahash(s))), gen_strings()), + ); +} + +fn bench_sip(c: &mut Criterion) { + c.bench( + "sip", + ParameterizedBenchmark::new("u8", |b, &s| b.iter(|| black_box(siphash(s))), &U8_VALUES), + ); + c.bench( + "sip", + ParameterizedBenchmark::new("u16", |b, &s| b.iter(|| black_box(siphash(s))), &U16_VALUES), + ); + c.bench( + "sip", + ParameterizedBenchmark::new("u32", |b, &s| b.iter(|| black_box(siphash(s))), &U32_VALUES), + ); + c.bench( + "sip", + ParameterizedBenchmark::new("u64", |b, &s| b.iter(|| black_box(siphash(s))), &U64_VALUES), + ); + c.bench( + "sip", + ParameterizedBenchmark::new("u128", |b, &s| b.iter(|| black_box(siphash(s))), &U128_VALUES), + ); + c.bench( + "sip", + ParameterizedBenchmark::new("string", |b, s| b.iter(|| black_box(siphash(s))), gen_strings()), + ); +} + +criterion_main!(benches); +criterion_group!( + benches, + bench_ahash, + bench_fallback, + bench_fx, + bench_fnv, + bench_sea, + bench_sip +); diff --git a/third_party/rust/ahash/tests/map_tests.rs b/third_party/rust/ahash/tests/map_tests.rs new file mode 100644 index 0000000000..70e4a86cfd --- /dev/null +++ b/third_party/rust/ahash/tests/map_tests.rs @@ -0,0 +1,204 @@ +use std::hash::{Hash, Hasher}; + +use criterion::*; +use fxhash::FxHasher; + +use ahash::{AHasher, CallHasher}; + +fn gen_word_pairs() -> Vec { + let words: Vec<_> = r#" +a, ability, able, about, above, accept, according, account, across, act, action, +activity, actually, add, address, administration, admit, adult, affect, after, +again, against, age, agency, agent, ago, agree, agreement, ahead, air, all, +allow, almost, alone, along, already, also, although, always, American, among, +amount, analysis, and, animal, another, answer, any, anyone, anything, appear, +apply, approach, area, argue, arm, around, arrive, art, article, artist, as, +ask, assume, at, attack, attention, attorney, audience, author, authority, +available, avoid, away, baby, back, bad, bag, ball, bank, bar, base, be, beat, +beautiful, because, become, bed, before, begin, behavior, behind, believe, +benefit, best, better, between, beyond, big, bill, billion, bit, black, blood, +blue, board, body, book, born, both, box, boy, break, bring, brother, budget, +build, building, business, but, buy, by, call, camera, campaign, can, cancer, +candidate, capital, car, card, care, career, carry, case, catch, cause, cell, +center, central, century, certain, certainly, chair, challenge, chance, change, +character, charge, check, child, choice, choose, church, citizen, city, civil, +claim, class, clear, clearly, close, coach, cold, collection, college, color, +come, commercial, common, community, company, compare, computer, concern, +condition, conference, Congress, consider, consumer, contain, continue, control, +cost, could, country, couple, course, court, cover, create, crime, cultural, +culture, cup, current, customer, cut, dark, data, daughter, day, dead, deal, +death, debate, decade, decide, decision, deep, defense, degree, Democrat, +democratic, describe, design, despite, detail, determine, develop, development, +die, difference, different, difficult, dinner, direction, director, discover, +discuss, discussion, disease, do, doctor, dog, door, down, draw, dream, drive, +drop, drug, during, each, early, east, easy, eat, economic, economy, edge, +education, effect, effort, eight, either, election, else, employee, end, energy, +enjoy, enough, enter, entire, environment, environmental, especially, establish, +even, evening, event, ever, every, everybody, everyone, everything, evidence, +exactly, example, executive, exist, expect, experience, expert, explain, eye, +face, fact, factor, fail, fall, family, far, fast, father, fear, federal, feel, +feeling, few, field, fight, figure, fill, film, final, finally, financial, find, +fine, finger, finish, fire, firm, first, fish, five, floor, fly, focus, follow, +food, foot, for, force, foreign, forget, form, former, forward, four, free, +friend, from, front, full, fund, future, game, garden, gas, general, generation, +get, girl, give, glass, go, goal, good, government, great, green, ground, group, +grow, growth, guess, gun, guy, hair, half, hand, hang, happen, happy, hard, +have, he, head, health, hear, heart, heat, heavy, help, her, here, herself, +high, him, himself, his, history, hit, hold, home, hope, hospital, hot, hotel, +hour, house, how, however, huge, human, hundred, husband, I, idea, identify, if, +image, imagine, impact, important, improve, in, include, including, increase, +indeed, indicate, individual, industry, information, inside, instead, +institution, interest, interesting, international, interview, into, investment, +involve, issue, it, item, its, itself, job, join, just, keep, key, kid, kill, +kind, kitchen, know, knowledge, land, language, large, last, late, later, laugh, +law, lawyer, lay, lead, leader, learn, least, leave, left, leg, legal, less, +let, letter, level, lie, life, light, like, likely, line, list, listen, little, +live, local, long, look, lose, loss, lot, love, low, machine, magazine, main, +maintain, major, majority, make, man, manage, management, manager, many, market, +marriage, material, matter, may, maybe, me, mean, measure, media, medical, meet, +meeting, member, memory, mention, message, method, middle, might, military, +million, mind, minute, miss, mission, model, modern, moment, money, month, more, +morning, most, mother, mouth, move, movement, movie, Mr, Mrs, much, music, must, +my, myself, name, nation, national, natural, nature, near, nearly, necessary, +need, network, never, new, news, newspaper, next, nice, night, no, none, nor, +north, not, note, nothing, notice, now, n't, number, occur, of, off, offer, +office, officer, official, often, oh, oil, ok, old, on, once, one, only, onto, +open, operation, opportunity, option, or, order, organization, other, others, +our, out, outside, over, own, owner, page, pain, painting, paper, parent, part, +participant, particular, particularly, partner, party, pass, past, patient, +pattern, pay, peace, people, per, perform, performance, perhaps, period, person, +personal, phone, physical, pick, picture, piece, place, plan, plant, play, +player, PM, point, police, policy, political, politics, poor, popular, +population, position, positive, possible, power, practice, prepare, present, +president, pressure, pretty, prevent, price, private, probably, problem, +process, produce, product, production, professional, professor, program, +project, property, protect, prove, provide, public, pull, purpose, push, put, +quality, question, quickly, quite, race, radio, raise, range, rate, rather, +reach, read, ready, real, reality, realize, really, reason, receive, recent, +recently, recognize, record, red, reduce, reflect, region, relate, relationship, +religious, remain, remember, remove, report, represent, Republican, require, +research, resource, respond, response, responsibility, rest, result, return, +reveal, rich, right, rise, risk, road, rock, role, room, rule, run, safe, same, +save, say, scene, school, science, scientist, score, sea, season, seat, second, +section, security, see, seek, seem, sell, send, senior, sense, series, serious, +serve, service, set, seven, several, sex, sexual, shake, share, she, shoot, +short, shot, should, shoulder, show, side, sign, significant, similar, simple, +simply, since, sing, single, sister, sit, site, situation, six, size, skill, +skin, small, smile, so, social, society, soldier, some, somebody, someone, +something, sometimes, son, song, soon, sort, sound, source, south, southern, +space, speak, special, specific, speech, spend, sport, spring, staff, stage, +stand, standard, star, start, state, statement, station, stay, step, still, +stock, stop, store, story, strategy, street, strong, structure, student, study, +stuff, style, subject, success, successful, such, suddenly, suffer, suggest, +summer, support, sure, surface, system, table, take, talk, task, tax, teach, +teacher, team, technology, television, tell, ten, tend, term, test, than, thank, +that, the, their, them, themselves, then, theory, there, these, they, thing, +think, third, this, those, though, thought, thousand, threat, three, through, +throughout, throw, thus, time, to, today, together, tonight, too, top, total, +tough, toward, town, trade, traditional, training, travel, treat, treatment, +tree, trial, trip, trouble, true, truth, try, turn, TV, two, type, under, +understand, unit, until, up, upon, us, use, usually, value, various, very, +victim, view, violence, visit, voice, vote, wait, walk, wall, want, war, watch, +water, way, we, weapon, wear, week, weight, well, west, western, what, whatever, +when, where, whether, which, while, white, who, whole, whom, whose, why, wide, +wife, will, win, wind, window, wish, with, within, without, woman, wonder, word, +work, worker, world, worry, would, write, writer, wrong, yard, yeah, year, yes, +yet, you, young, your, yourself"# + .split(',') + .map(|word| word.trim()) + .collect(); + + let mut word_pairs: Vec<_> = Vec::new(); + for word in &words { + for other_word in &words { + word_pairs.push(word.to_string() + " " + other_word); + } + } + assert_eq!(1_000_000, word_pairs.len()); + word_pairs +} + +#[allow(unused)] // False positive +fn test_hash_common_words(hasher: impl Fn() -> T) { + let word_pairs: Vec<_> = gen_word_pairs(); + check_for_collisions(&hasher, &word_pairs, 32); +} + +#[allow(unused)] // False positive +fn check_for_collisions(hasher: &impl Fn() -> T, items: &[H], bucket_count: usize) { + let mut buckets = vec![0; bucket_count]; + for item in items { + let value = hash(item, &hasher) as usize; + buckets[value % bucket_count] += 1; + } + let mean = items.len() / bucket_count; + let max = *buckets.iter().max().unwrap(); + let min = *buckets.iter().min().unwrap(); + assert!( + (min as f64) > (mean as f64) * 0.95, + "min: {}, max:{}, {:?}", + min, + max, + buckets + ); + assert!( + (max as f64) < (mean as f64) * 1.05, + "min: {}, max:{}, {:?}", + min, + max, + buckets + ); +} + +#[allow(unused)] // False positive +fn hash(b: &impl Hash, hasher: &dyn Fn() -> T) -> u64 { + let hasher = hasher(); + b.get_hash(hasher) +} + +#[test] +fn test_bucket_distribution() { + let hasher = || AHasher::new_with_keys(123456789, 987654321); + test_hash_common_words(&hasher); + let sequence: Vec<_> = (0..320000).collect(); + check_for_collisions(&hasher, &sequence, 32); + let sequence: Vec<_> = (0..2560000).collect(); + check_for_collisions(&hasher, &sequence, 256); + let sequence: Vec<_> = (0..320000).map(|i| i * 1024).collect(); + check_for_collisions(&hasher, &sequence, 32); + let sequence: Vec<_> = (0..2560000_u64).map(|i| i * 1024).collect(); + check_for_collisions(&hasher, &sequence, 256); +} + +fn ahash_vec(b: &Vec) -> u64 { + let mut total: u64 = 0; + for item in b { + let mut hasher = AHasher::new_with_keys(1234, 5678); + item.hash(&mut hasher); + total = total.wrapping_add(hasher.finish()); + } + total +} + +fn fxhash_vec(b: &Vec) -> u64 { + let mut total: u64 = 0; + for item in b { + let mut hasher = FxHasher::default(); + item.hash(&mut hasher); + total = total.wrapping_add(hasher.finish()); + } + total +} + +fn bench_ahash_words(c: &mut Criterion) { + let words = gen_word_pairs(); + c.bench_function("aes_words", |b| b.iter(|| black_box(ahash_vec(&words)))); +} + +fn bench_fx_words(c: &mut Criterion) { + let words = gen_word_pairs(); + c.bench_function("fx_words", |b| b.iter(|| black_box(fxhash_vec(&words)))); +} + +criterion_main!(benches); +criterion_group!(benches, bench_ahash_words, bench_fx_words,); diff --git a/third_party/rust/ahash/tests/nopanic.rs b/third_party/rust/ahash/tests/nopanic.rs new file mode 100644 index 0000000000..f3d9361d20 --- /dev/null +++ b/third_party/rust/ahash/tests/nopanic.rs @@ -0,0 +1,54 @@ +use ahash::{AHasher, CallHasher, RandomState}; +use std::hash::BuildHasher; + +#[macro_use] +extern crate no_panic; + +#[inline(never)] +#[no_panic] +fn hash_test_final(num: i32, string: &str) -> (u64, u64) { + use core::hash::Hasher; + let mut hasher1 = AHasher::new_with_keys(1, 2); + let mut hasher2 = AHasher::new_with_keys(3, 4); + hasher1.write_i32(num); + hasher2.write(string.as_bytes()); + (hasher1.finish(), hasher2.finish()) +} + +#[inline(never)] +fn hash_test_final_wrapper(num: i32, string: &str) { + hash_test_final(num, string); +} + +#[inline(never)] +#[no_panic] +fn hash_test_specialize(num: i32, string: &str) -> (u64, u64) { + let hasher1 = AHasher::new_with_keys(1, 2); + let hasher2 = AHasher::new_with_keys(1, 2); + (num.get_hash(hasher1), string.as_bytes().get_hash(hasher2)) +} + +#[inline(never)] +fn hash_test_random_wrapper(num: i32, string: &str) { + hash_test_specialize(num, string); +} + +#[inline(never)] +#[no_panic] +fn hash_test_random(num: i32, string: &str) -> (u64, u64) { + let hasher1 = RandomState::with_seeds(1, 2).build_hasher(); + let hasher2 = RandomState::with_seeds(1, 2).build_hasher(); + (num.get_hash(hasher1), string.as_bytes().get_hash(hasher2)) +} + +#[inline(never)] +fn hash_test_specialize_wrapper(num: i32, string: &str) { + hash_test_specialize(num, string); +} + +#[test] +fn test_no_panic() { + hash_test_final_wrapper(2, "Foo"); + hash_test_specialize_wrapper(2, "Bar"); + hash_test_random_wrapper(2, "Baz"); +} diff --git a/third_party/rust/fallible_collections/.cargo-checksum.json b/third_party/rust/fallible_collections/.cargo-checksum.json new file mode 100644 index 0000000000..895a04e38a --- /dev/null +++ b/third_party/rust/fallible_collections/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"a2ca9939d09ff7ac42adf14a16cdf8a0d5a5399bd59792b3a876d61cf03ead33","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720","README.md":"349d151d53634cb225a72d9f1b5d34175429c2311804211b490cb5489050823c","src/arc.rs":"3cf237ae0acb5b058a57b633170f079024455271e9420e5a9244bafbdeb90b1c","src/boxed.rs":"1b549a293ae905f2ee99f1c02fde66a1d0954b986f114070514b8ef79a242b1c","src/btree.rs":"b83820fc2a00e2e34127b3037abde8b945f0ca2785f3def725787e6813c3d3e0","src/btree/map.rs":"557ce3ff2d02c425adcb2b4ac53b6b6607c25c535aee8ffa4f12bf773fbcd763","src/btree/node.rs":"c37d08176427997ecb1d1b4d87e2da88ba665727d817de50df727acb0c0a2d78","src/btree/search.rs":"ae78f73f3e56ea277b0a02cc39454447b75e12a6c817ecfee00065b3ddbfff67","src/btree/set.rs":"607f0db0b189c39b41824fbbf6fd8d9c5fdf85cc40f4437b13152e7b86d2979f","src/format.rs":"5142970f6ac1fe66f667ee2565af786802e93e6728ec3a1b82ffaa9f6a6b5bce","src/hashmap.rs":"e20d4349c6b54267c1d13ec6cbc40b3e2965a1aec9616b3c358472e5b6406b93","src/lib.rs":"4cd0ef055208600292ec075f600e940eafcd24bd3e74fe34bba9164842d7f380","src/rc.rs":"f327a0adcfd2b1e225913ae716deb96777ca562985ac64e3b83550111f809864","src/try_clone.rs":"725130e0ddacde1ff7c976de62fbe45d01c67412af395aa41cac4bcfb85f6a5f","src/vec.rs":"95d9ad166180a44bb438da4d432e0100a6ee7e35e2df76e74cafb1106035737b"},"package":"4ad9169582543d2cfe9961be1e9eaf4fc42f9aa3483f7c485717b8dde36466ea"} \ No newline at end of file diff --git a/third_party/rust/fallible_collections/Cargo.toml b/third_party/rust/fallible_collections/Cargo.toml new file mode 100644 index 0000000000..0de400a7da --- /dev/null +++ b/third_party/rust/fallible_collections/Cargo.toml @@ -0,0 +1,28 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "fallible_collections" +version = "0.4.2" +authors = ["vcombey "] +description = "a crate which adds fallible allocation api to std collections" +readme = "README.md" +keywords = ["fallible", "collections"] +license = "MIT/Apache-2.0" +repository = "https://github.com/vcombey/fallible_collections.git" +[dependencies.hashbrown] +version = "0.9" + +[features] +std_io = [] +unstable = [] diff --git a/third_party/rust/fallible_collections/LICENSE-APACHE b/third_party/rust/fallible_collections/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/fallible_collections/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/fallible_collections/LICENSE-MIT b/third_party/rust/fallible_collections/LICENSE-MIT new file mode 100644 index 0000000000..25597d5838 --- /dev/null +++ b/third_party/rust/fallible_collections/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2010 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/fallible_collections/README.md b/third_party/rust/fallible_collections/README.md new file mode 100644 index 0000000000..e5818952b2 --- /dev/null +++ b/third_party/rust/fallible_collections/README.md @@ -0,0 +1,73 @@ +Fallible Collections.rs +============== + +Implement api on rust collection wich returns a result when an allocation error occurs. +This is inspired a lot by [RFC 2116](https://github.com/rust-lang/rfcs/blob/master/text/2116-alloc-me-maybe.md). + +The api currently propose a fallible interface for Vec, Box, Arc, Btree and Rc, +a TryClone trait wich is implemented for primitive rust traits and a fallible format macro. + +You can use this with try_clone_derive crate wich derive TryClone for your own types. + +# Getting Started + +[fallible collections is available on crates.io](https://crates.io/crates/fallible_collections). +It is recommended to look there for the newest released version, as well as links to the newest builds of the docs. + +At the point of the last update of this README, the latest published version could be used like this: + +Add the following dependency to your Cargo manifest... + +```toml +[dependencies] +fallible_collections = "0.4.1" +``` + +...and see the [docs](https://docs.rs/fallible_collections) for how to use it. + +# Example + +Exemple of using the FallibleBox interface. +```rust +use fallible_collections::FallibleBox; + +fn main() { + // this crate an Ordinary box but return an error on allocation failure + let mut a = as FallibleBox<_>>::try_new(5).unwrap(); + let mut b = Box::new(5); + + assert_eq!(a, b); + *a = 3; + assert_eq!(*a, 3); +} +``` + +Exemple of using the FallibleVec interface. +```rust +use fallible_collections::FallibleVec; + +fn main() { + // this crate an Ordinary Vec> but return an error on allocation failure + let a: Vec> = try_vec![try_vec![42; 10].unwrap(); 100].unwrap(); + let b: Vec> = vec![vec![42; 10]; 100]; + assert_eq!(a, b); + assert_eq!(a.try_clone().unwrap(), a); + ... +} +``` + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. + diff --git a/third_party/rust/fallible_collections/src/arc.rs b/third_party/rust/fallible_collections/src/arc.rs new file mode 100644 index 0000000000..282b8e5555 --- /dev/null +++ b/third_party/rust/fallible_collections/src/arc.rs @@ -0,0 +1,52 @@ +//! Implement a Fallible Arc +use super::FallibleBox; +use super::TryClone; + +use crate::TryReserveError; +use alloc::boxed::Box; +use alloc::sync::Arc; + +/// trait to implement Fallible Arc +#[deprecated( + since = "0.3.1", + note = "⚠️️️this function is not completely fallible, it can panic !, see [issue](https://github.com/vcombey/fallible_collections/issues/13). help wanted" +)] +pub trait FallibleArc { + /// try creating a new Arc, returning a Result, + /// TryReserveError> if allocation failed + fn try_new(t: T) -> Result + where + Self: Sized; +} + +#[allow(deprecated)] +impl FallibleArc for Arc { + fn try_new(t: T) -> Result { + // doesn't work as the inner variable of arc are also stocked in the box + + let b = as FallibleBox>::try_new(t)?; + Ok(Arc::from(b)) + } +} + +/// Just a TryClone boilerplate for Arc +impl TryClone for Arc { + fn try_clone(&self) -> Result { + Ok(self.clone()) + } +} + +#[cfg(test)] +mod test { + #[test] + fn fallible_rc() { + use std::sync::Arc; + + let mut x = Arc::new(3); + *Arc::get_mut(&mut x).unwrap() = 4; + assert_eq!(*x, 4); + + let _y = Arc::clone(&x); + assert!(Arc::get_mut(&mut x).is_none()); + } +} diff --git a/third_party/rust/fallible_collections/src/boxed.rs b/third_party/rust/fallible_collections/src/boxed.rs new file mode 100644 index 0000000000..f84cb2bf96 --- /dev/null +++ b/third_party/rust/fallible_collections/src/boxed.rs @@ -0,0 +1,125 @@ +//! Implement Fallible Box +use super::TryClone; +use crate::TryReserveError; +use alloc::alloc::Layout; +use alloc::boxed::Box; +use core::borrow::Borrow; +use core::ptr::NonNull; + +/// trait to implement Fallible Box +pub trait FallibleBox { + /// try creating a new box, returning a Result, + /// TryReserveError> if allocation failed + fn try_new(t: T) -> Result + where + Self: Sized; +} +/// TryBox is a thin wrapper around alloc::boxed::Box to provide support for +/// fallible allocation. +/// +/// See the crate documentation for more. +pub struct TryBox { + inner: Box, +} + +impl TryBox { + #[inline] + pub fn try_new(t: T) -> Result { + Ok(Self { + inner: as FallibleBox>::try_new(t)?, + }) + } + + #[inline(always)] + pub fn into_raw(b: TryBox) -> *mut T { + Box::into_raw(b.inner) + } + + /// # Safety + /// + /// See std::boxed::from_raw + #[inline(always)] + pub unsafe fn from_raw(raw: *mut T) -> Self { + Self { + inner: Box::from_raw(raw), + } + } +} + +impl TryClone for TryBox { + fn try_clone(&self) -> Result { + let clone: T = (*self.inner).try_clone()?; + Self::try_new(clone) + } +} + +fn alloc(layout: Layout) -> Result, TryReserveError> { + #[cfg(feature = "unstable")] // requires allocator_api + { + use core::alloc::AllocRef as _; + let mut g = alloc::alloc::Global; + g.alloc(layout, alloc::alloc::AllocInit::Uninitialized) + .map_err(|_e| TryReserveError::AllocError { + layout, + non_exhaustive: (), + }) + .map(|memory_block| memory_block.ptr) + } + #[cfg(not(feature = "unstable"))] + { + match layout.size() { + 0 => { + // Required for alloc safety + // See https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html#safety-1 + Ok(NonNull::dangling()) + } + 1..=core::usize::MAX => { + let ptr = unsafe { alloc::alloc::alloc(layout) }; + core::ptr::NonNull::new(ptr).ok_or(TryReserveError::AllocError { layout }) + } + _ => unreachable!("size must be non-negative"), + } + } +} + +impl FallibleBox for Box { + fn try_new(t: T) -> Result { + let layout = Layout::for_value(&t); + let ptr = alloc(layout)?.as_ptr() as *mut T; + unsafe { + core::ptr::write(ptr, t); + Ok(Box::from_raw(ptr)) + } + } +} + +impl TryClone for Box { + #[inline] + fn try_clone(&self) -> Result { + >::try_new(Borrow::::borrow(self).try_clone()?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn boxed() { + let mut v = as FallibleBox<_>>::try_new(5).unwrap(); + assert_eq!(*v, 5); + *v = 3; + assert_eq!(*v, 3); + } + // #[test] + // fn big_alloc() { + // let layout = Layout::from_size_align(1_000_000_000_000, 8).unwrap(); + // let ptr = unsafe { alloc::alloc::alloc(layout) }; + // assert!(ptr.is_null()); + // } + + #[test] + fn trybox_zst() { + let b = as FallibleBox<_>>::try_new(()).expect("ok"); + assert_eq!(b, Box::new(())); + } +} diff --git a/third_party/rust/fallible_collections/src/btree.rs b/third_party/rust/fallible_collections/src/btree.rs new file mode 100644 index 0000000000..63d8c6bcf5 --- /dev/null +++ b/third_party/rust/fallible_collections/src/btree.rs @@ -0,0 +1,20 @@ +//! Implement Fallible Btree, As there is no try_reserve methods on btree, I add no choice but to fork the std implementation and change return types. +//! Currently this functionality is only available when building this crate with nightly and the `unstable` feature. +pub mod map; +pub use map::BTreeMap; + +pub mod set; +pub use set::BTreeSet; + +mod node; +mod search; +use crate::TryReserveError; + +#[doc(hidden)] +trait Recover { + type Key; + + fn get(&self, key: &Q) -> Option<&Self::Key>; + fn take(&mut self, key: &Q) -> Option; + fn replace(&mut self, key: Self::Key) -> Result, TryReserveError>; +} diff --git a/third_party/rust/fallible_collections/src/btree/map.rs b/third_party/rust/fallible_collections/src/btree/map.rs new file mode 100644 index 0000000000..3a69a6679f --- /dev/null +++ b/third_party/rust/fallible_collections/src/btree/map.rs @@ -0,0 +1,2684 @@ +use crate::TryReserveError; +use core::borrow::Borrow; +use core::cmp::Ordering; +use core::fmt::Debug; +use core::hash::{Hash, Hasher}; +use core::iter::{FromIterator, FusedIterator, Peekable}; +use core::marker::PhantomData; +use core::ops::Bound::{Excluded, Included, Unbounded}; +use core::ops::{Index, RangeBounds}; +use core::{fmt, intrinsics, mem, ptr}; + +use super::node::{self, marker, ForceResult::*, Handle, InsertResult::*, NodeRef}; +use super::search::{self, SearchResult::*}; + +use Entry::*; +use UnderflowResult::*; + +/// A map based on a B-Tree. +/// +/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing +/// the amount of work performed in a search. In theory, a binary search tree (BST) is the optimal +/// choice for a sorted map, as a perfectly balanced BST performs the theoretical minimum amount of +/// comparisons necessary to find an element (log2n). However, in practice the way this +/// is done is *very* inefficient for modern computer architectures. In particular, every element +/// is stored in its own individually heap-allocated node. This means that every single insertion +/// triggers a heap-allocation, and every single comparison should be a cache-miss. Since these +/// are both notably expensive things to do in practice, we are forced to at very least reconsider +/// the BST strategy. +/// +/// A B-Tree instead makes each node contain B-1 to 2B-1 elements in a contiguous array. By doing +/// this, we reduce the number of allocations by a factor of B, and improve cache efficiency in +/// searches. However, this does mean that searches will have to do *more* comparisons on average. +/// The precise number of comparisons depends on the node search strategy used. For optimal cache +/// efficiency, one could search the nodes linearly. For optimal comparisons, one could search +/// the node using binary search. As a compromise, one could also perform a linear search +/// that initially only checks every ith element for some choice of i. +/// +/// Currently, our implementation simply performs naive linear search. This provides excellent +/// performance on *small* nodes of elements which are cheap to compare. However in the future we +/// would like to further explore choosing the optimal search strategy based on the choice of B, +/// and possibly other factors. Using linear search, searching for a random element is expected +/// to take O(B logBn) comparisons, which is generally worse than a BST. In practice, +/// however, performance is excellent. +/// +/// It is a logic error for a key to be modified in such a way that the key's ordering relative to +/// any other key, as determined by the [`Ord`] trait, changes while it is in the map. This is +/// normally only possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. +/// +/// [`Ord`]: ../../std/cmp/trait.Ord.html +/// [`Cell`]: ../../std/cell/struct.Cell.html +/// [`RefCell`]: ../../std/cell/struct.RefCell.html +/// +/// # Examples +/// +/// ``` +/// use std::collections::BTreeMap; +/// +/// // type inference lets us omit an explicit type signature (which +/// // would be `BTreeMap<&str, &str>` in this example). +/// let mut movie_reviews = BTreeMap::new(); +/// +/// // review some movies. +/// movie_reviews.insert("Office Space", "Deals with real issues in the workplace."); +/// movie_reviews.insert("Pulp Fiction", "Masterpiece."); +/// movie_reviews.insert("The Godfather", "Very enjoyable."); +/// movie_reviews.insert("The Blues Brothers", "Eye lyked it a lot."); +/// +/// // check for a specific one. +/// if !movie_reviews.contains_key("Les Misérables") { +/// println!("We've got {} reviews, but Les Misérables ain't one.", +/// movie_reviews.len()); +/// } +/// +/// // oops, this review has a lot of spelling mistakes, let's delete it. +/// movie_reviews.remove("The Blues Brothers"); +/// +/// // look up the values associated with some keys. +/// let to_find = ["Up!", "Office Space"]; +/// for book in &to_find { +/// match movie_reviews.get(book) { +/// Some(review) => println!("{}: {}", book, review), +/// None => println!("{} is unreviewed.", book) +/// } +/// } +/// +/// // Look up the value for a key (will panic if the key is not found). +/// println!("Movie review: {}", movie_reviews["Office Space"]); +/// +/// // iterate over everything. +/// for (movie, review) in &movie_reviews { +/// println!("{}: \"{}\"", movie, review); +/// } +/// ``` +/// +/// `BTreeMap` also implements an [`Entry API`](#method.entry), which allows +/// for more complex methods of getting, setting, updating and removing keys and +/// their values: +/// +/// ``` +/// use std::collections::BTreeMap; +/// +/// // type inference lets us omit an explicit type signature (which +/// // would be `BTreeMap<&str, u8>` in this example). +/// let mut player_stats = BTreeMap::new(); +/// +/// fn random_stat_buff() -> u8 { +/// // could actually return some random value here - let's just return +/// // some fixed value for now +/// 42 +/// } +/// +/// // insert a key only if it doesn't already exist +/// player_stats.entry("health").or_insert(100); +/// +/// // insert a key using a function that provides a new value only if it +/// // doesn't already exist +/// player_stats.entry("defence").or_insert_with(random_stat_buff); +/// +/// // update a key, guarding against the key possibly not being set +/// let stat = player_stats.entry("attack").or_insert(100); +/// *stat += random_stat_buff(); +/// ``` + +pub struct BTreeMap { + root: node::Root, + length: usize, +} + +unsafe impl<#[may_dangle] K, #[may_dangle] V> Drop for BTreeMap { + fn drop(&mut self) { + unsafe { + drop(ptr::read(self).into_iter()); + } + } +} + +use crate::TryClone; + +impl TryClone for BTreeMap { + fn try_clone(&self) -> Result, TryReserveError> { + fn clone_subtree<'a, K: TryClone, V: TryClone>( + node: node::NodeRef, K, V, marker::LeafOrInternal>, + ) -> Result, TryReserveError> + where + K: 'a, + V: 'a, + { + match node.force() { + Leaf(leaf) => { + let mut out_tree = BTreeMap { + root: node::Root::new_leaf()?, + length: 0, + }; + + { + let mut out_node = match out_tree.root.as_mut().force() { + Leaf(leaf) => leaf, + Internal(_) => unreachable!(), + }; + + let mut in_edge = leaf.first_edge(); + while let Ok(kv) = in_edge.right_kv() { + let (k, v) = kv.into_kv(); + in_edge = kv.right_edge(); + + out_node.push(k.try_clone()?, v.try_clone()?); + out_tree.length += 1; + } + } + + Ok(out_tree) + } + Internal(internal) => { + let mut out_tree = clone_subtree(internal.first_edge().descend())?; + + { + let mut out_node = out_tree.root.push_level()?; + let mut in_edge = internal.first_edge(); + while let Ok(kv) = in_edge.right_kv() { + let (k, v) = kv.into_kv(); + in_edge = kv.right_edge(); + + let k = (*k).try_clone()?; + let v = (*v).try_clone()?; + let subtree = clone_subtree(in_edge.descend())?; + + // We can't destructure subtree directly + // because BTreeMap implements Drop + let (subroot, sublength) = unsafe { + let root = ptr::read(&subtree.root); + let length = subtree.length; + mem::forget(subtree); + (root, length) + }; + + out_node.push(k, v, subroot); + out_tree.length += 1 + sublength; + } + } + + Ok(out_tree) + } + } + } + + if self.len() == 0 { + // Ideally we'd call `BTreeMap::new` here, but that has the `K: + // Ord` constraint, which this method lacks. + Ok(BTreeMap { + root: node::Root::shared_empty_root(), + length: 0, + }) + } else { + clone_subtree(self.root.as_ref()) + } + } +} + +impl Clone for BTreeMap { + fn clone(&self) -> BTreeMap { + fn clone_subtree<'a, K: Clone, V: Clone>( + node: node::NodeRef, K, V, marker::LeafOrInternal>, + ) -> BTreeMap + where + K: 'a, + V: 'a, + { + match node.force() { + Leaf(leaf) => { + let mut out_tree = BTreeMap { + root: node::Root::new_leaf().expect("Out of Mem"), + length: 0, + }; + + { + let mut out_node = match out_tree.root.as_mut().force() { + Leaf(leaf) => leaf, + Internal(_) => unreachable!(), + }; + + let mut in_edge = leaf.first_edge(); + while let Ok(kv) = in_edge.right_kv() { + let (k, v) = kv.into_kv(); + in_edge = kv.right_edge(); + + out_node.push(k.clone(), v.clone()); + out_tree.length += 1; + } + } + + out_tree + } + Internal(internal) => { + let mut out_tree = clone_subtree(internal.first_edge().descend()); + + { + let mut out_node = out_tree.root.push_level().expect("Out of Mem"); + let mut in_edge = internal.first_edge(); + while let Ok(kv) = in_edge.right_kv() { + let (k, v) = kv.into_kv(); + in_edge = kv.right_edge(); + + let k = (*k).clone(); + let v = (*v).clone(); + let subtree = clone_subtree(in_edge.descend()); + + // We can't destructure subtree directly + // because BTreeMap implements Drop + let (subroot, sublength) = unsafe { + let root = ptr::read(&subtree.root); + let length = subtree.length; + mem::forget(subtree); + (root, length) + }; + + out_node.push(k, v, subroot); + out_tree.length += 1 + sublength; + } + } + + out_tree + } + } + } + + if self.len() == 0 { + // Ideally we'd call `BTreeMap::new` here, but that has the `K: + // Ord` constraint, which this method lacks. + BTreeMap { + root: node::Root::shared_empty_root(), + length: 0, + } + } else { + clone_subtree(self.root.as_ref()) + } + } +} + +impl super::Recover for BTreeMap +where + K: Borrow + Ord, + Q: Ord, +{ + type Key = K; + + fn get(&self, key: &Q) -> Option<&K> { + match search::search_tree(self.root.as_ref(), key) { + Found(handle) => Some(handle.into_kv().0), + GoDown(_) => None, + } + } + + fn take(&mut self, key: &Q) -> Option { + match search::search_tree(self.root.as_mut(), key) { + Found(handle) => Some( + OccupiedEntry { + handle, + length: &mut self.length, + _marker: PhantomData, + } + .remove_kv() + .0, + ), + GoDown(_) => None, + } + } + + fn replace(&mut self, key: K) -> Result, TryReserveError> { + self.ensure_root_is_owned()?; + match search::search_tree::, K, (), K>(self.root.as_mut(), &key) { + Found(handle) => Ok(Some(mem::replace(handle.into_kv_mut().0, key))), + GoDown(handle) => { + VacantEntry { + key, + handle, + length: &mut self.length, + _marker: PhantomData, + } + .try_insert(())?; + Ok(None) + } + } + } +} + +/// An iterator over the entries of a `BTreeMap`. +/// +/// This `struct` is created by the [`iter`] method on [`BTreeMap`]. See its +/// documentation for more. +/// +/// [`iter`]: struct.BTreeMap.html#method.iter +/// [`BTreeMap`]: struct.BTreeMap.html + +pub struct Iter<'a, K: 'a, V: 'a> { + range: Range<'a, K, V>, + length: usize, +} + +impl fmt::Debug for Iter<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A mutable iterator over the entries of a `BTreeMap`. +/// +/// This `struct` is created by the [`iter_mut`] method on [`BTreeMap`]. See its +/// documentation for more. +/// +/// [`iter_mut`]: struct.BTreeMap.html#method.iter_mut +/// [`BTreeMap`]: struct.BTreeMap.html + +#[derive(Debug)] +pub struct IterMut<'a, K: 'a, V: 'a> { + range: RangeMut<'a, K, V>, + length: usize, +} + +/// An owning iterator over the entries of a `BTreeMap`. +/// +/// This `struct` is created by the [`into_iter`] method on [`BTreeMap`][`BTreeMap`] +/// (provided by the `IntoIterator` trait). See its documentation for more. +/// +/// [`into_iter`]: struct.BTreeMap.html#method.into_iter +/// [`BTreeMap`]: struct.BTreeMap.html + +pub struct IntoIter { + front: Handle, marker::Edge>, + back: Handle, marker::Edge>, + length: usize, +} + +impl fmt::Debug for IntoIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let range = Range { + front: self.front.reborrow(), + back: self.back.reborrow(), + }; + f.debug_list().entries(range).finish() + } +} + +/// An iterator over the keys of a `BTreeMap`. +/// +/// This `struct` is created by the [`keys`] method on [`BTreeMap`]. See its +/// documentation for more. +/// +/// [`keys`]: struct.BTreeMap.html#method.keys +/// [`BTreeMap`]: struct.BTreeMap.html + +pub struct Keys<'a, K: 'a, V: 'a> { + inner: Iter<'a, K, V>, +} + +impl fmt::Debug for Keys<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// An iterator over the values of a `BTreeMap`. +/// +/// This `struct` is created by the [`values`] method on [`BTreeMap`]. See its +/// documentation for more. +/// +/// [`values`]: struct.BTreeMap.html#method.values +/// [`BTreeMap`]: struct.BTreeMap.html + +pub struct Values<'a, K: 'a, V: 'a> { + inner: Iter<'a, K, V>, +} + +impl fmt::Debug for Values<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A mutable iterator over the values of a `BTreeMap`. +/// +/// This `struct` is created by the [`values_mut`] method on [`BTreeMap`]. See its +/// documentation for more. +/// +/// [`values_mut`]: struct.BTreeMap.html#method.values_mut +/// [`BTreeMap`]: struct.BTreeMap.html + +#[derive(Debug)] +pub struct ValuesMut<'a, K: 'a, V: 'a> { + inner: IterMut<'a, K, V>, +} + +/// An iterator over a sub-range of entries in a `BTreeMap`. +/// +/// This `struct` is created by the [`range`] method on [`BTreeMap`]. See its +/// documentation for more. +/// +/// [`range`]: struct.BTreeMap.html#method.range +/// [`BTreeMap`]: struct.BTreeMap.html + +pub struct Range<'a, K: 'a, V: 'a> { + front: Handle, K, V, marker::Leaf>, marker::Edge>, + back: Handle, K, V, marker::Leaf>, marker::Edge>, +} + +impl fmt::Debug for Range<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A mutable iterator over a sub-range of entries in a `BTreeMap`. +/// +/// This `struct` is created by the [`range_mut`] method on [`BTreeMap`]. See its +/// documentation for more. +/// +/// [`range_mut`]: struct.BTreeMap.html#method.range_mut +/// [`BTreeMap`]: struct.BTreeMap.html + +pub struct RangeMut<'a, K: 'a, V: 'a> { + front: Handle, K, V, marker::Leaf>, marker::Edge>, + back: Handle, K, V, marker::Leaf>, marker::Edge>, + + // Be invariant in `K` and `V` + _marker: PhantomData<&'a mut (K, V)>, +} + +impl fmt::Debug for RangeMut<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let range = Range { + front: self.front.reborrow(), + back: self.back.reborrow(), + }; + f.debug_list().entries(range).finish() + } +} + +/// A view into a single entry in a map, which may either be vacant or occupied. +/// +/// This `enum` is constructed from the [`entry`] method on [`BTreeMap`]. +/// +/// [`BTreeMap`]: struct.BTreeMap.html +/// [`entry`]: struct.BTreeMap.html#method.entry + +pub enum Entry<'a, K: 'a, V: 'a> { + /// A vacant entry. + Vacant(VacantEntry<'a, K, V>), + + /// An occupied entry. + Occupied(OccupiedEntry<'a, K, V>), +} + +impl Debug for Entry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Vacant(ref v) => f.debug_tuple("Entry").field(v).finish(), + Occupied(ref o) => f.debug_tuple("Entry").field(o).finish(), + } + } +} + +/// A view into a vacant entry in a `BTreeMap`. +/// It is part of the [`Entry`] enum. +/// +/// [`Entry`]: enum.Entry.html + +pub struct VacantEntry<'a, K: 'a, V: 'a> { + key: K, + handle: Handle, K, V, marker::Leaf>, marker::Edge>, + length: &'a mut usize, + + // Be invariant in `K` and `V` + _marker: PhantomData<&'a mut (K, V)>, +} + +impl Debug for VacantEntry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VacantEntry").field(self.key()).finish() + } +} + +/// A view into an occupied entry in a `BTreeMap`. +/// It is part of the [`Entry`] enum. +/// +/// [`Entry`]: enum.Entry.html + +pub struct OccupiedEntry<'a, K: 'a, V: 'a> { + handle: Handle, K, V, marker::LeafOrInternal>, marker::KV>, + + length: &'a mut usize, + + // Be invariant in `K` and `V` + _marker: PhantomData<&'a mut (K, V)>, +} + +impl Debug for OccupiedEntry<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OccupiedEntry") + .field("key", self.key()) + .field("value", self.get()) + .finish() + } +} + +// An iterator for merging two sorted sequences into one +struct MergeIter> { + left: Peekable, + right: Peekable, +} + +impl BTreeMap { + /// Makes a new empty BTreeMap with a reasonable choice for B. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// + /// // entries can now be inserted into the empty map + /// map.insert(1, "a"); + /// ``` + + pub fn new() -> BTreeMap { + BTreeMap { + root: node::Root::shared_empty_root(), + length: 0, + } + } + + /// Clears the map, removing all values. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// a.insert(1, "a"); + /// a.clear(); + /// assert!(a.is_empty()); + /// ``` + + pub fn clear(&mut self) { + *self = BTreeMap::new(); + } + + /// Returns a reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.get(&1), Some(&"a")); + /// assert_eq!(map.get(&2), None); + /// ``` + + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Ord, + { + match search::search_tree(self.root.as_ref(), key) { + Found(handle) => Some(handle.into_kv().1), + GoDown(_) => None, + } + } + + /// Returns the key-value pair corresponding to the supplied key. + /// + /// The supplied key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + /// + /// # Examples + /// + /// ``` + /// #![feature(map_get_key_value)] + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.get_key_value(&1), Some((&1, &"a"))); + /// assert_eq!(map.get_key_value(&2), None); + /// ``` + pub fn get_key_value(&self, k: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: Ord, + { + match search::search_tree(self.root.as_ref(), k) { + Found(handle) => Some(handle.into_kv()), + GoDown(_) => None, + } + } + + /// Returns `true` if the map contains a value for the specified key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.contains_key(&1), true); + /// assert_eq!(map.contains_key(&2), false); + /// ``` + + #[inline] + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: Ord, + { + self.get(key).is_some() + } + + /// Returns a mutable reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// map.insert(1, "a"); + /// if let Some(x) = map.get_mut(&1) { + /// *x = "b"; + /// } + /// assert_eq!(map[&1], "b"); + /// ``` + // See `get` for implementation notes, this is basically a copy-paste with mut's added + + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Ord, + { + match search::search_tree(self.root.as_mut(), key) { + Found(handle) => Some(handle.into_kv_mut().1), + GoDown(_) => None, + } + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// + /// If the map did have this key present, the value is updated, and the old + /// value is returned. The key is not updated, though; this matters for + /// types that can be `==` without being identical. See the [module-level + /// documentation] for more. + /// + /// [module-level documentation]: index.html#insert-and-complex-keys + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// assert_eq!(map.insert(37, "a"), None); + /// assert_eq!(map.is_empty(), false); + /// + /// map.insert(37, "b"); + /// assert_eq!(map.insert(37, "c"), Some("b")); + /// assert_eq!(map[&37], "c"); + /// ``` + + pub fn try_insert(&mut self, key: K, value: V) -> Result, TryReserveError> { + match self.try_entry(key)? { + Occupied(mut entry) => Ok(Some(entry.insert(value))), + Vacant(entry) => { + entry.try_insert(value)?; + Ok(None) + } + } + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.remove(&1), Some("a")); + /// assert_eq!(map.remove(&1), None); + /// ``` + + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Ord, + { + match search::search_tree(self.root.as_mut(), key) { + Found(handle) => Some( + OccupiedEntry { + handle, + length: &mut self.length, + _marker: PhantomData, + } + .remove(), + ), + GoDown(_) => None, + } + } + + /// Moves all elements from `other` into `Self`, leaving `other` empty. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// a.insert(1, "a"); + /// a.insert(2, "b"); + /// a.insert(3, "c"); + /// + /// let mut b = BTreeMap::new(); + /// b.insert(3, "d"); + /// b.insert(4, "e"); + /// b.insert(5, "f"); + /// + /// a.append(&mut b); + /// + /// assert_eq!(a.len(), 5); + /// assert_eq!(b.len(), 0); + /// + /// assert_eq!(a[&1], "a"); + /// assert_eq!(a[&2], "b"); + /// assert_eq!(a[&3], "d"); + /// assert_eq!(a[&4], "e"); + /// assert_eq!(a[&5], "f"); + /// ``` + + pub fn append(&mut self, other: &mut Self) { + // Do we have to append anything at all? + if other.len() == 0 { + return; + } + + // We can just swap `self` and `other` if `self` is empty. + if self.len() == 0 { + mem::swap(self, other); + return; + } + + // First, we merge `self` and `other` into a sorted sequence in linear time. + let self_iter = mem::replace(self, BTreeMap::new()).into_iter(); + let other_iter = mem::replace(other, BTreeMap::new()).into_iter(); + let iter = MergeIter { + left: self_iter.peekable(), + right: other_iter.peekable(), + }; + + // Second, we build a tree from the sorted sequence in linear time. + self.from_sorted_iter(iter); + self.fix_right_edge(); + } + + /// Constructs a double-ended iterator over a sub-range of elements in the map. + /// The simplest way is to use the range syntax `min..max`, thus `range(min..max)` will + /// yield elements from min (inclusive) to max (exclusive). + /// The range may also be entered as `(Bound, Bound)`, so for example + /// `range((Excluded(4), Included(10)))` will yield a left-exclusive, right-inclusive + /// range from 4 to 10. + /// + /// # Panics + /// + /// Panics if range `start > end`. + /// Panics if range `start == end` and both bounds are `Excluded`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::ops::Bound::Included; + /// + /// let mut map = BTreeMap::new(); + /// map.insert(3, "a"); + /// map.insert(5, "b"); + /// map.insert(8, "c"); + /// for (&key, &value) in map.range((Included(&4), Included(&8))) { + /// println!("{}: {}", key, value); + /// } + /// assert_eq!(Some((&5, &"b")), map.range(4..).next()); + /// ``` + + pub fn range(&self, range: R) -> Range<'_, K, V> + where + T: Ord, + K: Borrow, + R: RangeBounds, + { + let root1 = self.root.as_ref(); + let root2 = self.root.as_ref(); + let (f, b) = range_search(root1, root2, range); + + Range { front: f, back: b } + } + + /// Constructs a mutable double-ended iterator over a sub-range of elements in the map. + /// The simplest way is to use the range syntax `min..max`, thus `range(min..max)` will + /// yield elements from min (inclusive) to max (exclusive). + /// The range may also be entered as `(Bound, Bound)`, so for example + /// `range((Excluded(4), Included(10)))` will yield a left-exclusive, right-inclusive + /// range from 4 to 10. + /// + /// # Panics + /// + /// Panics if range `start > end`. + /// Panics if range `start == end` and both bounds are `Excluded`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, i32> = ["Alice", "Bob", "Carol", "Cheryl"] + /// .iter() + /// .map(|&s| (s, 0)) + /// .collect(); + /// for (_, balance) in map.range_mut("B".."Cheryl") { + /// *balance += 100; + /// } + /// for (name, balance) in &map { + /// println!("{} => {}", name, balance); + /// } + /// ``` + + pub fn range_mut(&mut self, range: R) -> RangeMut<'_, K, V> + where + T: Ord, + K: Borrow, + R: RangeBounds, + { + let root1 = self.root.as_mut(); + let root2 = unsafe { ptr::read(&root1) }; + let (f, b) = range_search(root1, root2, range); + + RangeMut { + front: f, + back: b, + _marker: PhantomData, + } + } + + /// Gets the given key's corresponding entry in the map for in-place manipulation. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut count: BTreeMap<&str, usize> = BTreeMap::new(); + /// + /// // count the number of occurrences of letters in the vec + /// for x in vec!["a","b","a","c","a","b"] { + /// *count.entry(x).or_insert(0) += 1; + /// } + /// + /// assert_eq!(count["a"], 3); + /// ``` + + pub fn try_entry(&mut self, key: K) -> Result, TryReserveError> { + // FIXME(@porglezomp) Avoid allocating if we don't insert + self.ensure_root_is_owned()?; + Ok(match search::search_tree(self.root.as_mut(), &key) { + Found(handle) => Occupied(OccupiedEntry { + handle, + length: &mut self.length, + _marker: PhantomData, + }), + GoDown(handle) => Vacant(VacantEntry { + key, + handle, + length: &mut self.length, + _marker: PhantomData, + }), + }) + } + + fn from_sorted_iter>(&mut self, iter: I) { + self.ensure_root_is_owned().expect("Out Of Mem"); + let mut cur_node = last_leaf_edge(self.root.as_mut()).into_node(); + // Iterate through all key-value pairs, pushing them into nodes at the right level. + for (key, value) in iter { + // Try to push key-value pair into the current leaf node. + if cur_node.len() < node::CAPACITY { + cur_node.push(key, value); + } else { + // No space left, go up and push there. + let mut open_node; + let mut test_node = cur_node.forget_type(); + loop { + match test_node.ascend() { + Ok(parent) => { + let parent = parent.into_node(); + if parent.len() < node::CAPACITY { + // Found a node with space left, push here. + open_node = parent; + break; + } else { + // Go up again. + test_node = parent.forget_type(); + } + } + Err(node) => { + // We are at the top, create a new root node and push there. + open_node = node.into_root_mut().push_level().expect("Out of Mem"); + break; + } + } + } + + // Push key-value pair and new right subtree. + let tree_height = open_node.height() - 1; + let mut right_tree = node::Root::new_leaf().expect("Out of Mem"); + for _ in 0..tree_height { + right_tree.push_level().expect("Out of Mem"); + } + open_node.push(key, value, right_tree); + + // Go down to the right-most leaf again. + cur_node = last_leaf_edge(open_node.forget_type()).into_node(); + } + + self.length += 1; + } + } + + fn fix_right_edge(&mut self) { + // Handle underfull nodes, start from the top. + let mut cur_node = self.root.as_mut(); + while let Internal(internal) = cur_node.force() { + // Check if right-most child is underfull. + let mut last_edge = internal.last_edge(); + let right_child_len = last_edge.reborrow().descend().len(); + if right_child_len < node::MIN_LEN { + // We need to steal. + let mut last_kv = match last_edge.left_kv() { + Ok(left) => left, + Err(_) => unreachable!(), + }; + last_kv.bulk_steal_left(node::MIN_LEN - right_child_len); + last_edge = last_kv.right_edge(); + } + + // Go further down. + cur_node = last_edge.descend(); + } + } + + /// Splits the collection into two at the given key. Returns everything after the given key, + /// including the key. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// a.insert(1, "a"); + /// a.insert(2, "b"); + /// a.insert(3, "c"); + /// a.insert(17, "d"); + /// a.insert(41, "e"); + /// + /// let b = a.split_off(&3); + /// + /// assert_eq!(a.len(), 2); + /// assert_eq!(b.len(), 3); + /// + /// assert_eq!(a[&1], "a"); + /// assert_eq!(a[&2], "b"); + /// + /// assert_eq!(b[&3], "c"); + /// assert_eq!(b[&17], "d"); + /// assert_eq!(b[&41], "e"); + /// ``` + + pub fn split_off(&mut self, key: &Q) -> Result + where + K: Borrow, + { + if self.is_empty() { + return Ok(Self::new()); + } + + let total_num = self.len(); + + let mut right = Self::new(); + right.root = node::Root::new_leaf()?; + for _ in 0..(self.root.as_ref().height()) { + right.root.push_level()?; + } + + { + let mut left_node = self.root.as_mut(); + let mut right_node = right.root.as_mut(); + + loop { + let mut split_edge = match search::search_node(left_node, key) { + // key is going to the right tree + Found(handle) => handle.left_edge(), + GoDown(handle) => handle, + }; + + split_edge.move_suffix(&mut right_node); + + match (split_edge.force(), right_node.force()) { + (Internal(edge), Internal(node)) => { + left_node = edge.descend(); + right_node = node.first_edge().descend(); + } + (Leaf(_), Leaf(_)) => { + break; + } + _ => { + unreachable!(); + } + } + } + } + + self.fix_right_border(); + right.fix_left_border(); + + if self.root.as_ref().height() < right.root.as_ref().height() { + self.recalc_length(); + right.length = total_num - self.len(); + } else { + right.recalc_length(); + self.length = total_num - right.len(); + } + + Ok(right) + } + + /// Calculates the number of elements if it is incorrect. + fn recalc_length(&mut self) { + fn dfs<'a, K, V>(node: NodeRef, K, V, marker::LeafOrInternal>) -> usize + where + K: 'a, + V: 'a, + { + let mut res = node.len(); + + if let Internal(node) = node.force() { + let mut edge = node.first_edge(); + loop { + res += dfs(edge.reborrow().descend()); + match edge.right_kv() { + Ok(right_kv) => { + edge = right_kv.right_edge(); + } + Err(_) => { + break; + } + } + } + } + + res + } + + self.length = dfs(self.root.as_ref()); + } + + /// Removes empty levels on the top. + fn fix_top(&mut self) { + loop { + { + let node = self.root.as_ref(); + if node.height() == 0 || node.len() > 0 { + break; + } + } + self.root.pop_level(); + } + } + + fn fix_right_border(&mut self) { + self.fix_top(); + + { + let mut cur_node = self.root.as_mut(); + + while let Internal(node) = cur_node.force() { + let mut last_kv = node.last_kv(); + + if last_kv.can_merge() { + cur_node = last_kv.merge().descend(); + } else { + let right_len = last_kv.reborrow().right_edge().descend().len(); + // `MINLEN + 1` to avoid readjust if merge happens on the next level. + if right_len < node::MIN_LEN + 1 { + last_kv.bulk_steal_left(node::MIN_LEN + 1 - right_len); + } + cur_node = last_kv.right_edge().descend(); + } + } + } + + self.fix_top(); + } + + /// The symmetric clone of `fix_right_border`. + fn fix_left_border(&mut self) { + self.fix_top(); + + { + let mut cur_node = self.root.as_mut(); + + while let Internal(node) = cur_node.force() { + let mut first_kv = node.first_kv(); + + if first_kv.can_merge() { + cur_node = first_kv.merge().descend(); + } else { + let left_len = first_kv.reborrow().left_edge().descend().len(); + if left_len < node::MIN_LEN + 1 { + first_kv.bulk_steal_right(node::MIN_LEN + 1 - left_len); + } + cur_node = first_kv.left_edge().descend(); + } + } + } + + self.fix_top(); + } + + /// If the root node is the shared root node, allocate our own node. + fn ensure_root_is_owned(&mut self) -> Result<(), TryReserveError> { + if self.root.is_shared_root() { + self.root = node::Root::new_leaf()?; + } + Ok(()) + } +} + +impl<'a, K: 'a, V: 'a> IntoIterator for &'a BTreeMap { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Iter<'a, K, V> { + self.iter() + } +} + +impl<'a, K: 'a, V: 'a> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + #[inline] + fn next(&mut self) -> Option<(&'a K, &'a V)> { + if self.length == 0 { + None + } else { + self.length -= 1; + unsafe { Some(self.range.next_unchecked()) } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.length, Some(self.length)) + } +} + +impl FusedIterator for Iter<'_, K, V> {} + +impl<'a, K: 'a, V: 'a> DoubleEndedIterator for Iter<'a, K, V> { + fn next_back(&mut self) -> Option<(&'a K, &'a V)> { + if self.length == 0 { + None + } else { + self.length -= 1; + unsafe { Some(self.range.next_back_unchecked()) } + } + } +} + +impl ExactSizeIterator for Iter<'_, K, V> { + #[inline(always)] + fn len(&self) -> usize { + self.length + } +} + +impl Clone for Iter<'_, K, V> { + fn clone(&self) -> Self { + Iter { + range: self.range.clone(), + length: self.length, + } + } +} + +impl<'a, K: 'a, V: 'a> IntoIterator for &'a mut BTreeMap { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + #[inline(always)] + fn into_iter(self) -> IterMut<'a, K, V> { + self.iter_mut() + } +} + +impl<'a, K: 'a, V: 'a> Iterator for IterMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + + #[inline] + fn next(&mut self) -> Option<(&'a K, &'a mut V)> { + if self.length == 0 { + None + } else { + self.length -= 1; + unsafe { Some(self.range.next_unchecked()) } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.length, Some(self.length)) + } +} + +impl<'a, K: 'a, V: 'a> DoubleEndedIterator for IterMut<'a, K, V> { + fn next_back(&mut self) -> Option<(&'a K, &'a mut V)> { + if self.length == 0 { + None + } else { + self.length -= 1; + unsafe { Some(self.range.next_back_unchecked()) } + } + } +} + +impl ExactSizeIterator for IterMut<'_, K, V> { + #[inline(always)] + fn len(&self) -> usize { + self.length + } +} + +impl FusedIterator for IterMut<'_, K, V> {} + +impl IntoIterator for BTreeMap { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> IntoIter { + let root1 = unsafe { ptr::read(&self.root).into_ref() }; + let root2 = unsafe { ptr::read(&self.root).into_ref() }; + let len = self.length; + mem::forget(self); + + IntoIter { + front: first_leaf_edge(root1), + back: last_leaf_edge(root2), + length: len, + } + } +} + +impl Drop for IntoIter { + fn drop(&mut self) { + self.for_each(drop); + unsafe { + let leaf_node = ptr::read(&self.front).into_node(); + if leaf_node.is_shared_root() { + return; + } + + if let Some(first_parent) = leaf_node.deallocate_and_ascend() { + let mut cur_node = first_parent.into_node(); + while let Some(parent) = cur_node.deallocate_and_ascend() { + cur_node = parent.into_node() + } + } + } + } +} + +impl Iterator for IntoIter { + type Item = (K, V); + + fn next(&mut self) -> Option<(K, V)> { + if self.length == 0 { + return None; + } else { + self.length -= 1; + } + + let handle = unsafe { ptr::read(&self.front) }; + + let mut cur_handle = match handle.right_kv() { + Ok(kv) => { + let k = unsafe { ptr::read(kv.reborrow().into_kv().0) }; + let v = unsafe { ptr::read(kv.reborrow().into_kv().1) }; + self.front = kv.right_edge(); + return Some((k, v)); + } + Err(last_edge) => unsafe { + unwrap_unchecked(last_edge.into_node().deallocate_and_ascend()) + }, + }; + + loop { + match cur_handle.right_kv() { + Ok(kv) => { + let k = unsafe { ptr::read(kv.reborrow().into_kv().0) }; + let v = unsafe { ptr::read(kv.reborrow().into_kv().1) }; + self.front = first_leaf_edge(kv.right_edge().descend()); + return Some((k, v)); + } + Err(last_edge) => unsafe { + cur_handle = unwrap_unchecked(last_edge.into_node().deallocate_and_ascend()); + }, + } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.length, Some(self.length)) + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option<(K, V)> { + if self.length == 0 { + return None; + } else { + self.length -= 1; + } + + let handle = unsafe { ptr::read(&self.back) }; + + let mut cur_handle = match handle.left_kv() { + Ok(kv) => { + let k = unsafe { ptr::read(kv.reborrow().into_kv().0) }; + let v = unsafe { ptr::read(kv.reborrow().into_kv().1) }; + self.back = kv.left_edge(); + return Some((k, v)); + } + Err(last_edge) => unsafe { + unwrap_unchecked(last_edge.into_node().deallocate_and_ascend()) + }, + }; + + loop { + match cur_handle.left_kv() { + Ok(kv) => { + let k = unsafe { ptr::read(kv.reborrow().into_kv().0) }; + let v = unsafe { ptr::read(kv.reborrow().into_kv().1) }; + self.back = last_leaf_edge(kv.left_edge().descend()); + return Some((k, v)); + } + Err(last_edge) => unsafe { + cur_handle = unwrap_unchecked(last_edge.into_node().deallocate_and_ascend()); + }, + } + } + } +} + +impl ExactSizeIterator for IntoIter { + #[inline(always)] + fn len(&self) -> usize { + self.length + } +} + +impl FusedIterator for IntoIter {} + +impl<'a, K, V> Iterator for Keys<'a, K, V> { + type Item = &'a K; + + #[inline] + fn next(&mut self) -> Option<&'a K> { + self.inner.next().map(|(k, _)| k) + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a, K, V> DoubleEndedIterator for Keys<'a, K, V> { + #[inline] + fn next_back(&mut self) -> Option<&'a K> { + self.inner.next_back().map(|(k, _)| k) + } +} + +impl ExactSizeIterator for Keys<'_, K, V> { + #[inline(always)] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for Keys<'_, K, V> {} + +impl Clone for Keys<'_, K, V> { + #[inline(always)] + fn clone(&self) -> Self { + Keys { + inner: self.inner.clone(), + } + } +} + +impl<'a, K, V> Iterator for Values<'a, K, V> { + type Item = &'a V; + + #[inline] + fn next(&mut self) -> Option<&'a V> { + self.inner.next().map(|(_, v)| v) + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a, K, V> DoubleEndedIterator for Values<'a, K, V> { + #[inline] + fn next_back(&mut self) -> Option<&'a V> { + self.inner.next_back().map(|(_, v)| v) + } +} + +impl ExactSizeIterator for Values<'_, K, V> { + #[inline(always)] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for Values<'_, K, V> {} + +impl Clone for Values<'_, K, V> { + #[inline(always)] + fn clone(&self) -> Self { + Values { + inner: self.inner.clone(), + } + } +} + +impl<'a, K, V> Iterator for Range<'a, K, V> { + type Item = (&'a K, &'a V); + + #[inline] + fn next(&mut self) -> Option<(&'a K, &'a V)> { + if self.front == self.back { + None + } else { + unsafe { Some(self.next_unchecked()) } + } + } +} + +impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { + type Item = &'a mut V; + + #[inline] + fn next(&mut self) -> Option<&'a mut V> { + self.inner.next().map(|(_, v)| v) + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a, K, V> DoubleEndedIterator for ValuesMut<'a, K, V> { + #[inline] + fn next_back(&mut self) -> Option<&'a mut V> { + self.inner.next_back().map(|(_, v)| v) + } +} + +impl ExactSizeIterator for ValuesMut<'_, K, V> { + #[inline(always)] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for ValuesMut<'_, K, V> {} + +impl<'a, K, V> Range<'a, K, V> { + unsafe fn next_unchecked(&mut self) -> (&'a K, &'a V) { + let handle = self.front; + + let mut cur_handle = match handle.right_kv() { + Ok(kv) => { + let ret = kv.into_kv(); + self.front = kv.right_edge(); + return ret; + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + unwrap_unchecked(next_level) + } + }; + + loop { + match cur_handle.right_kv() { + Ok(kv) => { + let ret = kv.into_kv(); + self.front = first_leaf_edge(kv.right_edge().descend()); + return ret; + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + cur_handle = unwrap_unchecked(next_level); + } + } + } + } +} + +impl<'a, K, V> DoubleEndedIterator for Range<'a, K, V> { + #[inline] + fn next_back(&mut self) -> Option<(&'a K, &'a V)> { + if self.front == self.back { + None + } else { + unsafe { Some(self.next_back_unchecked()) } + } + } +} + +impl<'a, K, V> Range<'a, K, V> { + unsafe fn next_back_unchecked(&mut self) -> (&'a K, &'a V) { + let handle = self.back; + + let mut cur_handle = match handle.left_kv() { + Ok(kv) => { + let ret = kv.into_kv(); + self.back = kv.left_edge(); + return ret; + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + unwrap_unchecked(next_level) + } + }; + + loop { + match cur_handle.left_kv() { + Ok(kv) => { + let ret = kv.into_kv(); + self.back = last_leaf_edge(kv.left_edge().descend()); + return ret; + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + cur_handle = unwrap_unchecked(next_level); + } + } + } + } +} + +impl FusedIterator for Range<'_, K, V> {} + +impl Clone for Range<'_, K, V> { + #[inline] + fn clone(&self) -> Self { + Range { + front: self.front, + back: self.back, + } + } +} + +impl<'a, K, V> Iterator for RangeMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + + #[inline] + fn next(&mut self) -> Option<(&'a K, &'a mut V)> { + if self.front == self.back { + None + } else { + unsafe { Some(self.next_unchecked()) } + } + } +} + +impl<'a, K, V> RangeMut<'a, K, V> { + unsafe fn next_unchecked(&mut self) -> (&'a K, &'a mut V) { + let handle = ptr::read(&self.front); + + let mut cur_handle = match handle.right_kv() { + Ok(kv) => { + self.front = ptr::read(&kv).right_edge(); + // Doing the descend invalidates the references returned by `into_kv_mut`, + // so we have to do this last. + let (k, v) = kv.into_kv_mut(); + return (k, v); // coerce k from `&mut K` to `&K` + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + unwrap_unchecked(next_level) + } + }; + + loop { + match cur_handle.right_kv() { + Ok(kv) => { + self.front = first_leaf_edge(ptr::read(&kv).right_edge().descend()); + // Doing the descend invalidates the references returned by `into_kv_mut`, + // so we have to do this last. + let (k, v) = kv.into_kv_mut(); + return (k, v); // coerce k from `&mut K` to `&K` + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + cur_handle = unwrap_unchecked(next_level); + } + } + } + } +} + +impl<'a, K, V> DoubleEndedIterator for RangeMut<'a, K, V> { + #[inline] + fn next_back(&mut self) -> Option<(&'a K, &'a mut V)> { + if self.front == self.back { + None + } else { + unsafe { Some(self.next_back_unchecked()) } + } + } +} + +impl FusedIterator for RangeMut<'_, K, V> {} + +impl<'a, K, V> RangeMut<'a, K, V> { + unsafe fn next_back_unchecked(&mut self) -> (&'a K, &'a mut V) { + let handle = ptr::read(&self.back); + + let mut cur_handle = match handle.left_kv() { + Ok(kv) => { + self.back = ptr::read(&kv).left_edge(); + // Doing the descend invalidates the references returned by `into_kv_mut`, + // so we have to do this last. + let (k, v) = kv.into_kv_mut(); + return (k, v); // coerce k from `&mut K` to `&K` + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + unwrap_unchecked(next_level) + } + }; + + loop { + match cur_handle.left_kv() { + Ok(kv) => { + self.back = last_leaf_edge(ptr::read(&kv).left_edge().descend()); + // Doing the descend invalidates the references returned by `into_kv_mut`, + // so we have to do this last. + let (k, v) = kv.into_kv_mut(); + return (k, v); // coerce k from `&mut K` to `&K` + } + Err(last_edge) => { + let next_level = last_edge.into_node().ascend().ok(); + cur_handle = unwrap_unchecked(next_level); + } + } + } + } +} + +impl FromIterator<(K, V)> for BTreeMap { + #[inline] + fn from_iter>(iter: T) -> BTreeMap { + let mut map = BTreeMap::new(); + map.extend(iter); + map + } +} + +impl Extend<(K, V)> for BTreeMap { + #[inline] + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(move |(k, v)| { + self.try_insert(k, v).expect("Out of Mem"); + }); + } +} + +impl<'a, K: Ord + Copy, V: Copy> Extend<(&'a K, &'a V)> for BTreeMap { + #[inline] + fn extend>(&mut self, iter: I) { + self.extend(iter.into_iter().map(|(&key, &value)| (key, value))); + } +} + +impl Hash for BTreeMap { + fn hash(&self, state: &mut H) { + for elt in self { + elt.hash(state); + } + } +} + +impl Default for BTreeMap { + /// Creates an empty `BTreeMap`. + #[inline(always)] + fn default() -> BTreeMap { + BTreeMap::new() + } +} + +impl PartialEq for BTreeMap { + fn eq(&self, other: &BTreeMap) -> bool { + self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a == b) + } +} + +impl Eq for BTreeMap {} + +impl PartialOrd for BTreeMap { + #[inline] + fn partial_cmp(&self, other: &BTreeMap) -> Option { + self.iter().partial_cmp(other.iter()) + } +} + +impl Ord for BTreeMap { + #[inline] + fn cmp(&self, other: &BTreeMap) -> Ordering { + self.iter().cmp(other.iter()) + } +} + +impl Debug for BTreeMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map().entries(self.iter()).finish() + } +} + +impl Index<&Q> for BTreeMap +where + K: Borrow, + Q: Ord, +{ + type Output = V; + + /// Returns a reference to the value corresponding to the supplied key. + /// + /// # Panics + /// + /// Panics if the key is not present in the `BTreeMap`. + #[inline] + fn index(&self, key: &Q) -> &V { + self.get(key).expect("no entry found for key") + } +} + +fn first_leaf_edge( + mut node: NodeRef, +) -> Handle, marker::Edge> { + loop { + match node.force() { + Leaf(leaf) => return leaf.first_edge(), + Internal(internal) => { + node = internal.first_edge().descend(); + } + } + } +} + +fn last_leaf_edge( + mut node: NodeRef, +) -> Handle, marker::Edge> { + loop { + match node.force() { + Leaf(leaf) => return leaf.last_edge(), + Internal(internal) => { + node = internal.last_edge().descend(); + } + } + } +} + +fn range_search>( + root1: NodeRef, + root2: NodeRef, + range: R, +) -> ( + Handle, marker::Edge>, + Handle, marker::Edge>, +) +where + Q: Ord, + K: Borrow, +{ + match (range.start_bound(), range.end_bound()) { + (Excluded(s), Excluded(e)) if s == e => { + panic!("range start and end are equal and excluded in BTreeMap") + } + (Included(s), Included(e)) + | (Included(s), Excluded(e)) + | (Excluded(s), Included(e)) + | (Excluded(s), Excluded(e)) + if s > e => + { + panic!("range start is greater than range end in BTreeMap") + } + _ => {} + }; + + let mut min_node = root1; + let mut max_node = root2; + let mut min_found = false; + let mut max_found = false; + let mut diverged = false; + + loop { + let min_edge = match (min_found, range.start_bound()) { + (false, Included(key)) => match search::search_linear(&min_node, key) { + (i, true) => { + min_found = true; + i + } + (i, false) => i, + }, + (false, Excluded(key)) => match search::search_linear(&min_node, key) { + (i, true) => { + min_found = true; + i + 1 + } + (i, false) => i, + }, + (_, Unbounded) => 0, + (true, Included(_)) => min_node.keys().len(), + (true, Excluded(_)) => 0, + }; + + let max_edge = match (max_found, range.end_bound()) { + (false, Included(key)) => match search::search_linear(&max_node, key) { + (i, true) => { + max_found = true; + i + 1 + } + (i, false) => i, + }, + (false, Excluded(key)) => match search::search_linear(&max_node, key) { + (i, true) => { + max_found = true; + i + } + (i, false) => i, + }, + (_, Unbounded) => max_node.keys().len(), + (true, Included(_)) => 0, + (true, Excluded(_)) => max_node.keys().len(), + }; + + if !diverged { + if max_edge < min_edge { + panic!("Ord is ill-defined in BTreeMap range") + } + if min_edge != max_edge { + diverged = true; + } + } + + let front = Handle::new_edge(min_node, min_edge); + let back = Handle::new_edge(max_node, max_edge); + match (front.force(), back.force()) { + (Leaf(f), Leaf(b)) => { + return (f, b); + } + (Internal(min_int), Internal(max_int)) => { + min_node = min_int.descend(); + max_node = max_int.descend(); + } + _ => unreachable!("BTreeMap has different depths"), + }; + } +} + +#[inline(always)] +unsafe fn unwrap_unchecked(val: Option) -> T { + val.unwrap_or_else(|| { + if cfg!(debug_assertions) { + panic!("'unchecked' unwrap on None in BTreeMap"); + } else { + intrinsics::unreachable(); + } + }) +} + +impl BTreeMap { + /// Gets an iterator over the entries of the map, sorted by key. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// map.insert(3, "c"); + /// map.insert(2, "b"); + /// map.insert(1, "a"); + /// + /// for (key, value) in map.iter() { + /// println!("{}: {}", key, value); + /// } + /// + /// let (first_key, first_value) = map.iter().next().unwrap(); + /// assert_eq!((*first_key, *first_value), (1, "a")); + /// ``` + + pub fn iter(&self) -> Iter<'_, K, V> { + Iter { + range: Range { + front: first_leaf_edge(self.root.as_ref()), + back: last_leaf_edge(self.root.as_ref()), + }, + length: self.length, + } + } + + /// Gets a mutable iterator over the entries of the map, sorted by key. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// map.insert("a", 1); + /// map.insert("b", 2); + /// map.insert("c", 3); + /// + /// // add 10 to the value if the key isn't "a" + /// for (key, value) in map.iter_mut() { + /// if key != &"a" { + /// *value += 10; + /// } + /// } + /// ``` + + pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { + let root1 = self.root.as_mut(); + let root2 = unsafe { ptr::read(&root1) }; + IterMut { + range: RangeMut { + front: first_leaf_edge(root1), + back: last_leaf_edge(root2), + _marker: PhantomData, + }, + length: self.length, + } + } + + /// Gets an iterator over the keys of the map, in sorted order. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// a.insert(2, "b"); + /// a.insert(1, "a"); + /// + /// let keys: Vec<_> = a.keys().cloned().collect(); + /// assert_eq!(keys, [1, 2]); + /// ``` + + #[inline(always)] + pub fn keys<'a>(&'a self) -> Keys<'a, K, V> { + Keys { inner: self.iter() } + } + + /// Gets an iterator over the values of the map, in order by key. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// a.insert(1, "hello"); + /// a.insert(2, "goodbye"); + /// + /// let values: Vec<&str> = a.values().cloned().collect(); + /// assert_eq!(values, ["hello", "goodbye"]); + /// ``` + + #[inline(always)] + pub fn values<'a>(&'a self) -> Values<'a, K, V> { + Values { inner: self.iter() } + } + + /// Gets a mutable iterator over the values of the map, in order by key. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// a.insert(1, String::from("hello")); + /// a.insert(2, String::from("goodbye")); + /// + /// for value in a.values_mut() { + /// value.push_str("!"); + /// } + /// + /// let values: Vec = a.values().cloned().collect(); + /// assert_eq!(values, [String::from("hello!"), + /// String::from("goodbye!")]); + /// ``` + + #[inline(always)] + pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { + ValuesMut { + inner: self.iter_mut(), + } + } + + /// Returns the number of elements in the map. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// assert_eq!(a.len(), 0); + /// a.insert(1, "a"); + /// assert_eq!(a.len(), 1); + /// ``` + + #[inline(always)] + pub fn len(&self) -> usize { + self.length + } + + /// Returns `true` if the map contains no elements. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut a = BTreeMap::new(); + /// assert!(a.is_empty()); + /// a.insert(1, "a"); + /// assert!(!a.is_empty()); + /// ``` + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'a, K: Ord, V> Entry<'a, K, V> { + /// Ensures a value is in the entry by inserting the default if empty, and returns + /// a mutable reference to the value in the entry. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// + /// assert_eq!(map["poneyland"], 12); + /// ``` + + pub fn or_try_insert(self, default: V) -> Result<&'a mut V, TryReserveError> { + match self { + Occupied(entry) => Ok(entry.into_mut()), + Vacant(entry) => entry.try_insert(default), + } + } + + /// Ensures a value is in the entry by inserting the result of the default function if empty, + /// and returns a mutable reference to the value in the entry. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, String> = BTreeMap::new(); + /// let s = "hoho".to_string(); + /// + /// map.entry("poneyland").or_insert_with(|| s); + /// + /// assert_eq!(map["poneyland"], "hoho".to_string()); + /// ``` + + pub fn or_try_insert_with V>( + self, + default: F, + ) -> Result<&'a mut V, TryReserveError> { + match self { + Occupied(entry) => Ok(entry.into_mut()), + Vacant(entry) => entry.try_insert(default()), + } + } + + /// Returns a reference to this entry's key. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); + /// ``` + + #[inline] + pub fn key(&self) -> &K { + match *self { + Occupied(ref entry) => entry.key(), + Vacant(ref entry) => entry.key(), + } + } + + /// Provides in-place mutable access to an occupied entry before any + /// potential inserts into the map. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// + /// map.entry("poneyland") + /// .and_modify(|e| { *e += 1 }) + /// .or_insert(42); + /// assert_eq!(map["poneyland"], 42); + /// + /// map.entry("poneyland") + /// .and_modify(|e| { *e += 1 }) + /// .or_insert(42); + /// assert_eq!(map["poneyland"], 43); + /// ``` + + pub fn and_modify(self, f: F) -> Self + where + F: FnOnce(&mut V), + { + match self { + Occupied(mut entry) => { + f(entry.get_mut()); + Occupied(entry) + } + Vacant(entry) => Vacant(entry), + } + } +} + +impl<'a, K: Ord, V: Default> Entry<'a, K, V> { + /// Ensures a value is in the entry by inserting the default value if empty, + /// and returns a mutable reference to the value in the entry. + /// + /// # Examples + /// + /// ``` + /// # fn main() { + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, Option> = BTreeMap::new(); + /// map.entry("poneyland").or_default(); + /// + /// assert_eq!(map["poneyland"], None); + /// # } + /// ``` + pub fn or_default(self) -> Result<&'a mut V, TryReserveError> { + match self { + Occupied(entry) => Ok(entry.into_mut()), + Vacant(entry) => entry.try_insert(Default::default()), + } + } +} + +impl<'a, K: Ord, V> VacantEntry<'a, K, V> { + /// Gets a reference to the key that would be used when inserting a value + /// through the VacantEntry. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); + /// ``` + + #[inline(always)] + pub fn key(&self) -> &K { + &self.key + } + + /// Take ownership of the key. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::collections::btree_map::Entry; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// + /// if let Entry::Vacant(v) = map.entry("poneyland") { + /// v.into_key(); + /// } + /// ``` + #[inline(always)] + pub fn into_key(self) -> K { + self.key + } + + /// Sets the value of the entry with the `VacantEntry`'s key, + /// and returns a mutable reference to it. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut count: BTreeMap<&str, usize> = BTreeMap::new(); + /// + /// // count the number of occurrences of letters in the vec + /// for x in vec!["a","b","a","c","a","b"] { + /// *count.entry(x).or_insert(0) += 1; + /// } + /// + /// assert_eq!(count["a"], 3); + /// ``` + + pub fn try_insert(self, value: V) -> Result<&'a mut V, TryReserveError> { + *self.length += 1; + + let out_ptr; + + let mut ins_k; + let mut ins_v; + let mut ins_edge; + + let mut cur_parent = match self.handle.insert(self.key, value)? { + (Fit(handle), _) => return Ok(handle.into_kv_mut().1), + (Split(left, k, v, right), ptr) => { + ins_k = k; + ins_v = v; + ins_edge = right; + out_ptr = ptr; + left.ascend().map_err(|n| n.into_root_mut()) + } + }; + + loop { + match cur_parent { + Ok(parent) => match parent.insert(ins_k, ins_v, ins_edge)? { + Fit(_) => return Ok(unsafe { &mut *out_ptr }), + Split(left, k, v, right) => { + ins_k = k; + ins_v = v; + ins_edge = right; + cur_parent = left.ascend().map_err(|n| n.into_root_mut()); + } + }, + Err(root) => { + root.push_level()?.push(ins_k, ins_v, ins_edge); + return Ok(unsafe { &mut *out_ptr }); + } + } + } + } +} + +impl<'a, K: Ord, V> OccupiedEntry<'a, K, V> { + /// Gets a reference to the key in the entry. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); + /// ``` + + #[inline] + pub fn key(&self) -> &K { + self.handle.reborrow().into_kv().0 + } + + /// Take ownership of the key and value from the map. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::collections::btree_map::Entry; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// + /// if let Entry::Occupied(o) = map.entry("poneyland") { + /// // We delete the entry from the map. + /// o.remove_entry(); + /// } + /// + /// // If now try to get the value, it will panic: + /// // println!("{}", map["poneyland"]); + /// ``` + + #[inline] + pub fn remove_entry(self) -> (K, V) { + self.remove_kv() + } + + /// Gets a reference to the value in the entry. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::collections::btree_map::Entry; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// + /// if let Entry::Occupied(o) = map.entry("poneyland") { + /// assert_eq!(o.get(), &12); + /// } + /// ``` + + #[inline] + pub fn get(&self) -> &V { + self.handle.reborrow().into_kv().1 + } + + /// Gets a mutable reference to the value in the entry. + /// + /// If you need a reference to the `OccupiedEntry` that may outlive the + /// destruction of the `Entry` value, see [`into_mut`]. + /// + /// [`into_mut`]: #method.into_mut + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::collections::btree_map::Entry; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// + /// assert_eq!(map["poneyland"], 12); + /// if let Entry::Occupied(mut o) = map.entry("poneyland") { + /// *o.get_mut() += 10; + /// assert_eq!(*o.get(), 22); + /// + /// // We can use the same Entry multiple times. + /// *o.get_mut() += 2; + /// } + /// assert_eq!(map["poneyland"], 24); + /// ``` + #[inline] + pub fn get_mut(&mut self) -> &mut V { + self.handle.kv_mut().1 + } + + /// Converts the entry into a mutable reference to its value. + /// + /// If you need multiple references to the `OccupiedEntry`, see [`get_mut`]. + /// + /// [`get_mut`]: #method.get_mut + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::collections::btree_map::Entry; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// + /// assert_eq!(map["poneyland"], 12); + /// if let Entry::Occupied(o) = map.entry("poneyland") { + /// *o.into_mut() += 10; + /// } + /// assert_eq!(map["poneyland"], 22); + /// ``` + #[inline] + pub fn into_mut(self) -> &'a mut V { + self.handle.into_kv_mut().1 + } + + /// Sets the value of the entry with the `OccupiedEntry`'s key, + /// and returns the entry's old value. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::collections::btree_map::Entry; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// + /// if let Entry::Occupied(mut o) = map.entry("poneyland") { + /// assert_eq!(o.insert(15), 12); + /// } + /// assert_eq!(map["poneyland"], 15); + /// ``` + #[inline] + pub fn insert(&mut self, value: V) -> V { + mem::replace(self.get_mut(), value) + } + + /// Takes the value of the entry out of the map, and returns it. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeMap; + /// use std::collections::btree_map::Entry; + /// + /// let mut map: BTreeMap<&str, usize> = BTreeMap::new(); + /// map.entry("poneyland").or_insert(12); + /// + /// if let Entry::Occupied(o) = map.entry("poneyland") { + /// assert_eq!(o.remove(), 12); + /// } + /// // If we try to get "poneyland"'s value, it'll panic: + /// // println!("{}", map["poneyland"]); + /// ``` + #[inline] + pub fn remove(self) -> V { + self.remove_kv().1 + } + + fn remove_kv(self) -> (K, V) { + *self.length -= 1; + + let (small_leaf, old_key, old_val) = match self.handle.force() { + Leaf(leaf) => { + let (hole, old_key, old_val) = leaf.remove(); + (hole.into_node(), old_key, old_val) + } + Internal(mut internal) => { + let key_loc = internal.kv_mut().0 as *mut K; + let val_loc = internal.kv_mut().1 as *mut V; + + let to_remove = first_leaf_edge(internal.right_edge().descend()) + .right_kv() + .ok(); + let to_remove = unsafe { unwrap_unchecked(to_remove) }; + + let (hole, key, val) = to_remove.remove(); + + let old_key = unsafe { mem::replace(&mut *key_loc, key) }; + let old_val = unsafe { mem::replace(&mut *val_loc, val) }; + + (hole.into_node(), old_key, old_val) + } + }; + + // Handle underflow + let mut cur_node = small_leaf.forget_type(); + while cur_node.len() < node::CAPACITY / 2 { + match handle_underfull_node(cur_node) { + AtRoot => break, + EmptyParent(_) => unreachable!(), + Merged(parent) => { + if parent.len() == 0 { + // We must be at the root + parent.into_root_mut().pop_level(); + break; + } else { + cur_node = parent.forget_type(); + } + } + Stole(_) => break, + } + } + + (old_key, old_val) + } +} + +enum UnderflowResult<'a, K, V> { + AtRoot, + EmptyParent(NodeRef, K, V, marker::Internal>), + Merged(NodeRef, K, V, marker::Internal>), + Stole(NodeRef, K, V, marker::Internal>), +} + +fn handle_underfull_node<'a, K, V>( + node: NodeRef, K, V, marker::LeafOrInternal>, +) -> UnderflowResult<'a, K, V> { + let parent = if let Ok(parent) = node.ascend() { + parent + } else { + return AtRoot; + }; + + let (is_left, mut handle) = match parent.left_kv() { + Ok(left) => (true, left), + Err(parent) => match parent.right_kv() { + Ok(right) => (false, right), + Err(parent) => { + return EmptyParent(parent.into_node()); + } + }, + }; + + if handle.can_merge() { + Merged(handle.merge().into_node()) + } else { + if is_left { + handle.steal_left(); + } else { + handle.steal_right(); + } + Stole(handle.into_node()) + } +} + +impl> Iterator for MergeIter { + type Item = (K, V); + + fn next(&mut self) -> Option<(K, V)> { + let res = match (self.left.peek(), self.right.peek()) { + (Some(&(ref left_key, _)), Some(&(ref right_key, _))) => left_key.cmp(right_key), + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (None, None) => return None, + }; + + // Check which elements comes first and only advance the corresponding iterator. + // If two keys are equal, take the value from `right`. + match res { + Ordering::Less => self.left.next(), + Ordering::Greater => self.right.next(), + Ordering::Equal => { + self.left.next(); + self.right.next() + } + } + } +} diff --git a/third_party/rust/fallible_collections/src/btree/node.rs b/third_party/rust/fallible_collections/src/btree/node.rs new file mode 100644 index 0000000000..09efa75774 --- /dev/null +++ b/third_party/rust/fallible_collections/src/btree/node.rs @@ -0,0 +1,1673 @@ +// This is an attempt at an implementation following the ideal +// +// ``` +// struct BTreeMap { +// height: usize, +// root: Option>> +// } +// +// struct Node { +// keys: [K; 2 * B - 1], +// vals: [V; 2 * B - 1], +// edges: if height > 0 { +// [Box>; 2 * B] +// } else { () }, +// parent: *const Node, +// parent_idx: u16, +// len: u16, +// } +// ``` +// +// Since Rust doesn't actually have dependent types and polymorphic recursion, +// we make do with lots of unsafety. + +// A major goal of this module is to avoid complexity by treating the tree as a generic (if +// weirdly shaped) container and avoiding dealing with most of the B-Tree invariants. As such, +// this module doesn't care whether the entries are sorted, which nodes can be underfull, or +// even what underfull means. However, we do rely on a few invariants: +// +// - Trees must have uniform depth/height. This means that every path down to a leaf from a +// given node has exactly the same length. +// - A node of length `n` has `n` keys, `n` values, and (in an internal node) `n + 1` edges. +// This implies that even an empty internal node has at least one edge. + +use core::marker::PhantomData; +use core::mem::{self, MaybeUninit}; +use core::ptr::{self, NonNull, Unique}; +use core::slice; + +use crate::boxed::FallibleBox; +use crate::TryReserveError; +use alloc::alloc::{AllocRef, Global, Layout}; +use alloc::boxed::Box; + +const B: usize = 6; +pub const MIN_LEN: usize = B - 1; +pub const CAPACITY: usize = 2 * B - 1; + +/// The underlying representation of leaf nodes. Note that it is often unsafe to actually store +/// these, since only the first `len` keys and values are assumed to be initialized. As such, +/// these should always be put behind pointers, and specifically behind `BoxedNode` in the owned +/// case. +/// +/// We have a separate type for the header and rely on it matching the prefix of `LeafNode`, in +/// order to statically allocate a single dummy node to avoid allocations. This struct is +/// `repr(C)` to prevent them from being reordered. `LeafNode` does not just contain a +/// `NodeHeader` because we do not want unnecessary padding between `len` and the keys. +/// Crucially, `NodeHeader` can be safely transmuted to different K and V. (This is exploited +/// by `as_header`.) +/// See `into_key_slice` for an explanation of K2. K2 cannot be safely transmuted around +/// because the size of `NodeHeader` depends on its alignment! +#[repr(C)] +struct NodeHeader { + /// We use `*const` as opposed to `*mut` so as to be covariant in `K` and `V`. + /// This either points to an actual node or is null. + parent: *const InternalNode, + + /// This node's index into the parent node's `edges` array. + /// `*node.parent.edges[node.parent_idx]` should be the same thing as `node`. + /// This is only guaranteed to be initialized when `parent` is non-null. + parent_idx: MaybeUninit, + + /// The number of keys and values this node stores. + /// + /// This next to `parent_idx` to encourage the compiler to join `len` and + /// `parent_idx` into the same 32-bit word, reducing space overhead. + len: u16, + + /// See `into_key_slice`. + keys_start: [K2; 0], +} +#[repr(C)] +struct LeafNode { + /// We use `*const` as opposed to `*mut` so as to be covariant in `K` and `V`. + /// This either points to an actual node or is null. + parent: *const InternalNode, + + /// This node's index into the parent node's `edges` array. + /// `*node.parent.edges[node.parent_idx]` should be the same thing as `node`. + /// This is only guaranteed to be initialized when `parent` is non-null. + parent_idx: MaybeUninit, + + /// The number of keys and values this node stores. + /// + /// This next to `parent_idx` to encourage the compiler to join `len` and + /// `parent_idx` into the same 32-bit word, reducing space overhead. + len: u16, + + /// The arrays storing the actual data of the node. Only the first `len` elements of each + /// array are initialized and valid. + keys: [MaybeUninit; CAPACITY], + vals: [MaybeUninit; CAPACITY], +} + +impl LeafNode { + /// Creates a new `LeafNode`. Unsafe because all nodes should really be hidden behind + /// `BoxedNode`, preventing accidental dropping of uninitialized keys and values. + unsafe fn new() -> Self { + LeafNode { + // As a general policy, we leave fields uninitialized if they can be, as this should + // be both slightly faster and easier to track in Valgrind. + keys: [MaybeUninit::UNINIT; CAPACITY], + vals: [MaybeUninit::UNINIT; CAPACITY], + parent: ptr::null(), + parent_idx: MaybeUninit::uninit(), + len: 0, + } + } +} + +impl NodeHeader { + fn is_shared_root(&self) -> bool { + ptr::eq(self, &EMPTY_ROOT_NODE as *const _ as *const _) + } +} + +// We need to implement Sync here in order to make a static instance. +unsafe impl Sync for NodeHeader<(), ()> {} + +// An empty node used as a placeholder for the root node, to avoid allocations. +// We use just a header in order to save space, since no operation on an empty tree will +// ever take a pointer past the first key. +static EMPTY_ROOT_NODE: NodeHeader<(), ()> = NodeHeader { + parent: ptr::null(), + parent_idx: MaybeUninit::uninit(), + len: 0, + keys_start: [], +}; + +/// The underlying representation of internal nodes. As with `LeafNode`s, these should be hidden +/// behind `BoxedNode`s to prevent dropping uninitialized keys and values. Any pointer to an +/// `InternalNode` can be directly casted to a pointer to the underlying `LeafNode` portion of the +/// node, allowing code to act on leaf and internal nodes generically without having to even check +/// which of the two a pointer is pointing at. This property is enabled by the use of `repr(C)`. +#[repr(C)] +struct InternalNode { + data: LeafNode, + + /// The pointers to the children of this node. `len + 1` of these are considered + /// initialized and valid. + edges: [MaybeUninit>; 2 * B], +} + +impl InternalNode { + /// Creates a new `InternalNode`. + /// + /// This is unsafe for two reasons. First, it returns an `InternalNode` by value, risking + /// dropping of uninitialized fields. Second, an invariant of internal nodes is that `len + 1` + /// edges are initialized and valid, meaning that even when the node is empty (having a + /// `len` of 0), there must be one initialized and valid edge. This function does not set up + /// such an edge. + unsafe fn new() -> Self { + InternalNode { + data: LeafNode::new(), + edges: [MaybeUninit::UNINIT; 2 * B], + } + } +} + +/// An owned pointer to a node. This basically is either `Box>` or +/// `Box>`. However, it contains no information as to which of the two types +/// of nodes is actually behind the box, and, partially due to this lack of information, has no +/// destructor. +struct BoxedNode { + ptr: Unique>, +} + +impl BoxedNode { + fn from_leaf(node: Box>) -> Self { + BoxedNode { + ptr: Box::into_unique(node), + } + } + + fn from_internal(node: Box>) -> Self { + unsafe { + BoxedNode { + ptr: Unique::new_unchecked(Box::into_raw(node) as *mut LeafNode), + } + } + } + + unsafe fn from_ptr(ptr: NonNull>) -> Self { + BoxedNode { + ptr: Unique::new_unchecked(ptr.as_ptr()), + } + } + + fn as_ptr(&self) -> NonNull> { + NonNull::from(self.ptr) + } +} + +/// An owned tree. Note that despite being owned, this does not have a destructor, +/// and must be cleaned up manually. +pub struct Root { + node: BoxedNode, + height: usize, +} + +unsafe impl Sync for Root {} +unsafe impl Send for Root {} + +impl Root { + pub fn is_shared_root(&self) -> bool { + self.as_ref().is_shared_root() + } + + pub fn shared_empty_root() -> Self { + Root { + node: unsafe { + BoxedNode::from_ptr(NonNull::new_unchecked( + &EMPTY_ROOT_NODE as *const _ as *const LeafNode as *mut _, + )) + }, + height: 0, + } + } + + pub fn new_leaf() -> Result { + Ok(Root { + node: BoxedNode::from_leaf( as FallibleBox<_>>::try_new(unsafe { LeafNode::new() })?), + height: 0, + }) + } + + pub fn as_ref(&self) -> NodeRef, K, V, marker::LeafOrInternal> { + NodeRef { + height: self.height, + node: self.node.as_ptr(), + root: self as *const _ as *mut _, + _marker: PhantomData, + } + } + + pub fn as_mut(&mut self) -> NodeRef, K, V, marker::LeafOrInternal> { + NodeRef { + height: self.height, + node: self.node.as_ptr(), + root: self as *mut _, + _marker: PhantomData, + } + } + + pub fn into_ref(self) -> NodeRef { + NodeRef { + height: self.height, + node: self.node.as_ptr(), + root: ptr::null_mut(), // FIXME: Is there anything better to do here? + _marker: PhantomData, + } + } + + /// Adds a new internal node with a single edge, pointing to the previous root, and make that + /// new node the root. This increases the height by 1 and is the opposite of `pop_level`. + pub fn push_level( + &mut self, + ) -> Result, K, V, marker::Internal>, TryReserveError> { + debug_assert!(!self.is_shared_root()); + let mut new_node = as FallibleBox<_>>::try_new(unsafe { InternalNode::new() })?; + new_node.edges[0].write(unsafe { BoxedNode::from_ptr(self.node.as_ptr()) }); + + self.node = BoxedNode::from_internal(new_node); + self.height += 1; + + let mut ret = NodeRef { + height: self.height, + node: self.node.as_ptr(), + root: self as *mut _, + _marker: PhantomData, + }; + + unsafe { + ret.reborrow_mut().first_edge().correct_parent_link(); + } + + Ok(ret) + } + + /// Removes the root node, using its first child as the new root. This cannot be called when + /// the tree consists only of a leaf node. As it is intended only to be called when the root + /// has only one edge, no cleanup is done on any of the other children are elements of the root. + /// This decreases the height by 1 and is the opposite of `push_level`. + pub fn pop_level(&mut self) { + debug_assert!(self.height > 0); + + let top = self.node.ptr; + + self.node = unsafe { + BoxedNode::from_ptr( + self.as_mut() + .cast_unchecked::() + .first_edge() + .descend() + .node, + ) + }; + self.height -= 1; + unsafe { + (*self.as_mut().as_leaf_mut()).parent = ptr::null(); + } + + unsafe { + Global.dealloc( + NonNull::from(top).cast(), + Layout::new::>(), + ); + } + } +} + +// N.B. `NodeRef` is always covariant in `K` and `V`, even when the `BorrowType` +// is `Mut`. This is technically wrong, but cannot result in any unsafety due to +// internal use of `NodeRef` because we stay completely generic over `K` and `V`. +// However, whenever a public type wraps `NodeRef`, make sure that it has the +// correct variance. +/// A reference to a node. +/// +/// This type has a number of parameters that controls how it acts: +/// - `BorrowType`: This can be `Immut<'a>` or `Mut<'a>` for some `'a` or `Owned`. +/// When this is `Immut<'a>`, the `NodeRef` acts roughly like `&'a Node`, +/// when this is `Mut<'a>`, the `NodeRef` acts roughly like `&'a mut Node`, +/// and when this is `Owned`, the `NodeRef` acts roughly like `Box`. +/// - `K` and `V`: These control what types of things are stored in the nodes. +/// - `Type`: This can be `Leaf`, `Internal`, or `LeafOrInternal`. When this is +/// `Leaf`, the `NodeRef` points to a leaf node, when this is `Internal` the +/// `NodeRef` points to an internal node, and when this is `LeafOrInternal` the +/// `NodeRef` could be pointing to either type of node. +/// Note that in case of a leaf node, this might still be the shared root! Only turn +/// this into a `LeafNode` reference if you know it is not a root! Shared references +/// must be dereferencable *for the entire size of their pointee*, so `&InternalNode` +/// pointing to the shared root is UB. +/// Turning this into a `NodeHeader` is always safe. +pub struct NodeRef { + height: usize, + node: NonNull>, + // This is null unless the borrow type is `Mut` + root: *const Root, + _marker: PhantomData<(BorrowType, Type)>, +} + +impl<'a, K: 'a, V: 'a, Type> Copy for NodeRef, K, V, Type> {} +impl<'a, K: 'a, V: 'a, Type> Clone for NodeRef, K, V, Type> { + fn clone(&self) -> Self { + *self + } +} + +unsafe impl Sync for NodeRef {} + +unsafe impl<'a, K: Sync + 'a, V: Sync + 'a, Type> Send for NodeRef, K, V, Type> {} +unsafe impl<'a, K: Send + 'a, V: Send + 'a, Type> Send for NodeRef, K, V, Type> {} +unsafe impl Send for NodeRef {} + +impl NodeRef { + fn as_internal(&self) -> &InternalNode { + unsafe { &*(self.node.as_ptr() as *mut InternalNode) } + } +} + +impl<'a, K, V> NodeRef, K, V, marker::Internal> { + fn as_internal_mut(&mut self) -> &mut InternalNode { + unsafe { &mut *(self.node.as_ptr() as *mut InternalNode) } + } +} + +impl NodeRef { + /// Finds the length of the node. This is the number of keys or values. In an + /// internal node, the number of edges is `len() + 1`. + pub fn len(&self) -> usize { + self.as_header().len as usize + } + + /// Returns the height of this node in the whole tree. Zero height denotes the + /// leaf level. + pub fn height(&self) -> usize { + self.height + } + + /// Removes any static information about whether this node is a `Leaf` or an + /// `Internal` node. + pub fn forget_type(self) -> NodeRef { + NodeRef { + height: self.height, + node: self.node, + root: self.root, + _marker: PhantomData, + } + } + + /// Temporarily takes out another, immutable reference to the same node. + fn reborrow<'a>(&'a self) -> NodeRef, K, V, Type> { + NodeRef { + height: self.height, + node: self.node, + root: self.root, + _marker: PhantomData, + } + } + + /// Assert that this is indeed a proper leaf node, and not the shared root. + unsafe fn as_leaf(&self) -> &LeafNode { + self.node.as_ref() + } + + fn as_header(&self) -> &NodeHeader { + unsafe { &*(self.node.as_ptr() as *const NodeHeader) } + } + + pub fn is_shared_root(&self) -> bool { + self.as_header().is_shared_root() + } + + pub fn keys(&self) -> &[K] { + self.reborrow().into_key_slice() + } + + fn vals(&self) -> &[V] { + self.reborrow().into_val_slice() + } + + /// Finds the parent of the current node. Returns `Ok(handle)` if the current + /// node actually has a parent, where `handle` points to the edge of the parent + /// that points to the current node. Returns `Err(self)` if the current node has + /// no parent, giving back the original `NodeRef`. + /// + /// `edge.descend().ascend().unwrap()` and `node.ascend().unwrap().descend()` should + /// both, upon success, do nothing. + pub fn ascend( + self, + ) -> Result, marker::Edge>, Self> { + let parent_as_leaf = self.as_header().parent as *const LeafNode; + if let Some(non_zero) = NonNull::new(parent_as_leaf as *mut _) { + Ok(Handle { + node: NodeRef { + height: self.height + 1, + node: non_zero, + root: self.root, + _marker: PhantomData, + }, + idx: unsafe { usize::from(*self.as_header().parent_idx.as_ptr()) }, + _marker: PhantomData, + }) + } else { + Err(self) + } + } + + pub fn first_edge(self) -> Handle { + Handle::new_edge(self, 0) + } + + pub fn last_edge(self) -> Handle { + let len = self.len(); + Handle::new_edge(self, len) + } + + /// Note that `self` must be nonempty. + pub fn first_kv(self) -> Handle { + debug_assert!(self.len() > 0); + Handle::new_kv(self, 0) + } + + /// Note that `self` must be nonempty. + pub fn last_kv(self) -> Handle { + let len = self.len(); + debug_assert!(len > 0); + Handle::new_kv(self, len - 1) + } +} + +impl NodeRef { + /// Similar to `ascend`, gets a reference to a node's parent node, but also + /// deallocate the current node in the process. This is unsafe because the + /// current node will still be accessible despite being deallocated. + pub unsafe fn deallocate_and_ascend( + self, + ) -> Option, marker::Edge>> { + debug_assert!(!self.is_shared_root()); + let node = self.node; + let ret = self.ascend().ok(); + Global.dealloc(node.cast(), Layout::new::>()); + ret + } +} + +impl NodeRef { + /// Similar to `ascend`, gets a reference to a node's parent node, but also + /// deallocate the current node in the process. This is unsafe because the + /// current node will still be accessible despite being deallocated. + pub unsafe fn deallocate_and_ascend( + self, + ) -> Option, marker::Edge>> { + let node = self.node; + let ret = self.ascend().ok(); + Global.dealloc(node.cast(), Layout::new::>()); + ret + } +} + +impl<'a, K, V, Type> NodeRef, K, V, Type> { + /// Unsafely asserts to the compiler some static information about whether this + /// node is a `Leaf`. + unsafe fn cast_unchecked(&mut self) -> NodeRef, K, V, NewType> { + NodeRef { + height: self.height, + node: self.node, + root: self.root, + _marker: PhantomData, + } + } + + /// Temporarily takes out another, mutable reference to the same node. Beware, as + /// this method is very dangerous, doubly so since it may not immediately appear + /// dangerous. + /// + /// Because mutable pointers can roam anywhere around the tree and can even (through + /// `into_root_mut`) mess with the root of the tree, the result of `reborrow_mut` + /// can easily be used to make the original mutable pointer dangling, or, in the case + /// of a reborrowed handle, out of bounds. + // FIXME(@gereeter) consider adding yet another type parameter to `NodeRef` that restricts + // the use of `ascend` and `into_root_mut` on reborrowed pointers, preventing this unsafety. + unsafe fn reborrow_mut(&mut self) -> NodeRef, K, V, Type> { + NodeRef { + height: self.height, + node: self.node, + root: self.root, + _marker: PhantomData, + } + } + + /// Returns a raw ptr to avoid asserting exclusive access to the entire node. + fn as_leaf_mut(&mut self) -> *mut LeafNode { + // We are mutable, so we cannot be the root, so accessing this as a leaf is okay. + self.node.as_ptr() + } + + fn keys_mut(&mut self) -> &mut [K] { + unsafe { self.reborrow_mut().into_key_slice_mut() } + } + + fn vals_mut(&mut self) -> &mut [V] { + unsafe { self.reborrow_mut().into_val_slice_mut() } + } +} + +impl<'a, K: 'a, V: 'a, Type> NodeRef, K, V, Type> { + fn into_key_slice(self) -> &'a [K] { + // We have to be careful here because we might be pointing to the shared root. + // In that case, we must not create an `&LeafNode`. We could just return + // an empty slice whenever the length is 0 (this includes the shared root), + // but we want to avoid that run-time check. + // Instead, we create a slice pointing into the node whenever possible. + // We can sometimes do this even for the shared root, as the slice will be + // empty. We cannot *always* do this because if the type is too highly + // aligned, the offset of `keys` in a "full node" might be outside the bounds + // of the header! So we do an alignment check first, that will be + // evaluated at compile-time, and only do any run-time check in the rare case + // that the alignment is very big. + if mem::align_of::() > mem::align_of::>() && self.is_shared_root() { + &[] + } else { + // Thanks to the alignment check above, we know that `keys` will be + // in-bounds of some allocation even if this is the shared root! + // (We might be one-past-the-end, but that is allowed by LLVM.) + // Getting the pointer is tricky though. `NodeHeader` does not have a `keys` + // field because we want its size to not depend on the alignment of `K` + // (needed becuase `as_header` should be safe). We cannot call `as_leaf` + // because we might be the shared root. + // For this reason, `NodeHeader` has this `K2` parameter (that's usually `()` + // and hence just adds a size-0-align-1 field, not affecting layout). + // We know that we can transmute `NodeHeader` to `NodeHeader` + // because we did the alignment check above, and hence `NodeHeader` + // is not bigger than `NodeHeader`! Then we can use `NodeHeader` + // to compute the pointer where the keys start. + // This entire hack will become unnecessary once + // lands, then we can just take a raw + // pointer to the `keys` field of `*const InternalNode`. + + // This is a non-debug-assert because it can be completely compile-time evaluated. + assert!(mem::size_of::>() == mem::size_of::>()); + let header = self.as_header() as *const _ as *const NodeHeader; + let keys = unsafe { &(*header).keys_start as *const _ as *const K }; + unsafe { slice::from_raw_parts(keys, self.len()) } + } + } + + fn into_val_slice(self) -> &'a [V] { + debug_assert!(!self.is_shared_root()); + // We cannot be the root, so `as_leaf` is okay + unsafe { slice::from_raw_parts(MaybeUninit::first_ptr(&self.as_leaf().vals), self.len()) } + } + + fn into_slices(self) -> (&'a [K], &'a [V]) { + let k = unsafe { ptr::read(&self) }; + (k.into_key_slice(), self.into_val_slice()) + } +} + +impl<'a, K: 'a, V: 'a, Type> NodeRef, K, V, Type> { + /// Gets a mutable reference to the root itself. This is useful primarily when the + /// height of the tree needs to be adjusted. Never call this on a reborrowed pointer. + pub fn into_root_mut(self) -> &'a mut Root { + unsafe { &mut *(self.root as *mut Root) } + } + + fn into_key_slice_mut(mut self) -> &'a mut [K] { + // Same as for `into_key_slice` above, we try to avoid a run-time check + // (the alignment comparison will usually be performed at compile-time). + if mem::align_of::() > mem::align_of::>() && self.is_shared_root() { + &mut [] + } else { + unsafe { + slice::from_raw_parts_mut( + MaybeUninit::first_ptr_mut(&mut (*self.as_leaf_mut()).keys), + self.len(), + ) + } + } + } + + fn into_val_slice_mut(mut self) -> &'a mut [V] { + debug_assert!(!self.is_shared_root()); + unsafe { + slice::from_raw_parts_mut( + MaybeUninit::first_ptr_mut(&mut (*self.as_leaf_mut()).vals), + self.len(), + ) + } + } + + fn into_slices_mut(mut self) -> (&'a mut [K], &'a mut [V]) { + debug_assert!(!self.is_shared_root()); + // We cannot use the getters here, because calling the second one + // invalidates the reference returned by the first. + // More precisely, it is the call to `len` that is the culprit, + // because that creates a shared reference to the header, which *can* + // overlap with the keys (and even the values, for ZST keys). + unsafe { + let len = self.len(); + let leaf = self.as_leaf_mut(); + let keys = + slice::from_raw_parts_mut(MaybeUninit::first_ptr_mut(&mut (*leaf).keys), len); + let vals = + slice::from_raw_parts_mut(MaybeUninit::first_ptr_mut(&mut (*leaf).vals), len); + (keys, vals) + } + } +} + +impl<'a, K, V> NodeRef, K, V, marker::Leaf> { + /// Adds a key/value pair the end of the node. + pub fn push(&mut self, key: K, val: V) { + // Necessary for correctness, but this is an internal module + debug_assert!(self.len() < CAPACITY); + debug_assert!(!self.is_shared_root()); + + let idx = self.len(); + + unsafe { + ptr::write(self.keys_mut().get_unchecked_mut(idx), key); + ptr::write(self.vals_mut().get_unchecked_mut(idx), val); + + (*self.as_leaf_mut()).len += 1; + } + } + + /// Adds a key/value pair to the beginning of the node. + pub fn push_front(&mut self, key: K, val: V) { + // Necessary for correctness, but this is an internal module + debug_assert!(self.len() < CAPACITY); + debug_assert!(!self.is_shared_root()); + + unsafe { + slice_insert(self.keys_mut(), 0, key); + slice_insert(self.vals_mut(), 0, val); + + (*self.as_leaf_mut()).len += 1; + } + } +} + +impl<'a, K, V> NodeRef, K, V, marker::Internal> { + /// Adds a key/value pair and an edge to go to the right of that pair to + /// the end of the node. + pub fn push(&mut self, key: K, val: V, edge: Root) { + // Necessary for correctness, but this is an internal module + debug_assert!(edge.height == self.height - 1); + debug_assert!(self.len() < CAPACITY); + + let idx = self.len(); + + unsafe { + ptr::write(self.keys_mut().get_unchecked_mut(idx), key); + ptr::write(self.vals_mut().get_unchecked_mut(idx), val); + self.as_internal_mut() + .edges + .get_unchecked_mut(idx + 1) + .write(edge.node); + + (*self.as_leaf_mut()).len += 1; + + Handle::new_edge(self.reborrow_mut(), idx + 1).correct_parent_link(); + } + } + + fn correct_childrens_parent_links(&mut self, first: usize, after_last: usize) { + for i in first..after_last { + Handle::new_edge(unsafe { self.reborrow_mut() }, i).correct_parent_link(); + } + } + + fn correct_all_childrens_parent_links(&mut self) { + let len = self.len(); + self.correct_childrens_parent_links(0, len + 1); + } + + /// Adds a key/value pair and an edge to go to the left of that pair to + /// the beginning of the node. + pub fn push_front(&mut self, key: K, val: V, edge: Root) { + // Necessary for correctness, but this is an internal module + debug_assert!(edge.height == self.height - 1); + debug_assert!(self.len() < CAPACITY); + + unsafe { + slice_insert(self.keys_mut(), 0, key); + slice_insert(self.vals_mut(), 0, val); + slice_insert( + slice::from_raw_parts_mut( + MaybeUninit::first_ptr_mut(&mut self.as_internal_mut().edges), + self.len() + 1, + ), + 0, + edge.node, + ); + + (*self.as_leaf_mut()).len += 1; + + self.correct_all_childrens_parent_links(); + } + } +} + +impl<'a, K, V> NodeRef, K, V, marker::LeafOrInternal> { + /// Removes a key/value pair from the end of this node. If this is an internal node, + /// also removes the edge that was to the right of that pair. + pub fn pop(&mut self) -> (K, V, Option>) { + // Necessary for correctness, but this is an internal module + debug_assert!(self.len() > 0); + + let idx = self.len() - 1; + + unsafe { + let key = ptr::read(self.keys().get_unchecked(idx)); + let val = ptr::read(self.vals().get_unchecked(idx)); + let edge = match self.reborrow_mut().force() { + ForceResult::Leaf(_) => None, + ForceResult::Internal(internal) => { + let edge = + ptr::read(internal.as_internal().edges.get_unchecked(idx + 1).as_ptr()); + let mut new_root = Root { + node: edge, + height: internal.height - 1, + }; + (*new_root.as_mut().as_leaf_mut()).parent = ptr::null(); + Some(new_root) + } + }; + + (*self.as_leaf_mut()).len -= 1; + (key, val, edge) + } + } + + /// Removes a key/value pair from the beginning of this node. If this is an internal node, + /// also removes the edge that was to the left of that pair. + pub fn pop_front(&mut self) -> (K, V, Option>) { + // Necessary for correctness, but this is an internal module + debug_assert!(self.len() > 0); + + let old_len = self.len(); + + unsafe { + let key = slice_remove(self.keys_mut(), 0); + let val = slice_remove(self.vals_mut(), 0); + let edge = match self.reborrow_mut().force() { + ForceResult::Leaf(_) => None, + ForceResult::Internal(mut internal) => { + let edge = slice_remove( + slice::from_raw_parts_mut( + MaybeUninit::first_ptr_mut(&mut internal.as_internal_mut().edges), + old_len + 1, + ), + 0, + ); + + let mut new_root = Root { + node: edge, + height: internal.height - 1, + }; + (*new_root.as_mut().as_leaf_mut()).parent = ptr::null(); + + for i in 0..old_len { + Handle::new_edge(internal.reborrow_mut(), i).correct_parent_link(); + } + + Some(new_root) + } + }; + + (*self.as_leaf_mut()).len -= 1; + + (key, val, edge) + } + } + + fn into_kv_pointers_mut(mut self) -> (*mut K, *mut V) { + (self.keys_mut().as_mut_ptr(), self.vals_mut().as_mut_ptr()) + } +} + +impl NodeRef { + /// Checks whether a node is an `Internal` node or a `Leaf` node. + pub fn force( + self, + ) -> ForceResult< + NodeRef, + NodeRef, + > { + if self.height == 0 { + ForceResult::Leaf(NodeRef { + height: self.height, + node: self.node, + root: self.root, + _marker: PhantomData, + }) + } else { + ForceResult::Internal(NodeRef { + height: self.height, + node: self.node, + root: self.root, + _marker: PhantomData, + }) + } + } +} + +/// A reference to a specific key/value pair or edge within a node. The `Node` parameter +/// must be a `NodeRef`, while the `Type` can either be `KV` (signifying a handle on a key/value +/// pair) or `Edge` (signifying a handle on an edge). +/// +/// Note that even `Leaf` nodes can have `Edge` handles. Instead of representing a pointer to +/// a child node, these represent the spaces where child pointers would go between the key/value +/// pairs. For example, in a node with length 2, there would be 3 possible edge locations - one +/// to the left of the node, one between the two pairs, and one at the right of the node. +pub struct Handle { + node: Node, + idx: usize, + _marker: PhantomData, +} + +impl Copy for Handle {} +// We don't need the full generality of `#[derive(Clone)]`, as the only time `Node` will be +// `Clone`able is when it is an immutable reference and therefore `Copy`. +impl Clone for Handle { + fn clone(&self) -> Self { + *self + } +} + +impl Handle { + /// Retrieves the node that contains the edge of key/value pair this handle points to. + pub fn into_node(self) -> Node { + self.node + } +} + +impl Handle, marker::KV> { + /// Creates a new handle to a key/value pair in `node`. `idx` must be less than `node.len()`. + pub fn new_kv(node: NodeRef, idx: usize) -> Self { + // Necessary for correctness, but in a private module + debug_assert!(idx < node.len()); + + Handle { + node, + idx, + _marker: PhantomData, + } + } + + pub fn left_edge(self) -> Handle, marker::Edge> { + Handle::new_edge(self.node, self.idx) + } + + pub fn right_edge(self) -> Handle, marker::Edge> { + Handle::new_edge(self.node, self.idx + 1) + } +} + +impl PartialEq + for Handle, HandleType> +{ + fn eq(&self, other: &Self) -> bool { + self.node.node == other.node.node && self.idx == other.idx + } +} + +impl + Handle, HandleType> +{ + /// Temporarily takes out another, immutable handle on the same location. + pub fn reborrow(&self) -> Handle, K, V, NodeType>, HandleType> { + // We can't use Handle::new_kv or Handle::new_edge because we don't know our type + Handle { + node: self.node.reborrow(), + idx: self.idx, + _marker: PhantomData, + } + } +} + +impl<'a, K, V, NodeType, HandleType> Handle, K, V, NodeType>, HandleType> { + /// Temporarily takes out another, mutable handle on the same location. Beware, as + /// this method is very dangerous, doubly so since it may not immediately appear + /// dangerous. + /// + /// Because mutable pointers can roam anywhere around the tree and can even (through + /// `into_root_mut`) mess with the root of the tree, the result of `reborrow_mut` + /// can easily be used to make the original mutable pointer dangling, or, in the case + /// of a reborrowed handle, out of bounds. + // FIXME(@gereeter) consider adding yet another type parameter to `NodeRef` that restricts + // the use of `ascend` and `into_root_mut` on reborrowed pointers, preventing this unsafety. + pub unsafe fn reborrow_mut( + &mut self, + ) -> Handle, K, V, NodeType>, HandleType> { + // We can't use Handle::new_kv or Handle::new_edge because we don't know our type + Handle { + node: self.node.reborrow_mut(), + idx: self.idx, + _marker: PhantomData, + } + } +} + +impl Handle, marker::Edge> { + /// Creates a new handle to an edge in `node`. `idx` must be less than or equal to + /// `node.len()`. + pub fn new_edge(node: NodeRef, idx: usize) -> Self { + // Necessary for correctness, but in a private module + debug_assert!(idx <= node.len()); + + Handle { + node, + idx, + _marker: PhantomData, + } + } + + pub fn left_kv(self) -> Result, marker::KV>, Self> { + if self.idx > 0 { + Ok(Handle::new_kv(self.node, self.idx - 1)) + } else { + Err(self) + } + } + + pub fn right_kv(self) -> Result, marker::KV>, Self> { + if self.idx < self.node.len() { + Ok(Handle::new_kv(self.node, self.idx)) + } else { + Err(self) + } + } +} + +impl<'a, K, V> Handle, K, V, marker::Leaf>, marker::Edge> { + /// Inserts a new key/value pair between the key/value pairs to the right and left of + /// this edge. This method assumes that there is enough space in the node for the new + /// pair to fit. + /// + /// The returned pointer points to the inserted value. + fn insert_fit(&mut self, key: K, val: V) -> *mut V { + // Necessary for correctness, but in a private module + debug_assert!(self.node.len() < CAPACITY); + debug_assert!(!self.node.is_shared_root()); + + unsafe { + slice_insert(self.node.keys_mut(), self.idx, key); + slice_insert(self.node.vals_mut(), self.idx, val); + + (*self.node.as_leaf_mut()).len += 1; + + self.node.vals_mut().get_unchecked_mut(self.idx) + } + } + + /// Inserts a new key/value pair between the key/value pairs to the right and left of + /// this edge. This method splits the node if there isn't enough room. + /// + /// The returned pointer points to the inserted value. + pub fn insert( + mut self, + key: K, + val: V, + ) -> Result<(InsertResult<'a, K, V, marker::Leaf>, *mut V), TryReserveError> { + if self.node.len() < CAPACITY { + let ptr = self.insert_fit(key, val); + Ok((InsertResult::Fit(Handle::new_kv(self.node, self.idx)), ptr)) + } else { + let middle = Handle::new_kv(self.node, B); + let (mut left, k, v, mut right) = middle.split()?; + let ptr = if self.idx <= B { + unsafe { Handle::new_edge(left.reborrow_mut(), self.idx).insert_fit(key, val) } + } else { + unsafe { + Handle::new_edge( + right.as_mut().cast_unchecked::(), + self.idx - (B + 1), + ) + .insert_fit(key, val) + } + }; + Ok((InsertResult::Split(left, k, v, right), ptr)) + } + } +} + +impl<'a, K, V> Handle, K, V, marker::Internal>, marker::Edge> { + /// Fixes the parent pointer and index in the child node below this edge. This is useful + /// when the ordering of edges has been changed, such as in the various `insert` methods. + fn correct_parent_link(mut self) { + let idx = self.idx as u16; + let ptr = self.node.as_internal_mut() as *mut _; + let mut child = self.descend(); + unsafe { + (*child.as_leaf_mut()).parent = ptr; + (*child.as_leaf_mut()).parent_idx.write(idx); + } + } + + /// Unsafely asserts to the compiler some static information about whether the underlying + /// node of this handle is a `Leaf`. + unsafe fn cast_unchecked( + &mut self, + ) -> Handle, K, V, NewType>, marker::Edge> { + Handle::new_edge(self.node.cast_unchecked(), self.idx) + } + + /// Inserts a new key/value pair and an edge that will go to the right of that new pair + /// between this edge and the key/value pair to the right of this edge. This method assumes + /// that there is enough space in the node for the new pair to fit. + fn insert_fit(&mut self, key: K, val: V, edge: Root) { + // Necessary for correctness, but in an internal module + debug_assert!(self.node.len() < CAPACITY); + debug_assert!(edge.height == self.node.height - 1); + + unsafe { + // This cast is a lie, but it allows us to reuse the key/value insertion logic. + self.cast_unchecked::().insert_fit(key, val); + + slice_insert( + slice::from_raw_parts_mut( + MaybeUninit::first_ptr_mut(&mut self.node.as_internal_mut().edges), + self.node.len(), + ), + self.idx + 1, + edge.node, + ); + + for i in (self.idx + 1)..(self.node.len() + 1) { + Handle::new_edge(self.node.reborrow_mut(), i).correct_parent_link(); + } + } + } + + /// Inserts a new key/value pair and an edge that will go to the right of that new pair + /// between this edge and the key/value pair to the right of this edge. This method splits + /// the node if there isn't enough room. + pub fn insert( + mut self, + key: K, + val: V, + edge: Root, + ) -> Result, TryReserveError> { + // Necessary for correctness, but this is an internal module + debug_assert!(edge.height == self.node.height - 1); + + if self.node.len() < CAPACITY { + self.insert_fit(key, val, edge); + Ok(InsertResult::Fit(Handle::new_kv(self.node, self.idx))) + } else { + let middle = Handle::new_kv(self.node, B); + let (mut left, k, v, mut right) = middle.split()?; + if self.idx <= B { + unsafe { + Handle::new_edge(left.reborrow_mut(), self.idx).insert_fit(key, val, edge); + } + } else { + unsafe { + Handle::new_edge( + right.as_mut().cast_unchecked::(), + self.idx - (B + 1), + ) + .insert_fit(key, val, edge); + } + } + Ok(InsertResult::Split(left, k, v, right)) + } + } +} + +impl Handle, marker::Edge> { + /// Finds the node pointed to by this edge. + /// + /// `edge.descend().ascend().unwrap()` and `node.ascend().unwrap().descend()` should + /// both, upon success, do nothing. + pub fn descend(self) -> NodeRef { + NodeRef { + height: self.node.height - 1, + node: unsafe { + (&*self + .node + .as_internal() + .edges + .get_unchecked(self.idx) + .as_ptr()) + .as_ptr() + }, + root: self.node.root, + _marker: PhantomData, + } + } +} + +impl<'a, K: 'a, V: 'a, NodeType> Handle, K, V, NodeType>, marker::KV> { + pub fn into_kv(self) -> (&'a K, &'a V) { + let (keys, vals) = self.node.into_slices(); + unsafe { (keys.get_unchecked(self.idx), vals.get_unchecked(self.idx)) } + } +} + +impl<'a, K: 'a, V: 'a, NodeType> Handle, K, V, NodeType>, marker::KV> { + pub fn into_kv_mut(self) -> (&'a mut K, &'a mut V) { + let (keys, vals) = self.node.into_slices_mut(); + unsafe { + ( + keys.get_unchecked_mut(self.idx), + vals.get_unchecked_mut(self.idx), + ) + } + } +} + +impl<'a, K, V, NodeType> Handle, K, V, NodeType>, marker::KV> { + pub fn kv_mut(&mut self) -> (&mut K, &mut V) { + unsafe { + let (keys, vals) = self.node.reborrow_mut().into_slices_mut(); + ( + keys.get_unchecked_mut(self.idx), + vals.get_unchecked_mut(self.idx), + ) + } + } +} + +impl<'a, K, V> Handle, K, V, marker::Leaf>, marker::KV> { + /// Splits the underlying node into three parts: + /// + /// - The node is truncated to only contain the key/value pairs to the right of + /// this handle. + /// - The key and value pointed to by this handle and extracted. + /// - All the key/value pairs to the right of this handle are put into a newly + /// allocated node. + pub fn split( + mut self, + ) -> Result< + ( + NodeRef, K, V, marker::Leaf>, + K, + V, + Root, + ), + TryReserveError, + > { + debug_assert!(!self.node.is_shared_root()); + unsafe { + let mut new_node = as FallibleBox<_>>::try_new(LeafNode::new())?; + + let k = ptr::read(self.node.keys().get_unchecked(self.idx)); + let v = ptr::read(self.node.vals().get_unchecked(self.idx)); + + let new_len = self.node.len() - self.idx - 1; + + ptr::copy_nonoverlapping( + self.node.keys().as_ptr().add(self.idx + 1), + new_node.keys.as_mut_ptr() as *mut K, + new_len, + ); + ptr::copy_nonoverlapping( + self.node.vals().as_ptr().add(self.idx + 1), + new_node.vals.as_mut_ptr() as *mut V, + new_len, + ); + + (*self.node.as_leaf_mut()).len = self.idx as u16; + new_node.len = new_len as u16; + + Ok(( + self.node, + k, + v, + Root { + node: BoxedNode::from_leaf(new_node), + height: 0, + }, + )) + } + } + + /// Removes the key/value pair pointed to by this handle, returning the edge between the + /// now adjacent key/value pairs to the left and right of this handle. + pub fn remove( + mut self, + ) -> ( + Handle, K, V, marker::Leaf>, marker::Edge>, + K, + V, + ) { + debug_assert!(!self.node.is_shared_root()); + unsafe { + let k = slice_remove(self.node.keys_mut(), self.idx); + let v = slice_remove(self.node.vals_mut(), self.idx); + (*self.node.as_leaf_mut()).len -= 1; + (self.left_edge(), k, v) + } + } +} + +impl<'a, K, V> Handle, K, V, marker::Internal>, marker::KV> { + /// Splits the underlying node into three parts: + /// + /// - The node is truncated to only contain the edges and key/value pairs to the + /// right of this handle. + /// - The key and value pointed to by this handle and extracted. + /// - All the edges and key/value pairs to the right of this handle are put into + /// a newly allocated node. + pub fn split( + mut self, + ) -> Result< + ( + NodeRef, K, V, marker::Internal>, + K, + V, + Root, + ), + TryReserveError, + > { + unsafe { + let mut new_node = as FallibleBox<_>>::try_new(InternalNode::new())?; + + let k = ptr::read(self.node.keys().get_unchecked(self.idx)); + let v = ptr::read(self.node.vals().get_unchecked(self.idx)); + + let height = self.node.height; + let new_len = self.node.len() - self.idx - 1; + + ptr::copy_nonoverlapping( + self.node.keys().as_ptr().add(self.idx + 1), + new_node.data.keys.as_mut_ptr() as *mut K, + new_len, + ); + ptr::copy_nonoverlapping( + self.node.vals().as_ptr().add(self.idx + 1), + new_node.data.vals.as_mut_ptr() as *mut V, + new_len, + ); + ptr::copy_nonoverlapping( + self.node.as_internal().edges.as_ptr().add(self.idx + 1), + new_node.edges.as_mut_ptr(), + new_len + 1, + ); + + (*self.node.as_leaf_mut()).len = self.idx as u16; + new_node.data.len = new_len as u16; + + let mut new_root = Root { + node: BoxedNode::from_internal(new_node), + height, + }; + + for i in 0..(new_len + 1) { + Handle::new_edge(new_root.as_mut().cast_unchecked(), i).correct_parent_link(); + } + + Ok((self.node, k, v, new_root)) + } + } + + /// Returns `true` if it is valid to call `.merge()`, i.e., whether there is enough room in + /// a node to hold the combination of the nodes to the left and right of this handle along + /// with the key/value pair at this handle. + pub fn can_merge(&self) -> bool { + (self.reborrow().left_edge().descend().len() + + self.reborrow().right_edge().descend().len() + + 1) + <= CAPACITY + } + + /// Combines the node immediately to the left of this handle, the key/value pair pointed + /// to by this handle, and the node immediately to the right of this handle into one new + /// child of the underlying node, returning an edge referencing that new child. + /// + /// Assumes that this edge `.can_merge()`. + pub fn merge( + mut self, + ) -> Handle, K, V, marker::Internal>, marker::Edge> { + let self1 = unsafe { ptr::read(&self) }; + let self2 = unsafe { ptr::read(&self) }; + let mut left_node = self1.left_edge().descend(); + let left_len = left_node.len(); + let mut right_node = self2.right_edge().descend(); + let right_len = right_node.len(); + + // necessary for correctness, but in a private module + debug_assert!(left_len + right_len + 1 <= CAPACITY); + + unsafe { + ptr::write( + left_node.keys_mut().get_unchecked_mut(left_len), + slice_remove(self.node.keys_mut(), self.idx), + ); + ptr::copy_nonoverlapping( + right_node.keys().as_ptr(), + left_node.keys_mut().as_mut_ptr().add(left_len + 1), + right_len, + ); + ptr::write( + left_node.vals_mut().get_unchecked_mut(left_len), + slice_remove(self.node.vals_mut(), self.idx), + ); + ptr::copy_nonoverlapping( + right_node.vals().as_ptr(), + left_node.vals_mut().as_mut_ptr().add(left_len + 1), + right_len, + ); + + slice_remove(&mut self.node.as_internal_mut().edges, self.idx + 1); + for i in self.idx + 1..self.node.len() { + Handle::new_edge(self.node.reborrow_mut(), i).correct_parent_link(); + } + (*self.node.as_leaf_mut()).len -= 1; + + (*left_node.as_leaf_mut()).len += right_len as u16 + 1; + + if self.node.height > 1 { + ptr::copy_nonoverlapping( + right_node.cast_unchecked().as_internal().edges.as_ptr(), + left_node + .cast_unchecked() + .as_internal_mut() + .edges + .as_mut_ptr() + .add(left_len + 1), + right_len + 1, + ); + + for i in left_len + 1..left_len + right_len + 2 { + Handle::new_edge(left_node.cast_unchecked().reborrow_mut(), i) + .correct_parent_link(); + } + + Global.dealloc(right_node.node.cast(), Layout::new::>()); + } else { + Global.dealloc(right_node.node.cast(), Layout::new::>()); + } + + Handle::new_edge(self.node, self.idx) + } + } + + /// This removes a key/value pair from the left child and replaces it with the key/value pair + /// pointed to by this handle while pushing the old key/value pair of this handle into the right + /// child. + pub fn steal_left(&mut self) { + unsafe { + let (k, v, edge) = self.reborrow_mut().left_edge().descend().pop(); + + let k = mem::replace(self.reborrow_mut().into_kv_mut().0, k); + let v = mem::replace(self.reborrow_mut().into_kv_mut().1, v); + + match self.reborrow_mut().right_edge().descend().force() { + ForceResult::Leaf(mut leaf) => leaf.push_front(k, v), + ForceResult::Internal(mut internal) => internal.push_front(k, v, edge.unwrap()), + } + } + } + + /// This removes a key/value pair from the right child and replaces it with the key/value pair + /// pointed to by this handle while pushing the old key/value pair of this handle into the left + /// child. + pub fn steal_right(&mut self) { + unsafe { + let (k, v, edge) = self.reborrow_mut().right_edge().descend().pop_front(); + + let k = mem::replace(self.reborrow_mut().into_kv_mut().0, k); + let v = mem::replace(self.reborrow_mut().into_kv_mut().1, v); + + match self.reborrow_mut().left_edge().descend().force() { + ForceResult::Leaf(mut leaf) => leaf.push(k, v), + ForceResult::Internal(mut internal) => internal.push(k, v, edge.unwrap()), + } + } + } + + /// This does stealing similar to `steal_left` but steals multiple elements at once. + pub fn bulk_steal_left(&mut self, count: usize) { + unsafe { + let mut left_node = ptr::read(self).left_edge().descend(); + let left_len = left_node.len(); + let mut right_node = ptr::read(self).right_edge().descend(); + let right_len = right_node.len(); + + // Make sure that we may steal safely. + debug_assert!(right_len + count <= CAPACITY); + debug_assert!(left_len >= count); + + let new_left_len = left_len - count; + + // Move data. + { + let left_kv = left_node.reborrow_mut().into_kv_pointers_mut(); + let right_kv = right_node.reborrow_mut().into_kv_pointers_mut(); + let parent_kv = { + let kv = self.reborrow_mut().into_kv_mut(); + (kv.0 as *mut K, kv.1 as *mut V) + }; + + // Make room for stolen elements in the right child. + ptr::copy(right_kv.0, right_kv.0.add(count), right_len); + ptr::copy(right_kv.1, right_kv.1.add(count), right_len); + + // Move elements from the left child to the right one. + move_kv(left_kv, new_left_len + 1, right_kv, 0, count - 1); + + // Move parent's key/value pair to the right child. + move_kv(parent_kv, 0, right_kv, count - 1, 1); + + // Move the left-most stolen pair to the parent. + move_kv(left_kv, new_left_len, parent_kv, 0, 1); + } + + (*left_node.reborrow_mut().as_leaf_mut()).len -= count as u16; + (*right_node.reborrow_mut().as_leaf_mut()).len += count as u16; + + match (left_node.force(), right_node.force()) { + (ForceResult::Internal(left), ForceResult::Internal(mut right)) => { + // Make room for stolen edges. + let right_edges = right.reborrow_mut().as_internal_mut().edges.as_mut_ptr(); + ptr::copy(right_edges, right_edges.add(count), right_len + 1); + right.correct_childrens_parent_links(count, count + right_len + 1); + + move_edges(left, new_left_len + 1, right, 0, count); + } + (ForceResult::Leaf(_), ForceResult::Leaf(_)) => {} + _ => { + unreachable!(); + } + } + } + } + + /// The symmetric clone of `bulk_steal_left`. + pub fn bulk_steal_right(&mut self, count: usize) { + unsafe { + let mut left_node = ptr::read(self).left_edge().descend(); + let left_len = left_node.len(); + let mut right_node = ptr::read(self).right_edge().descend(); + let right_len = right_node.len(); + + // Make sure that we may steal safely. + debug_assert!(left_len + count <= CAPACITY); + debug_assert!(right_len >= count); + + let new_right_len = right_len - count; + + // Move data. + { + let left_kv = left_node.reborrow_mut().into_kv_pointers_mut(); + let right_kv = right_node.reborrow_mut().into_kv_pointers_mut(); + let parent_kv = { + let kv = self.reborrow_mut().into_kv_mut(); + (kv.0 as *mut K, kv.1 as *mut V) + }; + + // Move parent's key/value pair to the left child. + move_kv(parent_kv, 0, left_kv, left_len, 1); + + // Move elements from the right child to the left one. + move_kv(right_kv, 0, left_kv, left_len + 1, count - 1); + + // Move the right-most stolen pair to the parent. + move_kv(right_kv, count - 1, parent_kv, 0, 1); + + // Fix right indexing + ptr::copy(right_kv.0.add(count), right_kv.0, new_right_len); + ptr::copy(right_kv.1.add(count), right_kv.1, new_right_len); + } + + (*left_node.reborrow_mut().as_leaf_mut()).len += count as u16; + (*right_node.reborrow_mut().as_leaf_mut()).len -= count as u16; + + match (left_node.force(), right_node.force()) { + (ForceResult::Internal(left), ForceResult::Internal(mut right)) => { + move_edges(right.reborrow_mut(), 0, left, left_len + 1, count); + + // Fix right indexing. + let right_edges = right.reborrow_mut().as_internal_mut().edges.as_mut_ptr(); + ptr::copy(right_edges.add(count), right_edges, new_right_len + 1); + right.correct_childrens_parent_links(0, new_right_len + 1); + } + (ForceResult::Leaf(_), ForceResult::Leaf(_)) => {} + _ => { + unreachable!(); + } + } + } + } +} + +unsafe fn move_kv( + source: (*mut K, *mut V), + source_offset: usize, + dest: (*mut K, *mut V), + dest_offset: usize, + count: usize, +) { + ptr::copy_nonoverlapping(source.0.add(source_offset), dest.0.add(dest_offset), count); + ptr::copy_nonoverlapping(source.1.add(source_offset), dest.1.add(dest_offset), count); +} + +// Source and destination must have the same height. +unsafe fn move_edges( + mut source: NodeRef, K, V, marker::Internal>, + source_offset: usize, + mut dest: NodeRef, K, V, marker::Internal>, + dest_offset: usize, + count: usize, +) { + let source_ptr = source.as_internal_mut().edges.as_mut_ptr(); + let dest_ptr = dest.as_internal_mut().edges.as_mut_ptr(); + ptr::copy_nonoverlapping( + source_ptr.add(source_offset), + dest_ptr.add(dest_offset), + count, + ); + dest.correct_childrens_parent_links(dest_offset, dest_offset + count); +} + +impl + Handle, HandleType> +{ + /// Checks whether the underlying node is an `Internal` node or a `Leaf` node. + pub fn force( + self, + ) -> ForceResult< + Handle, HandleType>, + Handle, HandleType>, + > { + match self.node.force() { + ForceResult::Leaf(node) => ForceResult::Leaf(Handle { + node, + idx: self.idx, + _marker: PhantomData, + }), + ForceResult::Internal(node) => ForceResult::Internal(Handle { + node, + idx: self.idx, + _marker: PhantomData, + }), + } + } +} + +impl<'a, K, V> Handle, K, V, marker::LeafOrInternal>, marker::Edge> { + /// Move the suffix after `self` from one node to another one. `right` must be empty. + /// The first edge of `right` remains unchanged. + pub fn move_suffix( + &mut self, + right: &mut NodeRef, K, V, marker::LeafOrInternal>, + ) { + unsafe { + let left_new_len = self.idx; + let mut left_node = self.reborrow_mut().into_node(); + + let right_new_len = left_node.len() - left_new_len; + let mut right_node = right.reborrow_mut(); + + debug_assert!(right_node.len() == 0); + debug_assert!(left_node.height == right_node.height); + + let left_kv = left_node.reborrow_mut().into_kv_pointers_mut(); + let right_kv = right_node.reborrow_mut().into_kv_pointers_mut(); + + move_kv(left_kv, left_new_len, right_kv, 0, right_new_len); + + (*left_node.reborrow_mut().as_leaf_mut()).len = left_new_len as u16; + (*right_node.reborrow_mut().as_leaf_mut()).len = right_new_len as u16; + + match (left_node.force(), right_node.force()) { + (ForceResult::Internal(left), ForceResult::Internal(right)) => { + move_edges(left, left_new_len + 1, right, 1, right_new_len); + } + (ForceResult::Leaf(_), ForceResult::Leaf(_)) => {} + _ => { + unreachable!(); + } + } + } + } +} + +pub enum ForceResult { + Leaf(Leaf), + Internal(Internal), +} + +pub enum InsertResult<'a, K, V, Type> { + Fit(Handle, K, V, Type>, marker::KV>), + Split(NodeRef, K, V, Type>, K, V, Root), +} + +pub mod marker { + use core::marker::PhantomData; + + pub enum Leaf {} + pub enum Internal {} + pub enum LeafOrInternal {} + + pub enum Owned {} + pub struct Immut<'a>(PhantomData<&'a ()>); + pub struct Mut<'a>(PhantomData<&'a mut ()>); + + pub enum KV {} + pub enum Edge {} +} + +unsafe fn slice_insert(slice: &mut [T], idx: usize, val: T) { + ptr::copy( + slice.as_ptr().add(idx), + slice.as_mut_ptr().add(idx + 1), + slice.len() - idx, + ); + ptr::write(slice.get_unchecked_mut(idx), val); +} + +unsafe fn slice_remove(slice: &mut [T], idx: usize) -> T { + let ret = ptr::read(slice.get_unchecked(idx)); + ptr::copy( + slice.as_ptr().add(idx + 1), + slice.as_mut_ptr().add(idx), + slice.len() - idx - 1, + ); + ret +} diff --git a/third_party/rust/fallible_collections/src/btree/search.rs b/third_party/rust/fallible_collections/src/btree/search.rs new file mode 100644 index 0000000000..0031fdc29c --- /dev/null +++ b/third_party/rust/fallible_collections/src/btree/search.rs @@ -0,0 +1,66 @@ +use core::borrow::Borrow; + +use core::cmp::Ordering; + +use super::node::{marker, ForceResult::*, Handle, NodeRef}; + +use SearchResult::*; + +pub enum SearchResult { + Found(Handle, marker::KV>), + GoDown(Handle, marker::Edge>), +} + +pub fn search_tree( + mut node: NodeRef, + key: &Q, +) -> SearchResult +where + Q: Ord, + K: Borrow, +{ + loop { + match search_node(node, key) { + Found(handle) => return Found(handle), + GoDown(handle) => match handle.force() { + Leaf(leaf) => return GoDown(leaf), + Internal(internal) => { + node = internal.descend(); + continue; + } + }, + } + } +} + +pub fn search_node( + node: NodeRef, + key: &Q, +) -> SearchResult +where + Q: Ord, + K: Borrow, +{ + match search_linear(&node, key) { + (idx, true) => Found(Handle::new_kv(node, idx)), + (idx, false) => SearchResult::GoDown(Handle::new_edge(node, idx)), + } +} + +pub fn search_linear( + node: &NodeRef, + key: &Q, +) -> (usize, bool) +where + Q: Ord, + K: Borrow, +{ + for (i, k) in node.keys().iter().enumerate() { + match key.cmp(k.borrow()) { + Ordering::Greater => {} + Ordering::Equal => return (i, true), + Ordering::Less => return (i, false), + } + } + (node.keys().len(), false) +} diff --git a/third_party/rust/fallible_collections/src/btree/set.rs b/third_party/rust/fallible_collections/src/btree/set.rs new file mode 100644 index 0000000000..c6112ee6cd --- /dev/null +++ b/third_party/rust/fallible_collections/src/btree/set.rs @@ -0,0 +1,1346 @@ +// This is pretty much entirely stolen from TreeSet, since BTreeMap has an identical interface +// to TreeMap + +use crate::TryReserveError; +use core::borrow::Borrow; +use core::cmp::max; +use core::cmp::Ordering::{self, Equal, Greater, Less}; +use core::fmt::{self, Debug}; +use core::iter::{FromIterator, FusedIterator, Peekable}; +use core::ops::{BitAnd, BitOr, BitXor, RangeBounds, Sub}; + +use super::map::{self, BTreeMap, Keys}; +use super::Recover; + +// FIXME(conventions): implement bounded iterators + +/// A set based on a B-Tree. +/// +/// See [`BTreeMap`]'s documentation for a detailed discussion of this collection's performance +/// benefits and drawbacks. +/// +/// It is a logic error for an item to be modified in such a way that the item's ordering relative +/// to any other item, as determined by the [`Ord`] trait, changes while it is in the set. This is +/// normally only possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. +/// +/// [`BTreeMap`]: struct.BTreeMap.html +/// [`Ord`]: ../../std/cmp/trait.Ord.html +/// [`Cell`]: ../../std/cell/struct.Cell.html +/// [`RefCell`]: ../../std/cell/struct.RefCell.html +/// +/// # Examples +/// +/// ``` +/// use std::collections::BTreeSet; +/// +/// // Type inference lets us omit an explicit type signature (which +/// // would be `BTreeSet<&str>` in this example). +/// let mut books = BTreeSet::new(); +/// +/// // Add some books. +/// books.insert("A Dance With Dragons"); +/// books.insert("To Kill a Mockingbird"); +/// books.insert("The Odyssey"); +/// books.insert("The Great Gatsby"); +/// +/// // Check for a specific one. +/// if !books.contains("The Winds of Winter") { +/// println!("We have {} books, but The Winds of Winter ain't one.", +/// books.len()); +/// } +/// +/// // Remove a book. +/// books.remove("The Odyssey"); +/// +/// // Iterate over everything. +/// for book in &books { +/// println!("{}", book); +/// } +/// ``` +#[derive(Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] + +pub struct BTreeSet { + map: BTreeMap, +} + +/// An iterator over the items of a `BTreeSet`. +/// +/// This `struct` is created by the [`iter`] method on [`BTreeSet`]. +/// See its documentation for more. +/// +/// [`BTreeSet`]: struct.BTreeSet.html +/// [`iter`]: struct.BTreeSet.html#method.iter + +pub struct Iter<'a, T: 'a> { + iter: Keys<'a, T, ()>, +} + +impl fmt::Debug for Iter<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Iter").field(&self.iter.clone()).finish() + } +} + +/// An owning iterator over the items of a `BTreeSet`. +/// +/// This `struct` is created by the [`into_iter`] method on [`BTreeSet`][`BTreeSet`] +/// (provided by the `IntoIterator` trait). See its documentation for more. +/// +/// [`BTreeSet`]: struct.BTreeSet.html +/// [`into_iter`]: struct.BTreeSet.html#method.into_iter + +#[derive(Debug)] +pub struct IntoIter { + iter: map::IntoIter, +} + +/// An iterator over a sub-range of items in a `BTreeSet`. +/// +/// This `struct` is created by the [`range`] method on [`BTreeSet`]. +/// See its documentation for more. +/// +/// [`BTreeSet`]: struct.BTreeSet.html +/// [`range`]: struct.BTreeSet.html#method.range +#[derive(Debug)] + +pub struct Range<'a, T: 'a> { + iter: map::Range<'a, T, ()>, +} + +/// A lazy iterator producing elements in the difference of `BTreeSet`s. +/// +/// This `struct` is created by the [`difference`] method on [`BTreeSet`]. +/// See its documentation for more. +/// +/// [`BTreeSet`]: struct.BTreeSet.html +/// [`difference`]: struct.BTreeSet.html#method.difference + +pub struct Difference<'a, T: 'a> { + inner: DifferenceInner<'a, T>, +} +enum DifferenceInner<'a, T: 'a> { + Stitch { + self_iter: Iter<'a, T>, + other_iter: Peekable>, + }, + Search { + self_iter: Iter<'a, T>, + other_set: &'a BTreeSet, + }, +} + +impl fmt::Debug for Difference<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.inner { + DifferenceInner::Stitch { + self_iter, + other_iter, + } => f + .debug_tuple("Difference") + .field(&self_iter) + .field(&other_iter) + .finish(), + DifferenceInner::Search { + self_iter, + other_set: _, + } => f.debug_tuple("Difference").field(&self_iter).finish(), + } + } +} + +/// A lazy iterator producing elements in the symmetric difference of `BTreeSet`s. +/// +/// This `struct` is created by the [`symmetric_difference`] method on +/// [`BTreeSet`]. See its documentation for more. +/// +/// [`BTreeSet`]: struct.BTreeSet.html +/// [`symmetric_difference`]: struct.BTreeSet.html#method.symmetric_difference + +pub struct SymmetricDifference<'a, T: 'a> { + a: Peekable>, + b: Peekable>, +} + +impl fmt::Debug for SymmetricDifference<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("SymmetricDifference") + .field(&self.a) + .field(&self.b) + .finish() + } +} + +/// A lazy iterator producing elements in the intersection of `BTreeSet`s. +/// +/// This `struct` is created by the [`intersection`] method on [`BTreeSet`]. +/// See its documentation for more. +/// +/// [`BTreeSet`]: struct.BTreeSet.html +/// [`intersection`]: struct.BTreeSet.html#method.intersection + +pub struct Intersection<'a, T: 'a> { + inner: IntersectionInner<'a, T>, +} +enum IntersectionInner<'a, T: 'a> { + Stitch { + small_iter: Iter<'a, T>, // for size_hint, should be the smaller of the sets + other_iter: Iter<'a, T>, + }, + Search { + small_iter: Iter<'a, T>, + large_set: &'a BTreeSet, + }, +} + +impl fmt::Debug for Intersection<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.inner { + IntersectionInner::Stitch { + small_iter, + other_iter, + } => f + .debug_tuple("Intersection") + .field(&small_iter) + .field(&other_iter) + .finish(), + IntersectionInner::Search { + small_iter, + large_set: _, + } => f.debug_tuple("Intersection").field(&small_iter).finish(), + } + } +} + +/// A lazy iterator producing elements in the union of `BTreeSet`s. +/// +/// This `struct` is created by the [`union`] method on [`BTreeSet`]. +/// See its documentation for more. +/// +/// [`BTreeSet`]: struct.BTreeSet.html +/// [`union`]: struct.BTreeSet.html#method.union + +pub struct Union<'a, T: 'a> { + a: Peekable>, + b: Peekable>, +} + +impl fmt::Debug for Union<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Union") + .field(&self.a) + .field(&self.b) + .finish() + } +} + +// This constant is used by functions that compare two sets. +// It estimates the relative size at which searching performs better +// than iterating, based on the benchmarks in +// https://github.com/ssomers/rust_bench_btreeset_intersection; +// It's used to divide rather than multiply sizes, to rule out overflow, +// and it's a power of two to make that division cheap. +const ITER_PERFORMANCE_TIPPING_SIZE_DIFF: usize = 16; + +impl BTreeSet { + /// Makes a new `BTreeSet` with a reasonable choice of B. + /// + /// # Examples + /// + /// ``` + /// # #![allow(unused_mut)] + /// use std::collections::BTreeSet; + /// + /// let mut set: BTreeSet = BTreeSet::new(); + /// ``` + + #[inline] + pub fn new() -> BTreeSet { + BTreeSet { + map: BTreeMap::new(), + } + } + + /// Constructs a double-ended iterator over a sub-range of elements in the set. + /// The simplest way is to use the range syntax `min..max`, thus `range(min..max)` will + /// yield elements from min (inclusive) to max (exclusive). + /// The range may also be entered as `(Bound, Bound)`, so for example + /// `range((Excluded(4), Included(10)))` will yield a left-exclusive, right-inclusive + /// range from 4 to 10. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// use std::ops::Bound::Included; + /// + /// let mut set = BTreeSet::new(); + /// set.insert(3); + /// set.insert(5); + /// set.insert(8); + /// for &elem in set.range((Included(&4), Included(&8))) { + /// println!("{}", elem); + /// } + /// assert_eq!(Some(&5), set.range(4..).next()); + /// ``` + + #[inline] + pub fn range(&self, range: R) -> Range<'_, T> + where + K: Ord, + T: Borrow, + R: RangeBounds, + { + Range { + iter: self.map.range(range), + } + } + + /// Visits the values representing the difference, + /// i.e., the values that are in `self` but not in `other`, + /// in ascending order. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut a = BTreeSet::new(); + /// a.insert(1); + /// a.insert(2); + /// + /// let mut b = BTreeSet::new(); + /// b.insert(2); + /// b.insert(3); + /// + /// let diff: Vec<_> = a.difference(&b).cloned().collect(); + /// assert_eq!(diff, [1]); + /// ``` + + pub fn difference<'a>(&'a self, other: &'a BTreeSet) -> Difference<'a, T> { + if self.len() > other.len() / ITER_PERFORMANCE_TIPPING_SIZE_DIFF { + // Self is bigger than or not much smaller than other set. + // Iterate both sets jointly, spotting matches along the way. + Difference { + inner: DifferenceInner::Stitch { + self_iter: self.iter(), + other_iter: other.iter().peekable(), + }, + } + } else { + // Self is much smaller than other set, or both sets are empty. + // Iterate the small set, searching for matches in the large set. + Difference { + inner: DifferenceInner::Search { + self_iter: self.iter(), + other_set: other, + }, + } + } + } + + /// Visits the values representing the symmetric difference, + /// i.e., the values that are in `self` or in `other` but not in both, + /// in ascending order. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut a = BTreeSet::new(); + /// a.insert(1); + /// a.insert(2); + /// + /// let mut b = BTreeSet::new(); + /// b.insert(2); + /// b.insert(3); + /// + /// let sym_diff: Vec<_> = a.symmetric_difference(&b).cloned().collect(); + /// assert_eq!(sym_diff, [1, 3]); + /// ``` + + #[inline] + pub fn symmetric_difference<'a>( + &'a self, + other: &'a BTreeSet, + ) -> SymmetricDifference<'a, T> { + SymmetricDifference { + a: self.iter().peekable(), + b: other.iter().peekable(), + } + } + + /// Visits the values representing the intersection, + /// i.e., the values that are both in `self` and `other`, + /// in ascending order. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut a = BTreeSet::new(); + /// a.insert(1); + /// a.insert(2); + /// + /// let mut b = BTreeSet::new(); + /// b.insert(2); + /// b.insert(3); + /// + /// let intersection: Vec<_> = a.intersection(&b).cloned().collect(); + /// assert_eq!(intersection, [2]); + /// ``` + + pub fn intersection<'a>(&'a self, other: &'a BTreeSet) -> Intersection<'a, T> { + let (small, other) = if self.len() <= other.len() { + (self, other) + } else { + (other, self) + }; + if small.len() > other.len() / ITER_PERFORMANCE_TIPPING_SIZE_DIFF { + // Small set is not much smaller than other set. + // Iterate both sets jointly, spotting matches along the way. + Intersection { + inner: IntersectionInner::Stitch { + small_iter: small.iter(), + other_iter: other.iter(), + }, + } + } else { + // Big difference in number of elements, or both sets are empty. + // Iterate the small set, searching for matches in the large set. + Intersection { + inner: IntersectionInner::Search { + small_iter: small.iter(), + large_set: other, + }, + } + } + } + + /// Visits the values representing the union, + /// i.e., all the values in `self` or `other`, without duplicates, + /// in ascending order. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut a = BTreeSet::new(); + /// a.insert(1); + /// + /// let mut b = BTreeSet::new(); + /// b.insert(2); + /// + /// let union: Vec<_> = a.union(&b).cloned().collect(); + /// assert_eq!(union, [1, 2]); + /// ``` + + #[inline] + pub fn union<'a>(&'a self, other: &'a BTreeSet) -> Union<'a, T> { + Union { + a: self.iter().peekable(), + b: other.iter().peekable(), + } + } + + /// Clears the set, removing all values. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut v = BTreeSet::new(); + /// v.insert(1); + /// v.clear(); + /// assert!(v.is_empty()); + /// ``` + + #[inline(always)] + pub fn clear(&mut self) { + self.map.clear() + } + + /// Returns `true` if the set contains a value. + /// + /// The value may be any borrowed form of the set's value type, + /// but the ordering on the borrowed form *must* match the + /// ordering on the value type. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let set: BTreeSet<_> = [1, 2, 3].iter().cloned().collect(); + /// assert_eq!(set.contains(&1), true); + /// assert_eq!(set.contains(&4), false); + /// ``` + + #[inline(always)] + pub fn contains(&self, value: &Q) -> bool + where + T: Borrow, + Q: Ord, + { + self.map.contains_key(value) + } + + /// Returns a reference to the value in the set, if any, that is equal to the given value. + /// + /// The value may be any borrowed form of the set's value type, + /// but the ordering on the borrowed form *must* match the + /// ordering on the value type. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let set: BTreeSet<_> = [1, 2, 3].iter().cloned().collect(); + /// assert_eq!(set.get(&2), Some(&2)); + /// assert_eq!(set.get(&4), None); + /// ``` + + #[inline(always)] + pub fn get(&self, value: &Q) -> Option<&T> + where + T: Borrow, + Q: Ord, + { + Recover::get(&self.map, value) + } + + /// Returns `true` if `self` has no elements in common with `other`. + /// This is equivalent to checking for an empty intersection. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let a: BTreeSet<_> = [1, 2, 3].iter().cloned().collect(); + /// let mut b = BTreeSet::new(); + /// + /// assert_eq!(a.is_disjoint(&b), true); + /// b.insert(4); + /// assert_eq!(a.is_disjoint(&b), true); + /// b.insert(1); + /// assert_eq!(a.is_disjoint(&b), false); + /// ``` + + #[inline] + pub fn is_disjoint(&self, other: &BTreeSet) -> bool { + self.intersection(other).next().is_none() + } + + /// Returns `true` if the set is a subset of another, + /// i.e., `other` contains at least all the values in `self`. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let sup: BTreeSet<_> = [1, 2, 3].iter().cloned().collect(); + /// let mut set = BTreeSet::new(); + /// + /// assert_eq!(set.is_subset(&sup), true); + /// set.insert(2); + /// assert_eq!(set.is_subset(&sup), true); + /// set.insert(4); + /// assert_eq!(set.is_subset(&sup), false); + /// ``` + + pub fn is_subset(&self, other: &BTreeSet) -> bool { + // Same result as self.difference(other).next().is_none() + // but the 3 paths below are faster (in order: hugely, 20%, 5%). + if self.len() > other.len() { + false + } else if self.len() > other.len() / ITER_PERFORMANCE_TIPPING_SIZE_DIFF { + // Self is not much smaller than other set. + // Stolen from TreeMap + let mut x = self.iter(); + let mut y = other.iter(); + let mut a = x.next(); + let mut b = y.next(); + while a.is_some() { + if b.is_none() { + return false; + } + + let a1 = a.unwrap(); + let b1 = b.unwrap(); + + match b1.cmp(a1) { + Less => (), + Greater => return false, + Equal => a = x.next(), + } + + b = y.next(); + } + true + } else { + // Big difference in number of elements, or both sets are empty. + // Iterate the small set, searching for matches in the large set. + for next in self { + if !other.contains(next) { + return false; + } + } + true + } + } + + /// Returns `true` if the set is a superset of another, + /// i.e., `self` contains at least all the values in `other`. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let sub: BTreeSet<_> = [1, 2].iter().cloned().collect(); + /// let mut set = BTreeSet::new(); + /// + /// assert_eq!(set.is_superset(&sub), false); + /// + /// set.insert(0); + /// set.insert(1); + /// assert_eq!(set.is_superset(&sub), false); + /// + /// set.insert(2); + /// assert_eq!(set.is_superset(&sub), true); + /// ``` + + #[inline(always)] + pub fn is_superset(&self, other: &BTreeSet) -> bool { + other.is_subset(self) + } + + /// Adds a value to the set. + /// + /// If the set did not have this value present, `true` is returned. + /// + /// If the set did have this value present, `false` is returned, and the + /// entry is not updated. See the [module-level documentation] for more. + /// + /// [module-level documentation]: index.html#insert-and-complex-keys + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut set = BTreeSet::new(); + /// + /// assert_eq!(set.insert(2), true); + /// assert_eq!(set.insert(2), false); + /// assert_eq!(set.len(), 1); + /// ``` + + #[inline] + pub fn try_insert(&mut self, value: T) -> Result { + Ok(self.map.try_insert(value, ())?.is_none()) + } + + /// Adds a value to the set, replacing the existing value, if any, that is equal to the given + /// one. Returns the replaced value. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut set = BTreeSet::new(); + /// set.insert(Vec::::new()); + /// + /// assert_eq!(set.get(&[][..]).unwrap().capacity(), 0); + /// set.replace(Vec::with_capacity(10)); + /// assert_eq!(set.get(&[][..]).unwrap().capacity(), 10); + /// ``` + + #[inline] + pub fn replace(&mut self, value: T) -> Result, TryReserveError> { + Ok(Recover::replace(&mut self.map, value)?) + } + + /// Removes a value from the set. Returns whether the value was + /// present in the set. + /// + /// The value may be any borrowed form of the set's value type, + /// but the ordering on the borrowed form *must* match the + /// ordering on the value type. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut set = BTreeSet::new(); + /// + /// set.insert(2); + /// assert_eq!(set.remove(&2), true); + /// assert_eq!(set.remove(&2), false); + /// ``` + + #[inline(always)] + pub fn remove(&mut self, value: &Q) -> bool + where + T: Borrow, + Q: Ord, + { + self.map.remove(value).is_some() + } + + /// Removes and returns the value in the set, if any, that is equal to the given one. + /// + /// The value may be any borrowed form of the set's value type, + /// but the ordering on the borrowed form *must* match the + /// ordering on the value type. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut set: BTreeSet<_> = [1, 2, 3].iter().cloned().collect(); + /// assert_eq!(set.take(&2), Some(2)); + /// assert_eq!(set.take(&2), None); + /// ``` + + #[inline(always)] + pub fn take(&mut self, value: &Q) -> Option + where + T: Borrow, + Q: Ord, + { + Recover::take(&mut self.map, value) + } + + /// Moves all elements from `other` into `Self`, leaving `other` empty. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut a = BTreeSet::new(); + /// a.insert(1); + /// a.insert(2); + /// a.insert(3); + /// + /// let mut b = BTreeSet::new(); + /// b.insert(3); + /// b.insert(4); + /// b.insert(5); + /// + /// a.append(&mut b); + /// + /// assert_eq!(a.len(), 5); + /// assert_eq!(b.len(), 0); + /// + /// assert!(a.contains(&1)); + /// assert!(a.contains(&2)); + /// assert!(a.contains(&3)); + /// assert!(a.contains(&4)); + /// assert!(a.contains(&5)); + /// ``` + + #[inline(always)] + pub fn append(&mut self, other: &mut Self) { + self.map.append(&mut other.map); + } + + /// Splits the collection into two at the given key. Returns everything after the given key, + /// including the key. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut a = BTreeSet::new(); + /// a.insert(1); + /// a.insert(2); + /// a.insert(3); + /// a.insert(17); + /// a.insert(41); + /// + /// let b = a.split_off(&3); + /// + /// assert_eq!(a.len(), 2); + /// assert_eq!(b.len(), 3); + /// + /// assert!(a.contains(&1)); + /// assert!(a.contains(&2)); + /// + /// assert!(b.contains(&3)); + /// assert!(b.contains(&17)); + /// assert!(b.contains(&41)); + /// ``` + + #[inline] + pub fn try_split_off(&mut self, key: &Q) -> Result + where + T: Borrow, + { + Ok(BTreeSet { + map: self.map.split_off(key)?, + }) + } +} + +impl BTreeSet { + /// Gets an iterator that visits the values in the `BTreeSet` in ascending order. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let set: BTreeSet = [1, 2, 3].iter().cloned().collect(); + /// let mut set_iter = set.iter(); + /// assert_eq!(set_iter.next(), Some(&1)); + /// assert_eq!(set_iter.next(), Some(&2)); + /// assert_eq!(set_iter.next(), Some(&3)); + /// assert_eq!(set_iter.next(), None); + /// ``` + /// + /// Values returned by the iterator are returned in ascending order: + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let set: BTreeSet = [3, 1, 2].iter().cloned().collect(); + /// let mut set_iter = set.iter(); + /// assert_eq!(set_iter.next(), Some(&1)); + /// assert_eq!(set_iter.next(), Some(&2)); + /// assert_eq!(set_iter.next(), Some(&3)); + /// assert_eq!(set_iter.next(), None); + /// ``` + + #[inline(always)] + pub fn iter(&self) -> Iter<'_, T> { + Iter { + iter: self.map.keys(), + } + } + + /// Returns the number of elements in the set. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut v = BTreeSet::new(); + /// assert_eq!(v.len(), 0); + /// v.insert(1); + /// assert_eq!(v.len(), 1); + /// ``` + + #[inline(always)] + pub fn len(&self) -> usize { + self.map.len() + } + + /// Returns `true` if the set contains no elements. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let mut v = BTreeSet::new(); + /// assert!(v.is_empty()); + /// v.insert(1); + /// assert!(!v.is_empty()); + /// ``` + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl FromIterator for BTreeSet { + #[inline] + fn from_iter>(iter: I) -> BTreeSet { + let mut set = BTreeSet::new(); + set.extend(iter); + set + } +} + +impl IntoIterator for BTreeSet { + type Item = T; + type IntoIter = IntoIter; + + /// Gets an iterator for moving out the `BTreeSet`'s contents. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let set: BTreeSet = [1, 2, 3, 4].iter().cloned().collect(); + /// + /// let v: Vec<_> = set.into_iter().collect(); + /// assert_eq!(v, [1, 2, 3, 4]); + /// ``` + #[inline(always)] + fn into_iter(self) -> IntoIter { + IntoIter { + iter: self.map.into_iter(), + } + } +} + +impl<'a, T> IntoIterator for &'a BTreeSet { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + #[inline(always)] + fn into_iter(self) -> Iter<'a, T> { + self.iter() + } +} + +impl Extend for BTreeSet { + #[inline] + fn extend>(&mut self, iter: Iter) { + iter.into_iter().for_each(move |elem| { + self.try_insert(elem).expect("Out of Mem"); + }); + } +} + +impl<'a, T: 'a + Ord + Copy> Extend<&'a T> for BTreeSet { + #[inline] + fn extend>(&mut self, iter: I) { + self.extend(iter.into_iter().cloned()); + } +} + +impl Default for BTreeSet { + /// Makes an empty `BTreeSet` with a reasonable choice of B. + #[inline(always)] + fn default() -> BTreeSet { + BTreeSet::new() + } +} + +impl Sub<&BTreeSet> for &BTreeSet { + type Output = BTreeSet; + + /// Returns the difference of `self` and `rhs` as a new `BTreeSet`. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let a: BTreeSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: BTreeSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// let result = &a - &b; + /// let result_vec: Vec<_> = result.into_iter().collect(); + /// assert_eq!(result_vec, [1, 2]); + /// ``` + fn sub(self, rhs: &BTreeSet) -> BTreeSet { + self.difference(rhs).cloned().collect() + } +} + +impl BitXor<&BTreeSet> for &BTreeSet { + type Output = BTreeSet; + + /// Returns the symmetric difference of `self` and `rhs` as a new `BTreeSet`. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let a: BTreeSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: BTreeSet<_> = vec![2, 3, 4].into_iter().collect(); + /// + /// let result = &a ^ &b; + /// let result_vec: Vec<_> = result.into_iter().collect(); + /// assert_eq!(result_vec, [1, 4]); + /// ``` + fn bitxor(self, rhs: &BTreeSet) -> BTreeSet { + self.symmetric_difference(rhs).cloned().collect() + } +} + +impl BitAnd<&BTreeSet> for &BTreeSet { + type Output = BTreeSet; + + /// Returns the intersection of `self` and `rhs` as a new `BTreeSet`. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let a: BTreeSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: BTreeSet<_> = vec![2, 3, 4].into_iter().collect(); + /// + /// let result = &a & &b; + /// let result_vec: Vec<_> = result.into_iter().collect(); + /// assert_eq!(result_vec, [2, 3]); + /// ``` + fn bitand(self, rhs: &BTreeSet) -> BTreeSet { + self.intersection(rhs).cloned().collect() + } +} + +impl BitOr<&BTreeSet> for &BTreeSet { + type Output = BTreeSet; + + /// Returns the union of `self` and `rhs` as a new `BTreeSet`. + /// + /// # Examples + /// + /// ``` + /// use std::collections::BTreeSet; + /// + /// let a: BTreeSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: BTreeSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// let result = &a | &b; + /// let result_vec: Vec<_> = result.into_iter().collect(); + /// assert_eq!(result_vec, [1, 2, 3, 4, 5]); + /// ``` + fn bitor(self, rhs: &BTreeSet) -> BTreeSet { + self.union(rhs).cloned().collect() + } +} + +impl Debug for BTreeSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.iter()).finish() + } +} + +impl Clone for Iter<'_, T> { + #[inline(always)] + fn clone(&self) -> Self { + Iter { + iter: self.iter.clone(), + } + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + #[inline(always)] + fn next(&mut self) -> Option<&'a T> { + self.iter.next() + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + #[inline(always)] + fn next_back(&mut self) -> Option<&'a T> { + self.iter.next_back() + } +} + +impl ExactSizeIterator for Iter<'_, T> { + #[inline(always)] + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for Iter<'_, T> {} + +impl Iterator for IntoIter { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|(k, _)| k) + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl DoubleEndedIterator for IntoIter { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|(k, _)| k) + } +} + +impl ExactSizeIterator for IntoIter { + #[inline(always)] + fn len(&self) -> usize { + self.iter.len() + } +} + +impl FusedIterator for IntoIter {} + +impl Clone for Range<'_, T> { + #[inline(always)] + fn clone(&self) -> Self { + Range { + iter: self.iter.clone(), + } + } +} + +impl<'a, T> Iterator for Range<'a, T> { + type Item = &'a T; + + #[inline] + fn next(&mut self) -> Option<&'a T> { + self.iter.next().map(|(k, _)| k) + } +} + +impl<'a, T> DoubleEndedIterator for Range<'a, T> { + #[inline] + fn next_back(&mut self) -> Option<&'a T> { + self.iter.next_back().map(|(k, _)| k) + } +} + +impl FusedIterator for Range<'_, T> {} + +/// Compares `x` and `y`, but return `short` if x is None and `long` if y is None +fn cmp_opt(x: Option<&T>, y: Option<&T>, short: Ordering, long: Ordering) -> Ordering { + match (x, y) { + (None, _) => short, + (_, None) => long, + (Some(x1), Some(y1)) => x1.cmp(y1), + } +} + +impl Clone for Difference<'_, T> { + fn clone(&self) -> Self { + Difference { + inner: match &self.inner { + DifferenceInner::Stitch { + self_iter, + other_iter, + } => DifferenceInner::Stitch { + self_iter: self_iter.clone(), + other_iter: other_iter.clone(), + }, + DifferenceInner::Search { + self_iter, + other_set, + } => DifferenceInner::Search { + self_iter: self_iter.clone(), + other_set, + }, + }, + } + } +} + +impl<'a, T: Ord> Iterator for Difference<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + match &mut self.inner { + DifferenceInner::Stitch { + self_iter, + other_iter, + } => { + let mut self_next = self_iter.next()?; + loop { + match other_iter + .peek() + .map_or(Less, |other_next| Ord::cmp(self_next, other_next)) + { + Less => return Some(self_next), + Equal => { + self_next = self_iter.next()?; + other_iter.next(); + } + Greater => { + other_iter.next(); + } + } + } + } + DifferenceInner::Search { + self_iter, + other_set, + } => loop { + let self_next = self_iter.next()?; + if !other_set.contains(&self_next) { + return Some(self_next); + } + }, + } + } + + fn size_hint(&self) -> (usize, Option) { + let (self_len, other_len) = match &self.inner { + DifferenceInner::Stitch { + self_iter, + other_iter, + } => (self_iter.len(), other_iter.len()), + DifferenceInner::Search { + self_iter, + other_set, + } => (self_iter.len(), other_set.len()), + }; + (self_len.saturating_sub(other_len), Some(self_len)) + } +} + +impl FusedIterator for Difference<'_, T> {} + +impl Clone for SymmetricDifference<'_, T> { + fn clone(&self) -> Self { + SymmetricDifference { + a: self.a.clone(), + b: self.b.clone(), + } + } +} + +impl<'a, T: Ord> Iterator for SymmetricDifference<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + loop { + match cmp_opt(self.a.peek(), self.b.peek(), Greater, Less) { + Less => return self.a.next(), + Equal => { + self.a.next(); + self.b.next(); + } + Greater => return self.b.next(), + } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.a.len() + self.b.len())) + } +} + +impl FusedIterator for SymmetricDifference<'_, T> {} + +impl Clone for Intersection<'_, T> { + fn clone(&self) -> Self { + Intersection { + inner: match &self.inner { + IntersectionInner::Stitch { + small_iter, + other_iter, + } => IntersectionInner::Stitch { + small_iter: small_iter.clone(), + other_iter: other_iter.clone(), + }, + IntersectionInner::Search { + small_iter, + large_set, + } => IntersectionInner::Search { + small_iter: small_iter.clone(), + large_set, + }, + }, + } + } +} + +impl<'a, T: Ord> Iterator for Intersection<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + match &mut self.inner { + IntersectionInner::Stitch { + small_iter, + other_iter, + } => { + let mut small_next = small_iter.next()?; + let mut other_next = other_iter.next()?; + loop { + match Ord::cmp(small_next, other_next) { + Less => small_next = small_iter.next()?, + Greater => other_next = other_iter.next()?, + Equal => return Some(small_next), + } + } + } + IntersectionInner::Search { + small_iter, + large_set, + } => loop { + let small_next = small_iter.next()?; + if large_set.contains(&small_next) { + return Some(small_next); + } + }, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let min_len = match &self.inner { + IntersectionInner::Stitch { small_iter, .. } => small_iter.len(), + IntersectionInner::Search { small_iter, .. } => small_iter.len(), + }; + (0, Some(min_len)) + } +} + +impl FusedIterator for Intersection<'_, T> {} + +impl Clone for Union<'_, T> { + #[inline] + fn clone(&self) -> Self { + Union { + a: self.a.clone(), + b: self.b.clone(), + } + } +} + +impl<'a, T: Ord> Iterator for Union<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + match cmp_opt(self.a.peek(), self.b.peek(), Greater, Less) { + Less => self.a.next(), + Equal => { + self.b.next(); + self.a.next() + } + Greater => self.b.next(), + } + } + + fn size_hint(&self) -> (usize, Option) { + let a_len = self.a.len(); + let b_len = self.b.len(); + (max(a_len, b_len), Some(a_len + b_len)) + } +} + +impl FusedIterator for Union<'_, T> {} diff --git a/third_party/rust/fallible_collections/src/format.rs b/third_party/rust/fallible_collections/src/format.rs new file mode 100644 index 0000000000..47a5fb9082 --- /dev/null +++ b/third_party/rust/fallible_collections/src/format.rs @@ -0,0 +1,46 @@ +//! A try_format! macro replacing format! +use super::FallibleVec; +use crate::TryReserveError; +use alloc::fmt::{Arguments, Write}; +use alloc::string::String; + +/// Take a max capacity a try allocating a string with it. +/// +/// # Warning: +/// +/// the max capacity must be > to the formating of the +/// arguments. If writing the argument on the string exceed the +/// capacity, no error is return and an allocation can occurs which +/// can lead to a panic +pub fn try_format(max_capacity: usize, args: Arguments<'_>) -> Result { + let v = FallibleVec::try_with_capacity(max_capacity)?; + let mut s = String::from_utf8(v).expect("wtf an empty vec should be valid utf8"); + s.write_fmt(args) + .expect("a formatting trait implementation returned an error"); + Ok(s) +} + +#[macro_export] +/// Take a max capacity a try allocating a string with it. +/// +/// # Warning: +/// +/// the max capacity must be > to the formating of the +/// arguments. If writing the argument on the string exceed the +/// capacity, no error is return and an allocation can occurs which +/// can lead to a panic +macro_rules! tryformat { + ($max_capacity:tt, $($arg:tt)*) => ( + $crate::format::try_format($max_capacity, format_args!($($arg)*)) + ) +} + +#[cfg(test)] +mod tests { + #[test] + fn format() { + assert_eq!(tryformat!(1, "1").unwrap(), format!("1")); + assert_eq!(tryformat!(1, "{}", 1).unwrap(), format!("{}", 1)); + assert_eq!(tryformat!(3, "{}", 123).unwrap(), format!("{}", 123)); + } +} diff --git a/third_party/rust/fallible_collections/src/hashmap.rs b/third_party/rust/fallible_collections/src/hashmap.rs new file mode 100644 index 0000000000..78ceed3052 --- /dev/null +++ b/third_party/rust/fallible_collections/src/hashmap.rs @@ -0,0 +1,116 @@ +//! Implement Fallible HashMap +use super::TryClone; +use crate::TryReserveError; +use core::borrow::Borrow; +use core::default::Default; +use core::fmt::Debug; +use core::hash::Hash; + +type HashMap = hashbrown::hash_map::HashMap; + +pub struct TryHashMap { + inner: HashMap, +} + +impl Default for TryHashMap { + #[inline(always)] + fn default() -> Self { + Self { + inner: Default::default(), + } + } +} + +impl Debug for TryHashMap { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.inner.fmt(f) + } +} + +impl TryHashMap +where + K: Eq + Hash, +{ + #[inline] + pub fn with_capacity(capacity: usize) -> Result { + let mut map = Self { + inner: HashMap::new(), + }; + map.reserve(capacity)?; + Ok(map) + } + + #[inline(always)] + pub fn get(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq, + { + self.inner.get(k) + } + + #[inline] + pub fn insert(&mut self, k: K, v: V) -> Result, TryReserveError> { + self.reserve(if self.inner.capacity() == 0 { 4 } else { 1 })?; + Ok(self.inner.insert(k, v)) + } + + #[inline(always)] + pub fn iter(&self) -> hashbrown::hash_map::Iter<'_, K, V> { + self.inner.iter() + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.inner.len() + } + + #[inline(always)] + pub fn remove(&mut self, k: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq, + { + self.inner.remove(k) + } + + #[inline(always)] + fn reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.inner.try_reserve(additional) + } +} + +impl IntoIterator for TryHashMap { + type Item = (K, V); + type IntoIter = hashbrown::hash_map::IntoIter; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl TryClone for TryHashMap +where + K: Eq + Hash + TryClone, + V: TryClone, +{ + fn try_clone(&self) -> Result { + let mut clone = Self::with_capacity(self.inner.len())?; + + for (key, value) in self.inner.iter() { + clone.insert(key.try_clone()?, value.try_clone()?)?; + } + + Ok(clone) + } +} + +#[test] +fn tryhashmap_oom() { + match TryHashMap::::default().reserve(core::usize::MAX) { + Ok(_) => panic!("it should be OOM"), + _ => (), + } +} diff --git a/third_party/rust/fallible_collections/src/lib.rs b/third_party/rust/fallible_collections/src/lib.rs new file mode 100644 index 0000000000..68273dff1c --- /dev/null +++ b/third_party/rust/fallible_collections/src/lib.rs @@ -0,0 +1,81 @@ +//! impl Fallible collections on allocation errors, quite as describe +//! in [RFC 2116](https://github.com/rust-lang/rfcs/blob/master/text/2116-alloc-me-maybe.md) +//! This was used in the turbofish OS hobby project to mitigate the +//! the lack of faillible allocation in rust. +//! +//! The `Try*` types in this module are thin wrappers around the stdlib types to add +//! support for fallible allocation. The API differences from the stdlib types ensure +//! that all operations which allocate return a `Result`. For the most part, this simply +//! means adding a `Result` return value to functions which return nothing or a +//! non-`Result` value. However, these types implement some traits whose API cannot +//! communicate failure, but which do require allocation, so it is important that these +//! wrapper types do not implement these traits. +//! +//! Specifically, these types must not implement any of the following traits: +//! - Clone +//! - Extend +//! - From +//! - FromIterator +//! +//! This list may not be exhaustive. Exercise caution when implementing +//! any new traits to ensure they won't potentially allocate in a way that +//! can't return a Result to indicate allocation failure. + +#![cfg_attr(not(test), no_std)] +#![cfg_attr(feature = "unstable", feature(try_reserve))] +#![cfg_attr(feature = "unstable", feature(specialization))] +#![cfg_attr(feature = "unstable", feature(allocator_api))] +#![cfg_attr(feature = "unstable", feature(dropck_eyepatch))] +#![cfg_attr(feature = "unstable", feature(ptr_internals))] +#![cfg_attr(feature = "unstable", feature(core_intrinsics))] +#![cfg_attr(feature = "unstable", feature(maybe_uninit_ref))] +#![cfg_attr(feature = "unstable", feature(maybe_uninit_slice))] +#![cfg_attr(feature = "unstable", feature(maybe_uninit_extra))] +#![cfg_attr(feature = "unstable", feature(internal_uninit_const))] +extern crate alloc; +#[cfg(feature = "std_io")] +extern crate std; + +pub mod boxed; +pub use boxed::*; +#[macro_use] +pub mod vec; +pub use vec::*; +pub mod rc; +pub use rc::*; +pub mod arc; +pub use arc::*; +#[cfg(feature = "unstable")] +pub mod btree; +#[cfg(not(feature = "unstable"))] +pub mod hashmap; +#[cfg(not(feature = "unstable"))] +pub use hashmap::*; +#[macro_use] +pub mod format; +pub mod try_clone; + +#[cfg(feature = "unstable")] +pub use alloc::collections::TryReserveError; +#[cfg(not(feature = "unstable"))] +pub use hashbrown::TryReserveError; + +#[cfg(feature = "std_io")] +pub use vec::std_io::*; + +/// trait for trying to clone an elem, return an error instead of +/// panic if allocation failed +/// # Examples +/// +/// ``` +/// use fallible_collections::TryClone; +/// let mut vec = vec![42, 100]; +/// assert_eq!(vec.try_clone().unwrap(), vec) +/// ``` +pub trait TryClone { + /// try clone method, (Self must be sized because of Result + /// constraint) + fn try_clone(&self) -> Result + where + Self: core::marker::Sized; +} diff --git a/third_party/rust/fallible_collections/src/rc.rs b/third_party/rust/fallible_collections/src/rc.rs new file mode 100644 index 0000000000..4fc16dc2af --- /dev/null +++ b/third_party/rust/fallible_collections/src/rc.rs @@ -0,0 +1,35 @@ +//! Implement a Fallible Rc +use super::FallibleBox; +use crate::TryReserveError; +use alloc::boxed::Box; +use alloc::rc::Rc; +/// trait to implement Fallible Rc +pub trait FallibleRc { + /// try creating a new Rc, returning a Result, + /// TryReserveError> if allocation failed + fn try_new(t: T) -> Result + where + Self: Sized; +} + +impl FallibleRc for Rc { + fn try_new(t: T) -> Result { + let b = as FallibleBox>::try_new(t)?; + Ok(Rc::from(b)) + } +} + +#[cfg(test)] +mod test { + #[test] + fn fallible_rc() { + use std::rc::Rc; + + let mut x = Rc::new(3); + *Rc::get_mut(&mut x).unwrap() = 4; + assert_eq!(*x, 4); + + let _y = Rc::clone(&x); + assert!(Rc::get_mut(&mut x).is_none()); + } +} diff --git a/third_party/rust/fallible_collections/src/try_clone.rs b/third_party/rust/fallible_collections/src/try_clone.rs new file mode 100644 index 0000000000..a8ff0442b4 --- /dev/null +++ b/third_party/rust/fallible_collections/src/try_clone.rs @@ -0,0 +1,39 @@ +//! this module implements try clone for primitive rust types + +use super::TryClone; +use crate::TryReserveError; + +macro_rules! impl_try_clone { + ($($e: ty),*) => { + $(impl TryClone for $e { + #[inline(always)] + fn try_clone(&self) -> Result + where + Self: core::marker::Sized, + { + Ok(*self) + } + } + )* + } +} + +impl_try_clone!(u8, u16, u32, u64, i8, i16, i32, i64, usize, isize, bool); + +impl TryClone for Option { + #[inline] + fn try_clone(&self) -> Result { + Ok(match self { + Some(t) => Some(t.try_clone()?), + None => None, + }) + } +} +// impl TryClone for T { +// fn try_clone(&self) -> Result +// where +// Self: core::marker::Sized, +// { +// Ok(*self) +// } +// } diff --git a/third_party/rust/fallible_collections/src/vec.rs b/third_party/rust/fallible_collections/src/vec.rs new file mode 100644 index 0000000000..c27b0adba9 --- /dev/null +++ b/third_party/rust/fallible_collections/src/vec.rs @@ -0,0 +1,921 @@ +//! Implement Fallible Vec +use super::TryClone; +use crate::TryReserveError; +#[allow(unused_imports)] +use alloc::alloc::{alloc, realloc, Layout}; +use alloc::vec::Vec; +use core::convert::TryInto as _; + +#[cfg(feature = "unstable")] +#[macro_export] +/// macro trying to create a vec, return a +/// Result,TryReserveError> +macro_rules! try_vec { + ($elem:expr; $n:expr) => ( + $crate::vec::try_from_elem($elem, $n) + ); + ($($x:expr),*) => ( + match as $crate::boxed::FallibleBox<_>>::try_new([$($x),*]) { + Err(e) => Err(e), + Ok(b) => Ok(<[_]>::into_vec(b)), + } + ); + ($($x:expr,)*) => ($crate::try_vec![$($x),*]) +} + +/// trait implementing all fallible methods on vec +pub trait FallibleVec { + /// see reserve + fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>; + /// see push + fn try_push(&mut self, elem: T) -> Result<(), TryReserveError>; + /// try push and give back ownership in case of error + fn try_push_give_back(&mut self, elem: T) -> Result<(), (T, TryReserveError)>; + /// see with capacity, (Self must be sized by the constraint of Result) + fn try_with_capacity(capacity: usize) -> Result + where + Self: core::marker::Sized; + /// see insert + fn try_insert(&mut self, index: usize, element: T) -> Result<(), (T, TryReserveError)>; + /// see append + fn try_append(&mut self, other: &mut Self) -> Result<(), TryReserveError>; + /// see resize, only works when the `value` implements Copy, otherwise, look at try_resize_no_copy + fn try_resize(&mut self, new_len: usize, value: T) -> Result<(), TryReserveError> + where + T: Copy + Clone; + fn try_resize_with(&mut self, new_len: usize, f: F) -> Result<(), TryReserveError> + where + F: FnMut() -> T; + /// resize the vec by trying to clone the value repeatingly + fn try_resize_no_copy(&mut self, new_len: usize, value: T) -> Result<(), TryReserveError> + where + T: TryClone; + /// see resize, only works when the `value` implements Copy, otherwise, look at try_extend_from_slice_no_copy + fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), TryReserveError> + where + T: Copy + Clone; + /// extend the vec by trying to clone the value in `other` + fn try_extend_from_slice_no_copy(&mut self, other: &[T]) -> Result<(), TryReserveError> + where + T: TryClone; +} + +/// TryVec is a thin wrapper around alloc::vec::Vec to provide support for +/// fallible allocation. +/// +/// See the crate documentation for more. +#[derive(PartialEq)] +pub struct TryVec { + inner: Vec, +} + +impl Default for TryVec { + #[inline(always)] + fn default() -> Self { + Self { + inner: Default::default(), + } + } +} + +impl core::fmt::Debug for TryVec { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.inner.fmt(f) + } +} + +impl TryVec { + #[inline(always)] + pub fn new() -> Self { + Self { inner: Vec::new() } + } + + #[inline] + pub fn with_capacity(capacity: usize) -> Result { + Ok(Self { + inner: FallibleVec::try_with_capacity(capacity)?, + }) + } + + #[inline(always)] + pub fn append(&mut self, other: &mut Self) -> Result<(), TryReserveError> { + FallibleVec::try_append(&mut self.inner, &mut other.inner) + } + + #[inline(always)] + pub fn as_mut_slice(&mut self) -> &mut [T] { + self + } + + #[inline(always)] + pub fn as_slice(&self) -> &[T] { + self + } + + #[inline(always)] + pub fn clear(&mut self) { + self.inner.clear() + } + + #[cfg(test)] + pub fn into_inner(self) -> Vec { + self.inner + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + #[inline(always)] + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + inner: self.inner.iter_mut(), + } + } + + #[inline(always)] + pub fn iter(&self) -> Iter { + Iter { + inner: self.inner.iter(), + } + } + + #[inline(always)] + pub fn pop(&mut self) -> Option { + self.inner.pop() + } + + #[inline(always)] + pub fn push(&mut self, value: T) -> Result<(), TryReserveError> { + FallibleVec::try_push(&mut self.inner, value) + } + + #[inline(always)] + pub fn reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + FallibleVec::try_reserve(&mut self.inner, additional) + } + + #[inline(always)] + pub fn resize_with(&mut self, new_len: usize, f: F) -> Result<(), TryReserveError> + where + F: FnMut() -> T, + { + FallibleVec::try_resize_with(&mut self.inner, new_len, f) + } +} + +impl TryClone for TryVec { + #[inline] + fn try_clone(&self) -> Result { + self.as_slice().try_into() + } +} + +impl TryVec> { + pub fn concat(&self) -> Result, TryReserveError> { + let size = self.iter().map(|v| v.inner.len()).sum(); + let mut result = TryVec::with_capacity(size)?; + for v in self.iter() { + result.inner.try_extend_from_slice_no_copy(&v.inner)?; + } + Ok(result) + } +} + +impl TryVec { + #[inline(always)] + pub fn extend_from_slice(&mut self, other: &[T]) -> Result<(), TryReserveError> { + self.inner.try_extend_from_slice_no_copy(other) + } +} + +impl IntoIterator for TryVec { + type Item = T; + type IntoIter = alloc::vec::IntoIter; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl<'a, T> IntoIterator for &'a TryVec { + type Item = &'a T; + type IntoIter = alloc::slice::Iter<'a, T>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.inner.iter() + } +} + +#[cfg(feature = "std_io")] +pub mod std_io { + use super::*; + use std::io::{self, Read, Take, Write}; + + pub trait TryRead { + fn try_read_to_end(&mut self, buf: &mut TryVec) -> io::Result; + + #[inline] + fn read_into_try_vec(&mut self) -> io::Result> { + let mut buf = TryVec::new(); + self.try_read_to_end(&mut buf)?; + Ok(buf) + } + } + + impl TryRead for Take { + /// This function reserves the upper limit of what `src` can generate before + /// reading all bytes until EOF in this source, placing them into `buf`. If the + /// allocation is unsuccessful, or reading from the source generates an error + /// before reaching EOF, this will return an error. Otherwise, it will return + /// the number of bytes read. + /// + /// Since `Take::limit()` may return a value greater than the number of bytes + /// which can be read from the source, it's possible this function may fail + /// in the allocation phase even though allocating the number of bytes available + /// to read would have succeeded. In general, it is assumed that the callers + /// have accurate knowledge of the number of bytes of interest and have created + /// `src` accordingly. + #[inline] + fn try_read_to_end(&mut self, buf: &mut TryVec) -> io::Result { + try_read_up_to(self, self.limit(), buf) + } + } + + /// Read up to `limit` bytes from `src`, placing them into `buf` and returning the + /// number of bytes read. Space for `limit` additional bytes is reserved in `buf`, so + /// this function will return an error if the allocation fails. + pub fn try_read_up_to( + src: &mut R, + limit: u64, + buf: &mut TryVec, + ) -> io::Result { + let additional = limit + .try_into() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + buf.reserve(additional) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "reserve allocation failed"))?; + let bytes_read = src.take(limit).read_to_end(&mut buf.inner)?; + Ok(bytes_read) + } + + impl Write for TryVec { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.extend_from_slice(buf) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "extend_from_slice failed"))?; + Ok(buf.len()) + } + + #[inline(always)] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn try_read_to_end() { + let mut src = b"1234567890".take(5); + let mut buf = TryVec::new(); + src.try_read_to_end(&mut buf).unwrap(); + assert_eq!(buf.len(), 5); + assert_eq!(buf, b"12345".as_ref()); + } + + #[test] + fn read_into_try_vec() { + let mut src = b"1234567890".take(5); + let buf = src.read_into_try_vec().unwrap(); + assert_eq!(buf.len(), 5); + assert_eq!(buf, b"12345".as_ref()); + } + + #[test] + fn read_into_try_vec_oom() { + let mut src = b"1234567890".take(core::usize::MAX.try_into().expect("usize < u64")); + assert!(src.read_into_try_vec().is_err()); + } + + #[test] + fn try_read_up_to() { + let src = b"1234567890"; + let mut buf = TryVec::new(); + super::try_read_up_to(&mut src.as_ref(), 5, &mut buf).unwrap(); + assert_eq!(buf.len(), 5); + assert_eq!(buf, b"12345".as_ref()); + } + + #[test] + fn try_read_up_to_oom() { + let src = b"1234567890"; + let mut buf = TryVec::new(); + let limit = core::usize::MAX.try_into().expect("usize < u64"); + let res = super::try_read_up_to(&mut src.as_ref(), limit, &mut buf); + assert!(res.is_err()); + } + } +} + +impl PartialEq> for TryVec { + #[inline(always)] + fn eq(&self, other: &Vec) -> bool { + self.inner.eq(other) + } +} + +impl<'a, T: PartialEq> PartialEq<&'a [T]> for TryVec { + #[inline(always)] + fn eq(&self, other: &&[T]) -> bool { + self.inner.eq(other) + } +} + +impl PartialEq<&str> for TryVec { + #[inline] + fn eq(&self, other: &&str) -> bool { + self.as_slice() == other.as_bytes() + } +} + +impl core::convert::AsRef<[u8]> for TryVec { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.inner.as_ref() + } +} + +impl core::convert::From> for TryVec { + #[inline(always)] + fn from(value: Vec) -> Self { + Self { inner: value } + } +} + +impl core::convert::TryFrom<&[T]> for TryVec { + type Error = TryReserveError; + + #[inline] + fn try_from(value: &[T]) -> Result { + let mut v = Self::new(); + v.inner.try_extend_from_slice_no_copy(value)?; + Ok(v) + } +} + +impl core::convert::TryFrom<&str> for TryVec { + type Error = TryReserveError; + + #[inline] + fn try_from(value: &str) -> Result { + let mut v = Self::new(); + v.extend_from_slice(value.as_bytes())?; + Ok(v) + } +} + +impl core::ops::Deref for TryVec { + type Target = [T]; + + #[inline(always)] + fn deref(&self) -> &[T] { + self.inner.deref() + } +} + +impl core::ops::DerefMut for TryVec { + fn deref_mut(&mut self) -> &mut [T] { + self.inner.deref_mut() + } +} + +pub struct Iter<'a, T> { + inner: alloc::slice::Iter<'a, T>, +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + #[inline(always)] + fn next(&mut self) -> Option { + self.inner.next() + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +pub struct IterMut<'a, T> { + inner: alloc::slice::IterMut<'a, T>, +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + #[inline(always)] + fn next(&mut self) -> Option { + self.inner.next() + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +#[cfg(not(feature = "unstable"))] +fn vec_try_reserve(v: &mut Vec, additional: usize) -> Result<(), TryReserveError> { + let available = v.capacity().checked_sub(v.len()).expect("capacity >= len"); + if additional > available { + let increase = additional + .checked_sub(available) + .expect("additional > available"); + let new_cap = v + .capacity() + .checked_add(increase) + .ok_or(TryReserveError::CapacityOverflow)?; + vec_try_extend(v, new_cap)?; + debug_assert!(v.capacity() == new_cap); + } + + Ok(()) +} + +#[cfg(not(feature = "unstable"))] +fn vec_try_extend(v: &mut Vec, new_cap: usize) -> Result<(), TryReserveError> { + let old_len = v.len(); + let old_cap: usize = v.capacity(); + + if old_cap >= new_cap { + return Ok(()); + } + + let elem_size = core::mem::size_of::(); + let new_alloc_size = new_cap + .checked_mul(elem_size) + .ok_or(TryReserveError::CapacityOverflow)?; + + // required for alloc safety + // See https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html#safety-1 + // Should be unreachable given prior `old_cap >= new_cap` check. + assert!(new_alloc_size > 0); + + let align = core::mem::align_of::(); + + let (new_ptr, layout) = { + if old_cap == 0 { + let layout = Layout::from_size_align(new_alloc_size, align).expect("Invalid layout"); + let new_ptr = unsafe { alloc(layout) }; + (new_ptr, layout) + } else { + let old_alloc_size = old_cap + .checked_mul(elem_size) + .ok_or(TryReserveError::CapacityOverflow)?; + let layout = Layout::from_size_align(old_alloc_size, align).expect("Invalid layout"); + let new_ptr = unsafe { realloc(v.as_mut_ptr() as *mut u8, layout, new_alloc_size) }; + (new_ptr, layout) + } + }; + + if new_ptr.is_null() { + return Err(TryReserveError::AllocError { layout }); + } + + let new_vec = unsafe { Vec::from_raw_parts(new_ptr.cast(), old_len, new_cap) }; + + core::mem::forget(core::mem::replace(v, new_vec)); + Ok(()) +} + +impl FallibleVec for Vec { + + #[inline(always)] + fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + #[cfg(feature = "unstable")] + { + self.try_reserve(additional) + } + + #[cfg(not(feature = "unstable"))] + { + vec_try_reserve(self, additional) + } + } + + #[inline] + fn try_push(&mut self, elem: T) -> Result<(), TryReserveError> { + FallibleVec::try_reserve(self, 1)?; + Ok(self.push(elem)) + } + + #[inline] + fn try_push_give_back(&mut self, elem: T) -> Result<(), (T, TryReserveError)> { + if let Err(e) = FallibleVec::try_reserve(self, 1) { + return Err((elem, e)); + } + Ok(self.push(elem)) + } + + #[inline] + fn try_with_capacity(capacity: usize) -> Result + where + Self: core::marker::Sized, + { + let mut n = Self::new(); + FallibleVec::try_reserve(&mut n, capacity)?; + Ok(n) + } + + #[inline] + fn try_insert(&mut self, index: usize, element: T) -> Result<(), (T, TryReserveError)> { + if let Err(e) = FallibleVec::try_reserve(self, 1) { + return Err((element, e)); + } + Ok(self.insert(index, element)) + } + #[inline] + fn try_append(&mut self, other: &mut Self) -> Result<(), TryReserveError> { + FallibleVec::try_reserve(self, other.len())?; + Ok(self.append(other)) + } + fn try_resize(&mut self, new_len: usize, value: T) -> Result<(), TryReserveError> + where + T: Copy + Clone, + { + let len = self.len(); + if new_len > len { + FallibleVec::try_reserve(self, new_len - len)?; + } + Ok(self.resize(new_len, value)) + } + fn try_resize_with(&mut self, new_len: usize, f: F) -> Result<(), TryReserveError> + where + F: FnMut() -> T, + { + let len = self.len(); + if new_len > len { + FallibleVec::try_reserve(self, new_len - len)?; + } + Ok(self.resize_with(new_len, f)) + } + fn try_resize_no_copy(&mut self, new_len: usize, value: T) -> Result<(), TryReserveError> + where + T: TryClone, + { + let len = self.len(); + + if new_len > len { + self.try_extend_with(new_len - len, TryExtendElement(value)) + } else { + Ok(self.truncate(new_len)) + } + } + #[inline] + fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), TryReserveError> + where + T: Clone, + { + FallibleVec::try_reserve(self, other.len())?; + Ok(self.extend_from_slice(other)) + } + fn try_extend_from_slice_no_copy(&mut self, other: &[T]) -> Result<(), TryReserveError> + where + T: TryClone, + { + let mut len = self.len(); + FallibleVec::try_reserve(self, other.len())?; + let mut iterator = other.iter(); + while let Some(element) = iterator.next() { + unsafe { + core::ptr::write(self.get_unchecked_mut(len), element.try_clone()?); + // NB can't overflow since we would have had to alloc the address space + len += 1; + self.set_len(len); + } + } + Ok(()) + } +} + +trait ExtendWith { + fn next(&mut self) -> Result; + fn last(self) -> T; +} + +struct TryExtendElement(T); +impl ExtendWith for TryExtendElement { + #[inline(always)] + fn next(&mut self) -> Result { + self.0.try_clone() + } + #[inline(always)] + fn last(self) -> T { + self.0 + } +} + +trait TryExtend { + fn try_extend_with>( + &mut self, + n: usize, + value: E, + ) -> Result<(), TryReserveError>; +} + +impl TryExtend for Vec { + /// Extend the vector by `n` values, using the given generator. + fn try_extend_with>( + &mut self, + n: usize, + mut value: E, + ) -> Result<(), TryReserveError> { + FallibleVec::try_reserve(self, n)?; + + unsafe { + let mut ptr = self.as_mut_ptr().add(self.len()); + + let mut local_len = self.len(); + // Write all elements except the last one + for _ in 1..n { + core::ptr::write(ptr, value.next()?); + ptr = ptr.offset(1); + // Increment the length in every step in case next() panics + local_len += 1; + self.set_len(local_len); + } + + if n > 0 { + // We can write the last element directly without cloning needlessly + core::ptr::write(ptr, value.last()); + local_len += 1; + self.set_len(local_len); + } + + // len set by scope guard + } + Ok(()) + } +} + +trait Truncate { + fn truncate(&mut self, len: usize); +} + +impl Truncate for Vec { + fn truncate(&mut self, len: usize) { + let current_len = self.len(); + unsafe { + let mut ptr = self.as_mut_ptr().add(current_len); + // Set the final length at the end, keeping in mind that + // dropping an element might panic. Works around a missed + // optimization, as seen in the following issue: + // https://github.com/rust-lang/rust/issues/51802 + let mut local_len = self.len(); + + // drop any extra elements + for _ in len..current_len { + ptr = ptr.offset(-1); + core::ptr::drop_in_place(ptr); + local_len -= 1; + self.set_len(local_len); + } + } + } +} + +/// try creating a vec from an `elem` cloned `n` times, see std::from_elem +#[cfg(feature = "unstable")] +pub fn try_from_elem(elem: T, n: usize) -> Result, TryReserveError> { + ::try_from_elem(elem, n) +} + +// Specialization trait used for Vec::from_elem +#[cfg(feature = "unstable")] +trait SpecFromElem: Sized { + fn try_from_elem(elem: Self, n: usize) -> Result, TryReserveError>; +} + +#[cfg(feature = "unstable")] +impl SpecFromElem for T { + default fn try_from_elem(elem: Self, n: usize) -> Result, TryReserveError> { + let mut v = Vec::new(); + v.try_resize_no_copy(n, elem)?; + Ok(v) + } +} + +#[cfg(feature = "unstable")] +impl SpecFromElem for u8 { + #[inline] + fn try_from_elem(elem: u8, n: usize) -> Result, TryReserveError> { + unsafe { + let mut v = FallibleVec::try_with_capacity(n)?; + core::ptr::write_bytes(v.as_mut_ptr(), elem, n); + v.set_len(n); + Ok(v) + } + } +} + +impl TryClone for Vec { + #[inline] + fn try_clone(&self) -> Result + where + Self: core::marker::Sized, + { + let mut v = Vec::new(); + v.try_extend_from_slice_no_copy(self)?; + Ok(v) + } +} + +pub trait TryFromIterator: Sized { + fn try_from_iterator>(iterator: T) -> Result; +} + +impl TryFromIterator for Vec { + fn try_from_iterator>(iterator: T) -> Result + where + T: IntoIterator, + { + let mut new = Self::new(); + for i in iterator { + new.try_push(i)?; + } + Ok(new) + } +} + +pub trait TryCollect { + fn try_collect>(self) -> Result; +} + +impl TryCollect for T +where + T: IntoIterator, +{ + #[inline(always)] + fn try_collect>(self) -> Result { + C::try_from_iterator(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(feature = "unstable")] + fn vec() { + // let v: Vec = from_elem(1, 10); + let v: Vec> = try_vec![try_vec![42; 10].unwrap(); 100].unwrap(); + println!("{:?}", v); + let v2 = try_vec![0, 1, 2]; + println!("{:?}", v2); + assert_eq!(2 + 2, 4); + } + + #[test] + fn try_clone_vec() { + // let v: Vec = from_elem(1, 10); + let v = vec![42; 100]; + assert_eq!(v.try_clone().unwrap(), v); + } + + #[test] + fn try_clone_oom() { + let layout = Layout::new::(); + let v = + unsafe { Vec::::from_raw_parts(alloc(layout), core::usize::MAX, core::usize::MAX) }; + assert!(v.try_clone().is_err()); + } + + #[test] + fn tryvec_try_clone_oom() { + let layout = Layout::new::(); + let inner = + unsafe { Vec::::from_raw_parts(alloc(layout), core::usize::MAX, core::usize::MAX) }; + let tv = TryVec { inner }; + assert!(tv.try_clone().is_err()); + } + + // #[test] + // fn try_out_of_mem() { + // let v = try_vec![42_u8; 1000000000]; + // assert_eq!(v.try_clone().unwrap(), v); + // } + + #[test] + fn oom() { + let mut vec: Vec = Vec::new(); + match FallibleVec::try_reserve(&mut vec, core::usize::MAX) { + Ok(_) => panic!("it should be OOM"), + _ => (), + } + } + + #[test] + fn tryvec_oom() { + let mut vec: TryVec = TryVec::new(); + match vec.reserve(core::usize::MAX) { + Ok(_) => panic!("it should be OOM"), + _ => (), + } + } + + #[test] + fn try_reserve() { + let mut vec: Vec<_> = vec![1]; + let additional_room = vec.capacity() - vec.len(); + let additional = additional_room + 1; + let old_cap = vec.capacity(); + FallibleVec::try_reserve(&mut vec, additional).unwrap(); + assert!(vec.capacity() > old_cap); + } + + #[test] + fn tryvec_reserve() { + let mut vec: TryVec<_> = vec![1].into(); + let old_cap = vec.inner.capacity(); + let new_cap = old_cap + 1; + vec.reserve(new_cap).unwrap(); + assert!(vec.inner.capacity() >= new_cap); + } + + #[test] + fn try_reserve_idempotent() { + let mut vec: Vec<_> = vec![1]; + let additional_room = vec.capacity() - vec.len(); + let additional = additional_room + 1; + FallibleVec::try_reserve(&mut vec, additional).unwrap(); + let cap_after_reserve = vec.capacity(); + FallibleVec::try_reserve(&mut vec, additional).unwrap(); + assert_eq!(vec.capacity(), cap_after_reserve); + } + + #[test] + fn tryvec_reserve_idempotent() { + let mut vec: TryVec<_> = vec![1].into(); + let old_cap = vec.inner.capacity(); + let new_cap = old_cap + 1; + vec.reserve(new_cap).unwrap(); + let cap_after_reserve = vec.inner.capacity(); + vec.reserve(new_cap).unwrap(); + assert_eq!(cap_after_reserve, vec.inner.capacity()); + } + + #[test] + fn capacity_overflow() { + let mut vec: Vec<_> = vec![1]; + match FallibleVec::try_reserve(&mut vec, core::usize::MAX) { + Ok(_) => panic!("capacity calculation should overflow"), + _ => (), + } + } + + #[test] + fn tryvec_capacity_overflow() { + let mut vec: TryVec<_> = vec![1].into(); + match vec.reserve(core::usize::MAX) { + Ok(_) => panic!("capacity calculation should overflow"), + _ => (), + } + } + + #[test] + fn extend_from_slice() { + let mut vec: Vec = b"foo".as_ref().into(); + vec.try_extend_from_slice(b"bar").unwrap(); + assert_eq!(vec, b"foobar".as_ref()); + } + + #[test] + fn tryvec_extend_from_slice() { + let mut vec: TryVec = b"foo".as_ref().try_into().unwrap(); + vec.extend_from_slice(b"bar").unwrap(); + assert_eq!(vec, b"foobar".as_ref()); + } + + #[test] + #[cfg(not(feature = "unstable"))] + fn try_extend_zst() { + let mut vec: Vec<()> = Vec::new(); + assert_eq!(vec.capacity(), core::usize::MAX); + assert!(vec_try_extend(&mut vec, 10).is_ok()); + assert!(vec_try_extend(&mut vec, core::usize::MAX).is_ok()); + } + + #[test] + fn try_reserve_zst() { + let mut vec: Vec<()> = Vec::new(); + assert!(FallibleVec::try_reserve(&mut vec, core::usize::MAX).is_ok()); + } +} diff --git a/third_party/rust/mime_guess/src/mime_types.rs b/third_party/rust/mime_guess/src/mime_types.rs index 244b4c819c..8b0af889ce 100644 --- a/third_party/rust/mime_guess/src/mime_types.rs +++ b/third_party/rust/mime_guess/src/mime_types.rs @@ -1343,6 +1343,7 @@ pub static MIME_TYPES: &[(&str, &[&str])] = &[ ("webm", &["video/webm"]), ("webmanifest", &["application/manifest+json"]), ("webp", &["image/webp"]), + ("avif", &["image/avif"]), ("webtest", &["application/xml"]), ("wg", &["application/vnd.pmi.widget"]), ("wgt", &["application/widget"]), diff --git a/toolkit/components/mediasniffer/nsMediaSniffer.cpp b/toolkit/components/mediasniffer/nsMediaSniffer.cpp index faa5ca1174..e514947cd2 100644 --- a/toolkit/components/mediasniffer/nsMediaSniffer.cpp +++ b/toolkit/components/mediasniffer/nsMediaSniffer.cpp @@ -51,6 +51,7 @@ nsMediaSnifferEntry sFtypEntries[] = { PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "qt ", VIDEO_QUICKTIME), PATTERN_ENTRY("\xFF\xFF\xFF", "iso", VIDEO_MP4), // Could be isom or iso2. PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "mmp4", VIDEO_MP4), + PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "avif", IMAGE_AVIF), }; static bool MatchesBrands(const uint8_t aData[4], nsACString& aSniffedType) { @@ -78,7 +79,7 @@ static bool MatchesBrands(const uint8_t aData[4], nsACString& aSniffedType) { // including MP4 (described at // http://mimesniff.spec.whatwg.org/#signature-for-mp4), M4A (Apple iTunes // audio), and 3GPP. -static bool MatchesMP4(const uint8_t* aData, const uint32_t aLength, +bool MatchesMP4(const uint8_t* aData, const uint32_t aLength, nsACString& aSniffedType) { if (aLength <= MP4_MIN_BYTES_COUNT) { return false; diff --git a/toolkit/components/mediasniffer/nsMediaSniffer.h b/toolkit/components/mediasniffer/nsMediaSniffer.h index 6bc1dd3420..541d7c62b4 100644 --- a/toolkit/components/mediasniffer/nsMediaSniffer.h +++ b/toolkit/components/mediasniffer/nsMediaSniffer.h @@ -32,6 +32,8 @@ struct nsMediaSnifferEntry { const char* mContentType; }; +bool MatchesMP4(const uint8_t* aData, const uint32_t aLength, nsACString& aSniffedType); + class nsMediaSniffer final : public nsIContentSniffer { public: NS_DECL_ISUPPORTS diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index db2a96e2a0..881a0f0de1 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -1345,6 +1345,18 @@ "description": "WebP image decode speed (Kbytes/sec)", "bug_numbers": [1294490] }, + "IMAGE_DECODE_SPEED_AVIF": { + "record_in_processes": ["main", "content"], + "products": ["firefox", "fennec"], + "alert_emails": ["gfx-telemetry-alerts@mozilla.com"], + "expires_in_version": "never", + "kind": "exponential", + "low": 500, + "high": 50000000, + "n_buckets": 50, + "description": "AVIF image decode speed (Kbytes/sec)", + "bug_numbers": [1294490] + }, "IMAGE_REQUEST_DISPATCHED": { "record_in_processes": ["main", "content"], "alert_emails": ["gfx-telemetry-alerts@mozilla.com","aosmond@mozilla.com"], @@ -1353,6 +1365,217 @@ "description": "Track how many image requests required event dispatching because we were unable to predict the correct scheduler group: true if the request required dispatching. See image/imgRequestProxy.cpp for details.", "bug_numbers": [1359833] }, + "AVIF_DECODE_RESULT": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": [ + "success", + "parse_error", + "no_primary_item", + "decode_error", + "size_overflow", + "out_of_memory", + "pipe_init_error", + "write_buffer_error", + "alpha_y_sz_mismatch", + "alpha_y_bpc_mismatch", + "ispe_mismatch", + "invalid_cicp", + "unsupported_a1lx", + "unsupported_a1op", + "unsupported_clap", + "unsupported_grid", + "unsupported_ipro", + "unsupported_lsel" + ], + "description": "Decode result of AVIF image", + "bug_numbers": [1670827] + }, + "AVIF_AOM_DECODE_ERROR": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": [ + "error", + "mem_error", + "abi_mismatch", + "incapable", + "unsup_bitstream", + "unsup_feature", + "corrupt_frame", + "invalid_param" + ], + "description": "Error code from aom_codec_decode when decoding AVIF image", + "bug_numbers": [1690406] + }, + "AVIF_DECODER": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": ["dav1d", "aom"], + "description": "Decoder of AVIF image", + "bug_numbers": [1670827] + }, + "AVIF_YUV_COLOR_SPACE": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": ["BT601", "BT709", "BT2020", "identity", "unknown"], + "description": "YUV color space of AVIF image", + "bug_numbers": [1670827] + }, + "AVIF_BIT_DEPTH": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": ["color_8", "color_10", "color_12", "color_16", "unknown"], + "description": "Bits per pixel of AVIF image", + "bug_numbers": [1670827] + }, + "AVIF_ALPHA": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": ["absent", "present"], + "description": "AVIF alpha plane", + "bug_numbers": [1696045] + }, + "AVIF_COLR": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": ["nclx", "icc", "absent", "both"], + "description": "AVIF colour information type", + "bug_numbers": [1696045] + }, + "AVIF_CICP_CP": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": [ + "RESERVED", + "BT709", + "UNSPECIFIED", + "RESERVED_3", + "BT470M", + "BT470BG", + "BT601", + "SMPTE240", + "GENERIC_FILM", + "BT2020", + "XYZ", + "SMPTE431", + "SMPTE432", + "RESERVED_13", + "RESERVED_14", + "RESERVED_15", + "RESERVED_16", + "RESERVED_17", + "RESERVED_18", + "RESERVED_19", + "RESERVED_20", + "RESERVED_21", + "EBU3213", + "RESERVED_REST" + ], + "description": "AVIF CICP colour primaries", + "bug_numbers": [1696045] + }, + "AVIF_CICP_TC": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": [ + "RESERVED", + "BT709", + "UNSPECIFIED", + "RESERVED_3", + "BT470M", + "BT470BG", + "BT601", + "SMPTE240", + "LINEAR", + "LOG_100", + "LOG_100_SQRT10", + "IEC61966", + "BT_1361", + "SRGB", + "BT2020_10BIT", + "BT2020_12BIT", + "SMPTE2084", + "SMPTE428", + "HLG", + "RESERVED_REST" + ], + "description": "AVIF CICP transfer characteristics", + "bug_numbers": [1696045] + }, + "AVIF_CICP_MC": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": [ + "IDENTITY", + "BT709", + "UNSPECIFIED", + "RESERVED", + "FCC", + "BT470BG", + "BT601", + "SMPTE240", + "YCGCO", + "BT2020_NCL", + "BT2020_CL", + "SMPTE2085", + "CHROMAT_NCL", + "CHROMAT_CL", + "ICTCP", + "RESERVED_REST" + ], + "description": "AVIF CICP matrix coefficients", + "bug_numbers": [1696045] + }, + "AVIF_ISPE": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": ["valid", "absent", "bitstream_mismatch"], + "description": "AVIF spatial extents (image size)", + "bug_numbers": [1696045] + }, + "AVIF_PIXI": { + "record_in_processes": ["main", "content"], + "alert_emails": ["cchang@mozilla.com", "jbauman@mozilla.com"], + "expires_in_version": "never", + "releaseChannelCollection": "opt-out", + "kind": "categorical", + "labels": ["valid", "absent", "bitstream_mismatch"], + "description": "AVIF pixel information (bits per channel)", + "bug_numbers": [1696045] + }, "KEYPRESS_PRESENT_LATENCY": { "record_in_processes": [ "all" ], "alert_emails": [ "perfteam@mozilla.com", "vchin@mozilla.com" ], diff --git a/toolkit/library/gtest/rust/Cargo.toml b/toolkit/library/gtest/rust/Cargo.toml index 24c9bb75b3..0d0a9a8b56 100644 --- a/toolkit/library/gtest/rust/Cargo.toml +++ b/toolkit/library/gtest/rust/Cargo.toml @@ -11,7 +11,7 @@ cubeb-remoting = ["gkrust-shared/cubeb-remoting"] cubeb_pulse_rust = ["gkrust-shared/cubeb_pulse_rust"] gecko_debug = ["gkrust-shared/gecko_debug"] simd-accel = ["gkrust-shared/simd-accel"] -moz_memory = ["gkrust-shared/moz_memory"] +#moz_memory = ["gkrust-shared/moz_memory"] moz_places = ["gkrust-shared/moz_places"] spidermonkey_rust = ["gkrust-shared/spidermonkey_rust"] cranelift_x86 = ["gkrust-shared/cranelift_x86"] diff --git a/toolkit/library/rust/Cargo.toml b/toolkit/library/rust/Cargo.toml index 9281b56138..571ece6a86 100644 --- a/toolkit/library/rust/Cargo.toml +++ b/toolkit/library/rust/Cargo.toml @@ -12,7 +12,7 @@ cubeb-remoting = ["gkrust-shared/cubeb-remoting"] cubeb_pulse_rust = ["gkrust-shared/cubeb_pulse_rust"] gecko_debug = ["gkrust-shared/gecko_debug"] simd-accel = ["gkrust-shared/simd-accel"] -moz_memory = ["gkrust-shared/moz_memory"] +#moz_memory = ["gkrust-shared/moz_memory"] moz_places = ["gkrust-shared/moz_places"] spidermonkey_rust = ["gkrust-shared/spidermonkey_rust"] cranelift_x86 = ["gkrust-shared/cranelift_x86"] diff --git a/toolkit/library/rust/gkrust-features.mozbuild b/toolkit/library/rust/gkrust-features.mozbuild index dd5c0cc8b9..9c245797f3 100644 --- a/toolkit/library/rust/gkrust-features.mozbuild +++ b/toolkit/library/rust/gkrust-features.mozbuild @@ -23,8 +23,8 @@ if CONFIG['MOZ_RUST_SIMD']: if ((CONFIG['OS_ARCH'] == 'Linux' and CONFIG['OS_TARGET'] != 'Android') or CONFIG['OS_ARCH'] == 'Darwin' or (CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['CPU_ARCH'] != 'aarch64')) and CONFIG['MOZ_CUBEB_REMOTING']: gkrust_features += ['cubeb-remoting'] -if CONFIG['MOZ_MEMORY']: - gkrust_features += ['moz_memory'] +#if CONFIG['MOZ_MEMORY']: +# gkrust_features += ['moz_memory'] if CONFIG['MOZ_PLACES']: gkrust_features += ['moz_places'] diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index 501ad417c3..07a61924e1 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -56,7 +56,7 @@ cubeb-remoting = ["cubeb-sys", "audioipc-client", "audioipc-server"] cubeb_pulse_rust = ["cubeb-sys", "cubeb-pulse"] gecko_debug = ["geckoservo/gecko_debug", "nsstring/gecko_debug"] simd-accel = ["encoding_glue/simd-accel", "jsrust_shared/simd-accel"] -moz_memory = ["mp4parse_capi/mp4parse_fallible"] +#moz_memory = ["mp4parse_capi/mp4parse_fallible"] moz_places = ["bookmark_sync"] spidermonkey_rust = ["jsrust_shared/baldrdash"] cranelift_x86 = ["jsrust_shared/cranelift_x86"] diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index 7f1b151a3d..0677d5cf09 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -491,6 +491,8 @@ static const nsExtraMimeTypeEntry extraMimeEntries[] = { {IMAGE_XBM, "xbm", "XBM Image"}, {IMAGE_SVG_XML, "svg", "Scalable Vector Graphics"}, {IMAGE_WEBP, "webp", "WebP Image"}, + {IMAGE_AVIF, "avif", "AV1 Image File"}, + {MESSAGE_RFC822, "eml", "RFC-822 data"}, {TEXT_PLAIN, "txt,text", "Text File"}, {APPLICATION_JSON, "json", "JavaScript Object Notation"},