dump of the functional dt-schema validator
this is slow but it works.
Change-Id: Ifdf94271680786c28365d861a1a0f13215378f8d
diff --git a/third_party/rust_crates/forks/dt-schema/Cargo.lock b/third_party/rust_crates/forks/dt-schema/Cargo.lock
new file mode 100644
index 0000000..9f5bf808
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/Cargo.lock
@@ -0,0 +1,938 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr"
+version = "0.15.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a93b8a41dbe230ad5087cc721f8d41611de654542180586b315d9f4cf6b72bef"
+dependencies = [
+ "psl",
+ "psl-types",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
+
+[[package]]
+name = "argh"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c375edecfd2074d5edcc31396860b6e54b6f928714d0e097b983053fac0cabe3"
+dependencies = [
+ "argh_derive",
+ "argh_shared",
+]
+
+[[package]]
+name = "argh_derive"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa013479b80109a1bf01a039412b0f0013d716f36921226d86c6709032fb7a03"
+dependencies = [
+ "argh_shared",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "argh_shared"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "149f75bbec1827618262e0855a68f0f9a7f2edc13faebf33c4f16d6725edb6a9"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bumpalo"
+version = "3.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cc"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "time",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "cxx"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dt-schema"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "argh",
+ "byteorder",
+ "fancy-regex",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "thiserror",
+ "tracing",
+ "tracing-subscriber",
+ "url",
+ "valico",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95b4efe5be9104a4a18a9916e86654319895138be727b229820c39257c30dda"
+dependencies = [
+ "bit-set",
+ "regex",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+
+[[package]]
+name = "js-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-pointer"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fe841b94e719a482213cee19dd04927cf412f26d8dc84c5a446c081e49c2997"
+dependencies = [
+ "serde_json",
+]
+
+[[package]]
+name = "jsonway"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "effcb749443c905fbaef49d214f8b1049c240e0adb7af9baa0e201e625e4f9de"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.135"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "psl"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07242622d9f4b9c1a6fe9c2691cf18ee7d34400a5eed2e1668c756bfaea93fb3"
+dependencies = [
+ "psl-types",
+]
+
+[[package]]
+name = "psl-types"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "regex"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+
+[[package]]
+name = "ryu"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+
+[[package]]
+name = "scratch"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
+
+[[package]]
+name = "serde"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8613d593412a0deb7bbd8de9d908efff5a0cb9ccd8f62c641e7b2ed2f57291d1"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "syn"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
+dependencies = [
+ "nu-ansi-term",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"
+
+[[package]]
+name = "uritemplate-next"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcde98d1fc3f528255b1ecb22fb688ee0d23deb672a8c57127df10b98b4bd18c"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "url"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "valico"
+version = "3.6.1-alpha.0"
+dependencies = [
+ "addr",
+ "base64",
+ "chrono",
+ "fancy-regex",
+ "json-pointer",
+ "jsonway",
+ "percent-encoding",
+ "phf",
+ "phf_codegen",
+ "serde",
+ "serde_json",
+ "uritemplate-next",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/third_party/rust_crates/forks/dt-schema/Cargo.toml b/third_party/rust_crates/forks/dt-schema/Cargo.toml
new file mode 100644
index 0000000..000537a
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "dt-schema"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0"
+argh = "0.1.9"
+#jsonschema = {version = "0.16", default-features = false, features = ["draft201909"]}
+valico = {path = "../valico"}
+url = "2.3"
+serde_yaml = "0.9.13"
+serde_json = "1.0.86"
+serde = {version = "1", features = ["derive"]}
+fancy-regex = "0.8"
+thiserror = "1.0"
+tracing = "0.1"
+tracing-subscriber = "0.3"
+byteorder = "1.4"
diff --git a/third_party/rust_crates/forks/dt-schema/README.fuchsia b/third_party/rust_crates/forks/dt-schema/README.fuchsia
new file mode 100644
index 0000000..01e1e6c
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/README.fuchsia
@@ -0,0 +1 @@
+This is not actually third-party code. This is currently the source of truth, but eventually it will exist as a separate repository that is rolled into fuchsia.git.
diff --git a/third_party/rust_crates/forks/dt-schema/README.md b/third_party/rust_crates/forks/dt-schema/README.md
new file mode 100644
index 0000000..c7ca325
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/README.md
@@ -0,0 +1,11 @@
+## dt-schema
+
+This is an implementation of the [dt-schema](https://github.com/devicetree-org/dt-schema) python tool in Rust.
+
+Currently it only supports validating schemas against devicetree blobs.
+
+Example usage:
+
+```
+cargo run validate my-schema.yaml my-other-schema.yaml --dtb my-devicetree.dtb
+```
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree.rs
new file mode 100644
index 0000000..d898796
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree.rs
@@ -0,0 +1,326 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{
+ collections::HashMap,
+ io::{Read, Seek},
+ rc::Rc,
+};
+
+use byteorder::{BigEndian, ByteOrder};
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::{devicetree::parser::Parser, path::JsonPath};
+
+use self::{
+ property::{DevicetreeJsonError, Property},
+ types::PropertyTypeLookup,
+ util::NodeAncestry,
+};
+
+mod fixups;
+mod parser;
+mod property;
+pub mod types;
+mod util;
+
+#[derive(Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum NodeOrProperty {
+ Node(serde_json::Map<String, serde_json::Value>),
+ Property(Vec<u8>),
+}
+
+#[derive(Debug, Clone)]
+/// Represents a node in a devicetree.
+pub struct Node {
+ name: String,
+ properties: Vec<Property>,
+ children: Vec<Rc<Node>>,
+}
+
+impl Node {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn properties(&self) -> &Vec<Property> {
+ &self.properties
+ }
+
+ #[tracing::instrument(level = "info", skip_all, fields(name=self.name))]
+ pub fn as_json(
+ self: &Rc<Self>,
+ type_lookup: &dyn PropertyTypeLookup,
+ path: JsonPath,
+ ancestry: NodeAncestry,
+ ) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeJsonError> {
+ let mut ret = serde_json::Map::new();
+ for prop in self.properties.iter() {
+ ret.insert(
+ prop.key().clone(),
+ prop.value_json(&self.name, type_lookup, path.extend(prop.key()))?,
+ );
+ }
+
+ ret = fixups::fixup_node(&self.name, ret, &path, type_lookup, &ancestry)?;
+
+ for node in self.children.iter() {
+ ret.insert(
+ node.name().to_owned(),
+ json!(node.as_json(
+ type_lookup,
+ path.extend(node.name()),
+ ancestry.visit(self.clone()),
+ )?),
+ );
+ }
+
+ let name = if self.name.is_empty() {
+ "/"
+ } else {
+ &self.name
+ };
+
+ ret.insert("$nodename".to_owned(), json!([name]));
+
+ Ok(ret)
+ }
+}
+
+#[derive(Debug, Clone)]
+/// Represents a devicetree.
+pub struct Devicetree {
+ root_node: Rc<Node>,
+ phandle: HashMap<usize, (Rc<Node>, JsonPath)>,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("I/O error")]
+ IoError(#[from] std::io::Error),
+ #[error("Parse error")]
+ ParseError(#[from] parser::ParseError),
+}
+
+impl Devicetree {
+ pub fn from_reader(reader: impl Read + Seek) -> Result<Devicetree, Error> {
+ let mut parser = Parser::new(reader)?;
+ let mut ret = Devicetree {
+ root_node: parser.parse()?,
+ phandle: HashMap::new(),
+ };
+
+ let mut map = HashMap::new();
+ for (item, path) in ret.iter() {
+ if let Some(phandle) = item
+ .properties()
+ .iter()
+ .find(|prop| prop.key() == "phandle")
+ {
+ map.insert(BigEndian::read_u32(&phandle.value) as usize, (item, path));
+ }
+ }
+
+ ret.phandle = map;
+
+ Ok(ret)
+ }
+
+ pub fn iter(&self) -> Iter {
+ Iter::new(self.root_node.clone())
+ }
+
+ pub fn as_json(
+ &self,
+ lookup: &dyn PropertyTypeLookup,
+ ) -> Result<serde_json::Value, DevicetreeJsonError> {
+ self.root_node
+ .as_json(lookup, JsonPath::new(), NodeAncestry::new(self))
+ .map(|v| v.into())
+ }
+
+ pub fn by_phandle(&self, phandle: usize) -> Option<&(Rc<Node>, JsonPath)> {
+ self.phandle.get(&phandle)
+ }
+}
+
+pub struct Iter {
+ node_stack: Vec<(Rc<Node>, JsonPath, isize)>,
+}
+
+impl Iter {
+ fn new(root_node: Rc<Node>) -> Self {
+ Iter {
+ node_stack: vec![(root_node, JsonPath::new(), -1)],
+ }
+ }
+}
+
+impl Iterator for Iter {
+ type Item = (Rc<Node>, JsonPath);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let (next_node, path, child_index) = self.node_stack.pop()?;
+ // Record that we need to visit our next child at some point.
+ self.node_stack
+ .push((next_node.clone(), path.clone(), child_index + 1));
+
+ // If we haven't started, yield the root node first.
+ if child_index == -1 {
+ return Some((next_node, JsonPath::new()));
+ }
+
+ let child_index: usize = child_index.try_into().unwrap();
+ if next_node.children.len() > child_index {
+ // Visit the next child of |next_node| next.
+ let child = &next_node.children[child_index];
+ let child_path = path.extend(child.name());
+ self.node_stack.push((child.clone(), child_path.clone(), 0));
+ Some((next_node.children[child_index].clone(), child_path))
+ } else {
+ // Finished with children of |next_node|, so move up the stack.
+ self.node_stack.pop();
+ self.next()
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::BTreeSet;
+
+ use crate::validator::{dimension::Dimension, property_type::PropertyType};
+
+ use super::*;
+
+ fn empty_node(name: &str) -> Rc<Node> {
+ Rc::new(Node {
+ name: name.to_owned(),
+ properties: vec![],
+ children: vec![],
+ })
+ }
+
+ fn make_prop(name: &str, data: &[u8]) -> Property {
+ Property {
+ key: name.to_owned(),
+ value: data.into(),
+ }
+ }
+
+ #[test]
+ fn test_iterator() {
+ let fake_tree = Devicetree {
+ root_node: Rc::new(Node {
+ name: "".to_owned(),
+ properties: vec![],
+ children: vec![
+ empty_node("a"),
+ Rc::new(Node {
+ name: "b".to_owned(),
+ properties: vec![],
+ children: vec![empty_node("c"), empty_node("d")],
+ }),
+ empty_node("e"),
+ ],
+ }),
+ phandle: HashMap::new(),
+ };
+
+ let order: Vec<(String, String)> = fake_tree
+ .iter()
+ .map(|(n, p)| (n.name.clone(), p.to_string()))
+ .collect();
+ let expected: Vec<(String, String)> = [
+ ("", "/"),
+ ("a", "/a"),
+ ("b", "/b"),
+ ("c", "/b/c"),
+ ("d", "/b/d"),
+ ("e", "/e"),
+ ]
+ .into_iter()
+ .map(|(a, b)| (a.to_owned(), b.to_owned()))
+ .collect();
+ assert_eq!(order, expected);
+ }
+
+ struct FakeTypeLookup {
+ types: HashMap<String, (BTreeSet<PropertyType>, Option<Dimension>)>,
+ }
+
+ impl PropertyTypeLookup for FakeTypeLookup {
+ fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType> {
+ self.types
+ .get(propname)
+ .map(|v| v.0.clone())
+ .unwrap_or_default()
+ }
+
+ fn get_property_dimensions(&self, propname: &str) -> Option<Dimension> {
+ self.types.get(propname).and_then(|v| v.1)
+ }
+ }
+
+ #[test]
+ fn test_as_json() {
+ let fake_tree = Devicetree {
+ root_node: Rc::new(Node {
+ name: "".to_owned(),
+ properties: vec![
+ make_prop("test,str-prop", &[b'a', b'b', 0]),
+ make_prop("test,int16-array-prop", &[0x10, 0x01, 0x20, 0x02]),
+ ],
+ children: vec![
+ empty_node("a"),
+ Rc::new(Node {
+ name: "b".to_owned(),
+ properties: vec![
+ make_prop("test,bool-prop", &[]),
+ make_prop("test,int8-matrix-prop", &[0x10, 0x10, 0x20, 0x20]),
+ ],
+ children: vec![empty_node("c"), empty_node("d")],
+ }),
+ empty_node("e"),
+ ],
+ }),
+ phandle: HashMap::new(),
+ };
+ let types: HashMap<String, (BTreeSet<PropertyType>, Option<Dimension>)> = vec![
+ ("test,str-prop", ([PropertyType::String], None)),
+ ("test,int16-array-prop", ([PropertyType::Int16Array], None)),
+ ("test,bool-prop", ([PropertyType::Flag], None)),
+ (
+ "test,int8-matrix-prop",
+ ([PropertyType::Uint8Matrix], Some([[1, 0], [2, 2]].into())),
+ ),
+ ]
+ .into_iter()
+ .map(|(k, (i, d))| (k.to_owned(), (BTreeSet::from(i), d)))
+ .collect();
+ let lookup = FakeTypeLookup { types };
+
+ assert_eq!(
+ fake_tree.as_json(&lookup).expect("as json ok"),
+ json!({
+ "$nodename": ["/"],
+ "test,str-prop": ["ab"],
+ "test,int16-array-prop": [[0x1001, 0x2002]],
+ "a": {"$nodename": ["a"]},
+ "b": {
+ "$nodename": ["b"],
+ "test,bool-prop": true,
+ "test,int8-matrix-prop": [[0x10, 0x10], [0x20, 0x20]],
+ "c": {"$nodename": ["c"]},
+ "d": {"$nodename": ["d"]}
+ },
+ "e": {
+ "$nodename": ["e"],
+ }
+ })
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups.rs
new file mode 100644
index 0000000..09615c2
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups.rs
@@ -0,0 +1,113 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+mod addresses;
+mod gpios;
+mod interrupts;
+mod phandle;
+mod utils;
+
+use crate::path::JsonPath;
+
+pub use self::utils::PhandleError;
+
+use super::types::PropertyTypeLookup;
+
+#[derive(thiserror::Error, Debug)]
+pub enum DevicetreeFixupError {
+ #[error("Could not fixup property '{0}' as it was in an unexpected format: {1}")]
+ UnexpectedPropertyFormat(JsonPath, serde_json::Value),
+
+ #[error("Invalid phandle array")]
+ InvalidPhandle(#[from] PhandleError),
+
+ #[error("Missing property {0}")]
+ MissingProperty(JsonPath),
+
+ #[error("JSON error")]
+ JsonError(#[from] serde_json::Error),
+}
+
+/// Trait that is used by fixups to lookup information about the devicetree.
+/// A |DevicetreeLookup| has knowledge of the node it is associated with.
+pub trait DevicetreeLookup {
+ /// Gets value of the given |cell_prop| at |phandle|.
+ fn get_cells_size(
+ &self,
+ phandle_path: JsonPath,
+ phandle: u32,
+ cell_prop: &str,
+ ) -> Result<u32, PhandleError>;
+
+ /// Gets value of |prop| at this node's parent or an ancestor.
+ fn get_prop_from_parents(&self, prop: &str) -> Result<Option<u32>, PhandleError>;
+
+ fn get_cells_prop_for_prop(&self, prop: &str) -> String {
+ match prop {
+ "assigned-clocks" => "#clock-cells",
+ "assigned-clock-parents" => "#clock-cells",
+ "cooling-device" => "#cooling-cells",
+ "interrupts-extended" => "#interrupt-cells",
+ "interconnects" => "#interconnect-cells",
+ "mboxes" => "#mbox-cells",
+ "sound-dai" => "#sound-dai-cells",
+ "msi-parent" => "#msi-cells",
+ "msi-ranges" => "#interrupt-cells",
+ prop => {
+ if prop.ends_with('s') && !prop.contains("gpio") {
+ let name = "#".to_owned() + &prop[0..prop.len() - 1] + "-cells";
+ return name;
+ } else {
+ prop
+ }
+ }
+ }
+ .to_owned()
+ }
+}
+
+pub trait DevicetreeFixup: Sized {
+ /// Make a new instance of this |fixup|. Returns None if the fixup is not applicable to this property.
+ /// Note that |node| is a full device tree node.
+ fn new(
+ nodename: &str,
+ node: &serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+ type_lookup: &dyn PropertyTypeLookup,
+ ) -> Result<Option<Self>, DevicetreeFixupError>;
+
+ /// Run the fixup and give the fixed node back.
+ fn fixup(
+ self,
+ lookup: &dyn DevicetreeLookup,
+ ) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeFixupError>;
+}
+
+/// Helper function that does a fixup (if it is applicable, i.e. T::new() returns Ok(Some(...))), or just returns the given |value|.
+#[tracing::instrument(level = "debug", skip_all, fields(fixup=std::any::type_name::<T>(), path=%path))]
+fn do_fixup<T: DevicetreeFixup>(
+ nodename: &str,
+ value: serde_json::Map<String, serde_json::Value>,
+ path: &JsonPath,
+ type_lookup: &dyn PropertyTypeLookup,
+ lookup: &dyn DevicetreeLookup,
+) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeFixupError> {
+ T::new(nodename, &value, path.clone(), type_lookup)?
+ .map(|v| v.fixup(lookup))
+ .unwrap_or(Ok(value))
+}
+
+pub fn fixup_node(
+ nodename: &str,
+ value: serde_json::Map<String, serde_json::Value>,
+ path: &JsonPath,
+ type_lookup: &dyn PropertyTypeLookup,
+ lookup: &dyn DevicetreeLookup,
+) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeFixupError> {
+ let value = do_fixup::<gpios::GpioFixup>(nodename, value, path, type_lookup, lookup)?;
+ let value = do_fixup::<addresses::AddressFixup>(nodename, value, path, type_lookup, lookup)?;
+ let value = do_fixup::<interrupts::InterruptFixup>(nodename, value, path, type_lookup, lookup)?;
+ let value = do_fixup::<phandle::PhandleFixup>(nodename, value, path, type_lookup, lookup)?;
+ Ok(value)
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/addresses.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/addresses.rs
new file mode 100644
index 0000000..5e68a8e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/addresses.rs
@@ -0,0 +1,252 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use crate::{devicetree::types::PropertyTypeLookup, path::JsonPath};
+
+use super::{utils::get_cells_property_from_json, DevicetreeFixup, DevicetreeFixupError};
+
+pub struct AddressFixup {
+ node: serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+}
+
+impl AddressFixup {
+ // Expect something of the format:
+ // reg = [[<data>...]]
+ // and output something of the format:
+ // reg = [[data...], [data...]]
+ fn fixup_reg(
+ &self,
+ value: &serde_json::Value,
+ address_cells: usize,
+ size_cells: usize,
+ ) -> Result<serde_json::Value, DevicetreeFixupError> {
+ match value {
+ serde_json::Value::Array(array) => {
+ if array.len() != 1 || !array[0].is_array() {
+ tracing::error!("reg property should be an array containing an array.");
+ return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.clone(),
+ array.clone().into(),
+ ));
+ }
+
+ let array = array[0].as_array().unwrap();
+ let new_values: Option<Vec<Vec<u64>>> = array
+ .chunks(size_cells + address_cells)
+ .map(|v| v.iter().map(|i| i.as_u64()).collect::<Option<Vec<u64>>>())
+ .collect();
+ if let Some(new_values) = new_values {
+ Ok(serde_json::to_value(new_values)?)
+ } else {
+ Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.clone(),
+ array.clone().into(),
+ ))
+ }
+ }
+ other => {
+ tracing::error!("reg property should be an array.");
+ Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.clone(),
+ other.clone(),
+ ))
+ }
+ }
+ }
+
+ fn handle_ranges(
+ &self,
+ value: &serde_json::Value,
+ address_cells: usize,
+ prop: &str,
+ ) -> Result<Option<serde_json::Value>, DevicetreeFixupError> {
+ let array = match value {
+ serde_json::Value::Array(array) => {
+ if array.len() != 1 || !array[0].is_array() {
+ tracing::error!("ranges property should be an array containing an array.");
+ return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.extend(prop),
+ array.clone().into(),
+ ));
+ }
+ array[0].as_array().unwrap()
+ }
+ serde_json::Value::Bool(true) => {
+ return Ok(None);
+ }
+ other => {
+ tracing::error!("ranges property should be an array.");
+ return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.extend(prop),
+ other.clone(),
+ ));
+ }
+ };
+
+ let child_address_cells =
+ get_cells_property_from_json(&self.node, &self.path, "#address-cells")?;
+ let child_size_cells = get_cells_property_from_json(&self.node, &self.path, "#size-cells")?;
+
+ let new_values: Option<Vec<Vec<u64>>> = array
+ .chunks(
+ (child_size_cells + child_address_cells + address_cells as u64)
+ .try_into()
+ .unwrap(),
+ )
+ .map(|v| v.iter().map(|i| i.as_u64()).collect::<Option<Vec<u64>>>())
+ .collect();
+ Ok(Some(serde_json::to_value(new_values.ok_or(
+ DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.extend(prop),
+ array.clone().into(),
+ ),
+ )?)?))
+ }
+}
+
+impl DevicetreeFixup for AddressFixup {
+ fn new(
+ _nodename: &str,
+ node: &serde_json::Map<String, serde_json::Value>,
+ path: crate::path::JsonPath,
+ _type_lookup: &dyn PropertyTypeLookup,
+ ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+ if node.contains_key("reg")
+ || node.contains_key("ranges")
+ || node.contains_key("dma-ranges")
+ {
+ Ok(Some(AddressFixup {
+ node: node.clone(),
+ path,
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn fixup(
+ mut self,
+ lookup: &dyn super::DevicetreeLookup,
+ ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+ let size_cells = lookup.get_prop_from_parents("#size-cells")?.unwrap_or(1) as usize;
+ let address_cells = lookup.get_prop_from_parents("#address-cells")?.unwrap_or(2) as usize;
+
+ if let Some(reg) = self.node.get("reg") {
+ self.node.insert(
+ "reg".to_owned(),
+ self.fixup_reg(reg, address_cells, size_cells)?,
+ );
+ }
+
+ if let Some(ranges) = self.node.get("ranges") {
+ if let Some(new_ranges) = self.handle_ranges(ranges, address_cells, "ranges")? {
+ self.node.insert("ranges".to_owned(), new_ranges);
+ }
+ }
+
+ if let Some(ranges) = self.node.get("dma-ranges") {
+ if let Some(new_ranges) = self.handle_ranges(ranges, address_cells, "dma-ranges")? {
+ self.node.insert("dma-ranges".to_owned(), new_ranges);
+ }
+ }
+
+ Ok(self.node)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use crate::devicetree::fixups::utils::for_tests::FakeLookup;
+
+ use super::*;
+ #[test]
+ fn test_fixup_reg() {
+ let test_node = json!({
+ "reg": [[0x0, 0xdead_beef_u32, 0x0, 0x1000, 0x0, 0x472_0000, 0x0, 0x1_0000]],
+ });
+
+ let test_lookup = FakeLookup::new()
+ .with_parent("#address-cells", 2)
+ .with_parent("#size-cells", 2);
+
+ let result = AddressFixup::new(
+ "",
+ test_node.as_object().unwrap(),
+ JsonPath::new(),
+ &test_lookup,
+ )
+ .expect("valid node")
+ .expect("fixup applies")
+ .fixup(&test_lookup)
+ .expect("fixup ok");
+
+ assert_eq!(
+ serde_json::Value::from(result),
+ json!({
+ "reg": [[0x0, 0xdead_beef_u32, 0x0, 0x1000], [0x0, 0x472_0000, 0x0, 0x1_0000]],
+ })
+ );
+ }
+
+ #[test]
+ fn test_reg_implicit_values() {
+ let test_node = json!({
+ "reg": [[0x0, 0xdead_beef_u32, 0x1000, 0x0, 0x472_0000, 0x1_0000]],
+ });
+ let test_lookup = FakeLookup::new();
+
+ let result = AddressFixup::new(
+ "",
+ test_node.as_object().unwrap(),
+ JsonPath::new(),
+ &test_lookup,
+ )
+ .expect("valid node")
+ .expect("fixup applies")
+ .fixup(&test_lookup)
+ .expect("fixup ok");
+
+ assert_eq!(
+ serde_json::Value::from(result),
+ json!({
+ "reg": [[0x0, 0xdead_beef_u32, 0x1000], [0x0, 0x472_0000, 0x1_0000]],
+ })
+ );
+ }
+
+ #[test]
+ fn test_range_conversion() {
+ let test_node = json!({
+ "#address-cells": [[1]],
+ "#size-cells": [[2]],
+ "ranges": [[0x1000, 0x0, 0x0, 0x0, 0x1000, 0x5000, 0x0, 0x4000, 0x0, 0x800]],
+ "dma-ranges": [[0x10, 0x0, 0x1000, 0x4010, 0x0]],
+ });
+ let test_lookup = FakeLookup::new();
+
+ let result = AddressFixup::new(
+ "",
+ test_node.as_object().unwrap(),
+ JsonPath::new(),
+ &test_lookup,
+ )
+ .expect("valid node")
+ .expect("fixup applies")
+ .fixup(&test_lookup)
+ .expect("fixup ok");
+
+ assert_eq!(
+ serde_json::Value::from(result),
+ json!({
+ "#address-cells": [[1]],
+ "#size-cells": [[2]],
+ "ranges": [[0x1000, 0x0, 0x0, 0x0, 0x1000], [0x5000, 0x0, 0x4000, 0x0, 0x800]],
+ "dma-ranges": [[0x10, 0x0, 0x1000, 0x4010, 0x0]],
+ })
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/gpios.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/gpios.rs
new file mode 100644
index 0000000..50f6fc8
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/gpios.rs
@@ -0,0 +1,103 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::VecDeque;
+
+use crate::{devicetree::types::PropertyTypeLookup, path::JsonPath};
+
+use super::{
+ utils::{PhandleError, PhandleIterator},
+ DevicetreeFixup, DevicetreeFixupError,
+};
+
+/// |GpioFixup| converts any GPIO properties to matrices.
+pub struct GpioFixup {
+ node: serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+}
+
+fn key_is_gpio(key: &String) -> bool {
+ (key.ends_with("-gpio") || key.ends_with("-gpios") || key == "gpio" || key == "gpios")
+ && !key.ends_with(",nr-gpios")
+}
+
+impl DevicetreeFixup for GpioFixup {
+ fn new(
+ _nodename: &str,
+ node: &serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+ _type_lookup: &dyn PropertyTypeLookup,
+ ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+ // This fixup applies to nodes with any -gpio properties, but not a gpio-hog one.
+ let applies = node.keys().any(key_is_gpio) && !node.keys().any(|key| key == "gpio-hog");
+ if !applies {
+ Ok(None)
+ } else {
+ Ok(Some(GpioFixup {
+ node: node.clone(),
+ path,
+ }))
+ }
+ }
+
+ fn fixup(
+ mut self,
+ lookup: &dyn super::DevicetreeLookup,
+ ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+ for (key, value) in self.node.iter_mut().filter(|(key, _)| key_is_gpio(key)) {
+ let array = match value
+ .as_array()
+ .filter(|v| v.len() == 1)
+ .and_then(|v| v[0].as_array())
+ {
+ Some(array) => array,
+ None => {
+ return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.extend(key),
+ value.clone(),
+ ))
+ }
+ };
+
+ let collected: Result<Vec<VecDeque<u64>>, PhandleError> =
+ PhandleIterator::new(array, lookup, self.path.extend(key), "#gpio-cells").collect();
+ *value = serde_json::to_value(collected?)?;
+ }
+
+ Ok(self.node)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use serde_json::json;
+
+ use crate::devicetree::fixups::utils::for_tests::FakeLookup;
+
+ use super::*;
+
+ #[test]
+ fn test_gpio_translation() {
+ let fake_node = json!({
+ "test-gpios": [[0xface, 0x1, 0x2, 0xbeef, 0x3, 0x4, 0x5]],
+ });
+ let lookup = FakeLookup::new()
+ .with_phandle(0xface, "#gpio-cells", 2)
+ .with_phandle(0xbeef, "#gpio-cells", 3);
+
+ let fixup = GpioFixup::new("", fake_node.as_object().unwrap(), JsonPath::new(), &lookup)
+ .expect("valid dt object")
+ .expect("schema applies")
+ .fixup(&lookup)
+ .expect("fixup ok");
+
+ assert_eq!(
+ Into::<serde_json::Value>::into(fixup),
+ json!({
+ "test-gpios": [[0xface, 0x1, 0x2], [0xbeef, 0x3, 0x4, 0x5]],
+ })
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/interrupts.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/interrupts.rs
new file mode 100644
index 0000000..fe6b28d
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/interrupts.rs
@@ -0,0 +1,210 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::VecDeque;
+
+use crate::{devicetree::types::PropertyTypeLookup, path::JsonPath};
+
+use super::{utils::get_cells_property_from_json, DevicetreeFixup, DevicetreeFixupError};
+
+pub struct InterruptFixup {
+ value: serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+}
+
+/// Convert a matrix that looks like this:
+/// [[<values>]]
+/// into
+/// [<values>]
+fn flatten_matrix_with_one_row(val: &serde_json::Value) -> Option<&Vec<serde_json::Value>> {
+ val.as_array()
+ .and_then(|v| if v.len() == 1 { v.get(0) } else { None })
+ .and_then(|v| v.as_array())
+}
+
+impl DevicetreeFixup for InterruptFixup {
+ fn new(
+ _nodename: &str,
+ node: &serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+ _type_lookup: &dyn PropertyTypeLookup,
+ ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+ if node.iter().any(|(k, v)| {
+ // interrupts and interrupt-map could either be a bytes array (i.e. a 1D array),
+ // or a uint32 matrix (an array of arrays).
+ // a byte array is OK, but this fixup does not apply to byte arrays so we ensure that
+ // the property is a matrix.
+ (k == "interrupts" || k == "interrupt-map") && flatten_matrix_with_one_row(v).is_some()
+ }) {
+ Ok(Some(InterruptFixup {
+ value: node.clone(),
+ path,
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn fixup(
+ mut self,
+ lookup: &dyn super::DevicetreeLookup,
+ ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+ let interrupt_cells = if let Some(serde_json::Value::Array(parent)) = self
+ .value
+ .get("interrupt-parent")
+ .and_then(|v| v.as_array())
+ .and_then(|v| v.first())
+ {
+ let phandle = parent.first().and_then(|v| v.as_u64()).ok_or(
+ DevicetreeFixupError::UnexpectedPropertyFormat(
+ self.path.extend("interrupt-parent"),
+ parent.clone().into(),
+ ),
+ )?;
+
+ lookup.get_cells_size(
+ self.path.extend("interrupt-parent"),
+ phandle.try_into().unwrap(),
+ "#interrupt-cells",
+ )?
+ } else {
+ match lookup.get_prop_from_parents("interrupt-parent")? {
+ Some(phandle) => {
+ lookup.get_cells_size(self.path.clone(), phandle, "#interrupt-cells")?
+ }
+ None => 1,
+ }
+ };
+
+ if let Some(interrupts) = self
+ .value
+ .get("interrupts")
+ .and_then(flatten_matrix_with_one_row)
+ {
+ let new_interrupts = interrupts
+ .chunks(interrupt_cells as usize)
+ .map(|v| v.to_vec())
+ .collect::<Vec<Vec<serde_json::Value>>>();
+ self.value.insert(
+ "interrupts".to_owned(),
+ serde_json::to_value(new_interrupts)?,
+ );
+ }
+
+ if let Some(map) = self
+ .value
+ .get("interrupt-map")
+ .and_then(flatten_matrix_with_one_row)
+ {
+ let my_path = self.path.extend("interrupt-map");
+ // interrupt-map is of the format
+ // <child-unit-address> <child-interrupt-specifier> <interrupt-parent> <parent-unit-address> <parent-interrupt-specifier>
+ let mut map = map
+ .iter()
+ .map(|v| v.as_u64())
+ .collect::<Option<VecDeque<u64>>>()
+ .ok_or(DevicetreeFixupError::UnexpectedPropertyFormat(
+ my_path.clone(),
+ map.clone().into(),
+ ))?;
+
+ let interrupt_cells =
+ get_cells_property_from_json(&self.value, &self.path, "#interrupt-cells")? as usize;
+ // Note that according to the spec this could actually come from the parent node,
+ // but in practice it appears to always come from our node.
+ let address_cells =
+ get_cells_property_from_json(&self.value, &self.path, "#address-cells")? as usize;
+ let mut ret = vec![];
+ while !map.is_empty() {
+ let phandle = map[interrupt_cells + address_cells].try_into().unwrap();
+ let parent_icells =
+ lookup.get_cells_size(my_path.clone(), phandle, "#interrupt-cells")? as usize;
+ let parent_acells =
+ match lookup.get_cells_size(my_path.clone(), phandle, "#address-cells") {
+ Ok(v) => v as usize,
+ Err(super::PhandleError::NodeHadNoProperty(..)) => 0,
+ Err(e) => return Err(e.into()),
+ };
+
+ let cells = interrupt_cells + address_cells + 1 + parent_acells + parent_icells;
+ ret.push(map.drain(0..cells).collect::<Vec<_>>());
+ }
+
+ self.value
+ .insert("interrupt-map".to_owned(), serde_json::to_value(ret)?);
+ }
+
+ Ok(self.value)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use crate::devicetree::fixups::utils::for_tests::FakeLookup;
+
+ use super::*;
+ #[test]
+ fn test_interrupts() {
+ let value = json!({
+ "interrupt-parent": [[0xcafe]],
+ "interrupts": [[0xa, 0xb, 0xc, 0xd, 0xe, 0xf]],
+ });
+ let lookup = FakeLookup::new().with_phandle(0xcafe, "#interrupt-cells", 3);
+
+ let result = InterruptFixup::new("", value.as_object().unwrap(), JsonPath::new(), &lookup)
+ .expect("node OK")
+ .expect("fixup applies")
+ .fixup(&lookup)
+ .expect("fixup ok");
+
+ assert_eq!(
+ serde_json::Value::from(result),
+ json!({
+ "interrupt-parent": [[0xcafe]],
+ "interrupts": [[0xa, 0xb, 0xc], [0xd, 0xe, 0xf]],
+ })
+ )
+ }
+
+ #[test]
+ fn test_interrupt_map() {
+ let value = json!({
+ "#interrupt-cells": [[1]],
+ "#address-cells": [[1]],
+ "interrupt-map": [[
+ 0x0, 0x0, 0xfeed, 0xabc, 0xdef,
+ 0x1, 0x0, 0xf00d, 0xaa, 0xbb, 0xcc,
+ 0x1, 0x2, 0xcafe, 0xaa,
+ ]],
+ });
+
+ let lookup = FakeLookup::new()
+ .with_phandle(0xfeed, "#interrupt-cells", 1)
+ .with_phandle(0xfeed, "#address-cells", 1)
+ .with_phandle(0xf00d, "#interrupt-cells", 2)
+ .with_phandle(0xf00d, "#address-cells", 1)
+ .with_phandle(0xcafe, "#interrupt-cells", 1);
+
+ let result = InterruptFixup::new("", value.as_object().unwrap(), JsonPath::new(), &lookup)
+ .expect("node OK")
+ .expect("fixup applies")
+ .fixup(&lookup)
+ .expect("fixup ok");
+
+ assert_eq!(
+ serde_json::Value::from(result),
+ json!({
+ "#interrupt-cells": [[1]],
+ "#address-cells": [[1]],
+ "interrupt-map": [
+ [0x0, 0x0, 0xfeed, 0xabc, 0xdef],
+ [0x1, 0x0, 0xf00d, 0xaa, 0xbb, 0xcc],
+ [0x1, 0x2, 0xcafe, 0xaa],
+ ],
+ })
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/phandle.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/phandle.rs
new file mode 100644
index 0000000..fc8e143
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/phandle.rs
@@ -0,0 +1,154 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use crate::{
+ devicetree::types::PropertyTypeLookup, path::JsonPath, validator::property_type::PropertyType,
+};
+
+use super::{utils::PhandleIterator, DevicetreeFixup, PhandleError};
+
+pub struct PhandleFixup {
+ node: serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+ keys_to_visit: Vec<String>,
+}
+
+fn needs_fixup(lookup: &dyn PropertyTypeLookup, key: &str, value: &serde_json::Value) -> bool {
+ let types = lookup.get_property_type(key);
+ #[allow(clippy::if_same_then_else)]
+ if !types.contains(&PropertyType::PhandleArray) {
+ false
+ } else if lookup
+ .get_property_dimensions(key)
+ .map(|v| v.is_fixed())
+ .unwrap_or(false)
+ {
+ // Fixed dimensions, so skip.
+ false
+ } else {
+ // If this has already been fixed up, skip it.
+ !value
+ .as_array()
+ .map(|v| if v.len() == 1 { !v[0].is_array() } else { true })
+ .unwrap_or(true)
+ }
+}
+
+impl DevicetreeFixup for PhandleFixup {
+ fn new(
+ _nodename: &str,
+ node: &serde_json::Map<String, serde_json::Value>,
+ path: crate::path::JsonPath,
+ type_lookup: &dyn PropertyTypeLookup,
+ ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+ let keys_to_visit: Vec<String> = node
+ .iter()
+ .filter(|(k, v)| needs_fixup(type_lookup, k, v))
+ .map(|(k, _)| k.clone())
+ .collect();
+ if !keys_to_visit.is_empty() {
+ Ok(Some(PhandleFixup {
+ node: node.clone(),
+ path,
+ keys_to_visit,
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn fixup(
+ mut self,
+ lookup: &dyn super::DevicetreeLookup,
+ ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+ for key in self.keys_to_visit.into_iter() {
+ let value = self.node.get(&key).unwrap().as_array().unwrap()[0]
+ .as_array()
+ .unwrap();
+ let iter = PhandleIterator::new(value, lookup, self.path.extend(&key), &key);
+
+ let translated = iter.collect::<Result<Vec<_>, _>>();
+ match translated {
+ Ok(value) => self.node.insert(key.clone(), serde_json::to_value(value)?),
+ Err(PhandleError::NodeHadNoProperty(..)) => continue,
+ Err(e) => return Err(e.into()),
+ };
+ }
+ Ok(self.node)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use crate::{devicetree::fixups::utils::for_tests::FakeLookup, path::JsonPath};
+
+ use super::*;
+ #[test]
+ fn test_simple_phandle_fixup() {
+ let node = json!({
+ "fudges": [[
+ 0xcafe, 0x1, 0x2, 0x3,
+ 0xfeed, 0x1,
+ 0xd00d, 0x9, 0xa,
+ ]]
+ });
+
+ let lookup = FakeLookup::new()
+ .with_phandle(0xcafe, "#fudge-cells", 3)
+ .with_phandle(0xfeed, "#fudge-cells", 1)
+ .with_phandle(0xd00d, "#fudge-cells", 2)
+ .with_prop_type(
+ "fudges",
+ &[PropertyType::PhandleArray],
+ Some([[1, 0], [1, 0]].into()),
+ );
+
+ let result = PhandleFixup::new("", node.as_object().unwrap(), JsonPath::new(), &lookup)
+ .expect("node ok")
+ .expect("fixup applies")
+ .fixup(&lookup)
+ .expect("fixup ok");
+
+ assert_eq!(
+ serde_json::Value::from(result),
+ json!({
+ "fudges": [
+ [0xcafe, 0x1, 0x2, 0x3],
+ [0xfeed, 0x1],
+ [0xd00d, 0x9, 0xa],
+ ]
+
+ })
+ )
+ }
+
+ #[test]
+ fn test_interconnects_fixup() {
+ let node = json!({
+ "interconnects": [[
+ 0xcafe, 0x1, 0x2, 0x3,
+ 0xfeed, 0x1,
+ ]]
+ });
+
+ let lookup = FakeLookup::new()
+ .with_phandle(0xcafe, "#interconnect-cells", 3)
+ .with_phandle(0xfeed, "#interconnect-cells", 1)
+ .with_prop_type(
+ "interconnects",
+ &[PropertyType::PhandleArray],
+ Some([[1, 0], [1, 0]].into()),
+ );
+
+ let result = PhandleFixup::new("", node.as_object().unwrap(), JsonPath::new(), &lookup)
+ .expect("node ok")
+ .expect("fixup applies")
+ .fixup(&lookup)
+ .expect("fixup ok");
+
+ assert_eq!(serde_json::Value::from(result), node);
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/utils.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/utils.rs
new file mode 100644
index 0000000..60ba45c
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/utils.rs
@@ -0,0 +1,248 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::VecDeque;
+
+use crate::path::JsonPath;
+
+use super::{DevicetreeFixupError, DevicetreeLookup};
+
+#[derive(thiserror::Error, Debug)]
+pub enum PhandleError {
+ #[error("Invalid value in phandle array at {0}")]
+ InvalidValue(JsonPath),
+ #[error("node referenced by phandle ({1}) had no {0} value")]
+ NodeHadNoProperty(String, JsonPath),
+ #[error("phandle {0:x} is not valid in property {1}")]
+ InvalidPhandle(u32, JsonPath),
+}
+
+/// A |PhandleIterator| takes an array of integer values of the format
+/// [<phandle> <cell>...]... and uses a provided |propname| to determine
+/// the number of cells each phandle expects as arguments.
+/// For instance, take this simple example:
+/// ```
+/// gpio1 {
+/// #gpio-cells = <2>;
+/// }
+///
+/// gpio2 {
+/// #gpio-cells = <1>;
+/// }
+///
+/// my-gpios = <&gpio1 2 3>, <&gpio2 1>
+/// ```
+/// PhandleIterator over "my-gpios" with propname="#gpio-cells" would yield:
+/// [[x, 2, 3], [y, 1]] where x and y are the phandles of gpio1 and gpio2 respectively.
+pub struct PhandleIterator<'a> {
+ array: &'a [serde_json::Value],
+ lookup: &'a dyn DevicetreeLookup,
+ propname: &'a str,
+ path: JsonPath,
+ in_iter: bool,
+}
+
+impl<'a> PhandleIterator<'a> {
+ /// Create a new PhandleIterator.
+ /// |array|: slice of values in the phandle array.
+ /// |lookup|: helper used to lookup phandles in the tree.
+ /// |path|: path (including property name) to the property we are resolving.
+ /// |propname|: name of the property we are resolving.
+ pub fn new(
+ array: &'a [serde_json::Value],
+ lookup: &'a dyn DevicetreeLookup,
+ path: JsonPath,
+ propname: &'a str,
+ ) -> Self {
+ PhandleIterator {
+ array,
+ lookup,
+ propname,
+ path,
+ in_iter: false,
+ }
+ }
+}
+
+impl<'a> Iterator for PhandleIterator<'a> {
+ type Item = Result<VecDeque<u64>, PhandleError>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut iter = self.array.iter();
+ let phandle: u32 = match iter
+ .next()
+ .map(|v| v.as_u64().and_then(|v| v.try_into().ok()))
+ {
+ Some(Some(p)) => p,
+ Some(None) => return Some(Err(PhandleError::InvalidValue(self.path.clone()))),
+ None => return None,
+ };
+
+ let taken: Option<VecDeque<u64>> = if phandle == 0xffff_ffff {
+ // Calculate the next value.
+ iter.take_while(|cell| cell.as_u64() != Some(0xffff_ffff))
+ .map(|cell| cell.as_u64())
+ .collect()
+ } else {
+ let mut cells = match self.lookup.get_cells_size(
+ self.path.clone(),
+ phandle,
+ &self.lookup.get_cells_prop_for_prop(self.propname),
+ ) {
+ Ok(cells) => cells,
+ Err(e) => return Some(Err(e)),
+ };
+ if self.propname == "msi-ranges" {
+ cells += 1;
+ }
+ iter.take(cells as usize)
+ .map(|cell| cell.as_u64())
+ .collect()
+ };
+
+ match taken {
+ Some(mut ret) => {
+ ret.push_front(phandle.into());
+ self.array = &self.array[ret.len()..];
+ if self.propname == "interconnects" && !self.in_iter {
+ self.in_iter = true;
+ // Do it again for interconnects, because it expects
+ // two values at a time.
+ let next = self.next();
+ self.in_iter = false;
+ match next {
+ Some(Ok(v)) => ret.extend(v),
+ Some(Err(e)) => return Some(Err(e)),
+ None => {
+ if crate::strict_mode() {
+ return Some(Err(PhandleError::InvalidValue(self.path.clone())));
+ } else {
+ tracing::warn!("interconnects should have a pair of values!");
+ }
+ }
+ }
+ }
+ Some(Ok(ret))
+ }
+ None => Some(Err(PhandleError::InvalidValue(self.path.clone()))),
+ }
+ }
+}
+
+pub fn get_cells_property_from_json(
+ node: &serde_json::Map<String, serde_json::Value>,
+ path: &JsonPath,
+ name: &str,
+) -> Result<u64, DevicetreeFixupError> {
+ match node.get(name) {
+ Some(serde_json::Value::Array(array)) => {
+ if array.len() != 1
+ || !array[0]
+ .as_array()
+ .map(|v| v.len() == 1 && v[0].is_u64())
+ .unwrap_or(false)
+ {
+ return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ path.extend(name),
+ array.clone().into(),
+ ));
+ }
+
+ let value = array[0].as_array().unwrap()[0].as_u64().unwrap();
+ Ok(value)
+ }
+ Some(not_number) => Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+ path.extend(name),
+ not_number.clone(),
+ )),
+ None => Err(DevicetreeFixupError::MissingProperty(path.extend(name))),
+ }
+}
+
+#[cfg(test)]
+pub mod for_tests {
+ use std::collections::{BTreeSet, HashMap};
+
+ use crate::{
+ devicetree::{fixups::DevicetreeLookup, types::PropertyTypeLookup},
+ path::JsonPath,
+ validator::{dimension::Dimension, property_type::PropertyType},
+ };
+
+ use super::PhandleError;
+
+ pub struct FakeLookup {
+ cells_by_phandle: HashMap<(u32, String), u32>,
+ values_from_parents: HashMap<String, u32>,
+ type_info: HashMap<String, (BTreeSet<PropertyType>, Option<Dimension>)>,
+ }
+
+ impl FakeLookup {
+ pub fn new() -> Self {
+ FakeLookup {
+ cells_by_phandle: HashMap::new(),
+ values_from_parents: HashMap::new(),
+ type_info: HashMap::new(),
+ }
+ }
+
+ /// Registers |prop| for |phandle| to be returned by |get_cells_size|.
+ pub fn with_phandle(mut self, phandle: u32, prop: &str, cells: u32) -> Self {
+ self.cells_by_phandle
+ .insert((phandle, prop.to_owned()), cells);
+ self
+ }
+
+ /// Registers |prop| to be returned by |get_cells_size_parents|.
+ pub fn with_parent(mut self, prop: &str, cells: u32) -> Self {
+ self.values_from_parents.insert(prop.to_owned(), cells);
+ self
+ }
+
+ pub fn with_prop_type(
+ mut self,
+ prop: &str,
+ types: &[PropertyType],
+ dim: Option<Dimension>,
+ ) -> Self {
+ self.type_info
+ .insert(prop.to_owned(), (types.iter().copied().collect(), dim));
+ self
+ }
+ }
+
+ impl DevicetreeLookup for FakeLookup {
+ fn get_cells_size(
+ &self,
+ phandle_path: JsonPath,
+ phandle: u32,
+ cell_prop: &str,
+ ) -> Result<u32, PhandleError> {
+ self.cells_by_phandle
+ .get(&(phandle, cell_prop.to_owned()))
+ .copied()
+ .ok_or_else(|| PhandleError::NodeHadNoProperty(cell_prop.to_owned(), phandle_path))
+ }
+
+ fn get_prop_from_parents(&self, prop: &str) -> Result<Option<u32>, PhandleError> {
+ Ok(self.values_from_parents.get(prop).copied())
+ }
+ }
+
+ impl PropertyTypeLookup for FakeLookup {
+ fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType> {
+ self.type_info
+ .get(propname)
+ .map(|v| v.0.clone())
+ .unwrap_or_default()
+ }
+
+ fn get_property_dimensions(
+ &self,
+ propname: &str,
+ ) -> Option<crate::validator::dimension::Dimension> {
+ self.type_info.get(propname).and_then(|v| v.1)
+ }
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/parser.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/parser.rs
new file mode 100644
index 0000000..a8809c9
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/parser.rs
@@ -0,0 +1,212 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use super::{property::Property, Node};
+use byteorder::{BigEndian, ReadBytesExt};
+use std::{
+ ffi::CStr,
+ io::{BufRead, BufReader, Read, Seek, SeekFrom},
+ rc::Rc,
+};
+
+const DTB_MAGIC: u32 = 0xd00dfeed;
+const DTB_MIN_VERSION: u32 = 16;
+
+#[derive(Debug)]
+pub enum Token {
+ BeginNode(String),
+ EndNode,
+ Prop(Property),
+ Nop,
+ End,
+}
+
+pub struct Parser<R> {
+ reader: BufReader<R>,
+ // TODO(simonshields): validate against this.
+ #[allow(unused)]
+ struct_end: u32,
+
+ strings: Vec<u8>,
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum ParseError {
+ #[error("Invalid magic")]
+ InvalidMagic,
+ #[error("Version is not supported: {0}")]
+ UnsupportedVersion(u32),
+ #[error("Invalid string offset: {0}")]
+ InvalidStringOffset(usize),
+ #[error("String not null-terminated: {0}")]
+ StringNotTerminated(usize),
+ #[error("Invalid string")]
+ InvalidString(#[from] std::ffi::FromBytesWithNulError),
+ #[error("Invalid string encoding")]
+ InvalidStringEncoding(#[from] std::str::Utf8Error),
+ #[error("Invalid token at offset {0:x}: {1:x}")]
+ InvalidToken(u64, u32),
+ #[error("Unexpected token")]
+ UnexpectedToken(Token),
+ #[error("I/O error")]
+ IoError(#[from] std::io::Error),
+}
+
+impl<R: Read + Seek> Parser<R> {
+ pub fn new(reader: R) -> Result<Self, ParseError> {
+ let mut reader = BufReader::new(reader);
+ let magic = reader.read_u32::<BigEndian>()?;
+ if magic != DTB_MAGIC {
+ return Err(ParseError::InvalidMagic);
+ }
+
+ let _ = reader.read_u32::<BigEndian>()?; // totalsize
+
+ let off_dt_struct = reader.read_u32::<BigEndian>()?;
+ let off_dt_string = reader.read_u32::<BigEndian>()?;
+ let _ = reader.read_u32::<BigEndian>()?; // off_mem_rsvmap
+ let version = reader.read_u32::<BigEndian>()?;
+ let last_comp_version = reader.read_u32::<BigEndian>()?;
+ if version < DTB_MIN_VERSION || DTB_MIN_VERSION < last_comp_version {
+ return Err(ParseError::UnsupportedVersion(version));
+ }
+
+ let _ = reader.read_u32::<BigEndian>()?; // boot_cpuid_phys
+
+ let size_dt_string = reader.read_u32::<BigEndian>()?;
+ let size_dt_struct = reader.read_u32::<BigEndian>()?;
+
+ reader.seek(SeekFrom::Start(off_dt_string.into()))?;
+ let mut strings_vec: Vec<u8> = vec![0; size_dt_string.try_into().unwrap()];
+
+ reader.read_exact(strings_vec.as_mut_slice())?;
+
+ reader.seek(SeekFrom::Start(off_dt_struct.into()))?;
+
+ Ok(Parser {
+ reader,
+ struct_end: off_dt_struct + size_dt_struct,
+ strings: strings_vec,
+ })
+ }
+
+ fn offset(&mut self) -> Result<u64, std::io::Error> {
+ self.reader.seek(SeekFrom::Current(0))
+ }
+
+ fn string_at(&self, offset: usize) -> Result<String, ParseError> {
+ if offset > self.strings.len() {
+ return Err(ParseError::InvalidStringOffset(offset));
+ }
+
+ let slice = &self.strings[offset..];
+ let (byte_index, _) = slice
+ .iter()
+ .enumerate()
+ .find(|(_, &v)| v == 0)
+ .ok_or(ParseError::StringNotTerminated(offset))?;
+
+ let slice = &self.strings[offset..byte_index + offset + 1];
+ let c_string = CStr::from_bytes_with_nul(slice)?;
+
+ Ok(c_string.to_str()?.to_owned())
+ }
+
+ /// Align the parser to the next 4 byte boundary.
+ fn align(&mut self) -> Result<(), ParseError> {
+ let offset = self.offset()? as usize;
+ if offset % std::mem::size_of::<u32>() != 0 {
+ let adjust = std::mem::size_of::<u32>() - (offset % std::mem::size_of::<u32>());
+ self.reader
+ .seek(SeekFrom::Current(adjust.try_into().unwrap()))?;
+ }
+ Ok(())
+ }
+
+ fn parse_token(&mut self) -> Result<Token, ParseError> {
+ self.align()?;
+ let token = self.reader.read_u32::<BigEndian>()?;
+
+ let parsed = match token {
+ 0x1 => {
+ let mut buf: Vec<u8> = vec![];
+ self.reader.read_until(0, &mut buf)?;
+ if *buf.last().unwrap_or(&1) != 0 {
+ // TODO(simonshields): make a better error here.
+ return Err(ParseError::StringNotTerminated(0xffff));
+ }
+
+ let c_string = CStr::from_bytes_with_nul(&buf)?;
+ Token::BeginNode(c_string.to_str()?.to_owned())
+ }
+ 0x2 => Token::EndNode,
+ 0x3 => {
+ let len = self.reader.read_u32::<BigEndian>()?.try_into().unwrap();
+ let nameoff = self.reader.read_u32::<BigEndian>()?;
+ let key = self.string_at(nameoff.try_into().unwrap())?;
+ let mut value = vec![0; len];
+
+ self.reader.read_exact(value.as_mut_slice())?;
+
+ Token::Prop(Property { key, value })
+ }
+ 0x4 => Token::Nop,
+ 0x9 => Token::End,
+ t => {
+ return Err(ParseError::InvalidToken(
+ self.offset()? - std::mem::size_of::<u32>() as u64,
+ t,
+ ))
+ }
+ };
+
+ Ok(parsed)
+ }
+
+ fn parse_node(&mut self, name: String) -> Result<Rc<Node>, ParseError> {
+ let mut can_end = false;
+ let mut children = vec![];
+ let mut properties = vec![];
+ loop {
+ match self.parse_token()? {
+ Token::BeginNode(name) => {
+ children.push(self.parse_node(name)?);
+ can_end = true;
+ }
+ Token::EndNode => break,
+ Token::End => {
+ if can_end {
+ self.reader.seek(SeekFrom::Current(-4))?;
+ break;
+ } else {
+ return Err(ParseError::UnexpectedToken(Token::End));
+ }
+ }
+ Token::Prop(prop) => {
+ properties.push(prop);
+ can_end = false;
+ }
+ Token::Nop => {}
+ }
+ }
+
+ Ok(Rc::new(Node {
+ name,
+ children,
+ properties,
+ }))
+ }
+
+ pub fn parse(&mut self) -> Result<Rc<Node>, ParseError> {
+ loop {
+ match self.parse_token()? {
+ Token::BeginNode(name) => {
+ return self.parse_node(name);
+ }
+ Token::Nop => {}
+ t => return Err(ParseError::UnexpectedToken(t)),
+ }
+ }
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/property.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/property.rs
new file mode 100644
index 0000000..82a0ba8
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/property.rs
@@ -0,0 +1,404 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{
+ collections::BTreeSet,
+ ffi::{CStr, FromBytesWithNulError},
+};
+
+use byteorder::{BigEndian, ByteOrder};
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::property_type::PropertyType};
+
+use super::{fixups::DevicetreeFixupError, types::PropertyTypeLookup};
+
+#[derive(thiserror::Error, Debug)]
+pub enum DevicetreeJsonError {
+ #[error("String was not valid: {1}")]
+ InvalidString(#[source] FromBytesWithNulError, JsonPath),
+
+ #[error("String has non-printable characters")]
+ NonPrintableString(JsonPath),
+
+ #[error("Error serialising JSON value")]
+ JsonError(#[from] serde_json::Error),
+
+ #[error("Invalid unicode")]
+ Utf8Error(#[from] std::str::Utf8Error),
+
+ #[error("Error performing fixups")]
+ FixupError(#[from] DevicetreeFixupError),
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Property {
+ pub(super) key: String,
+ pub(super) value: Vec<u8>,
+}
+
+impl Property {
+ #[tracing::instrument(level = "info", skip_all, fields(property=self.key))]
+ pub fn value_json(
+ &self,
+ nodename: &str,
+ type_lookup: &dyn PropertyTypeLookup,
+ path: JsonPath,
+ ) -> Result<serde_json::Value, DevicetreeJsonError> {
+ if self.value.is_empty() {
+ return Ok(json!(true));
+ }
+
+ if nodename == "__fixups__" || nodename == "aliases" {
+ return self.as_json_string(path);
+ }
+
+ let mut types = type_lookup.get_property_type(&self.key);
+ types.remove(&PropertyType::Node);
+ if types.len() > 1 {
+ // If we have more than one possible type, try and reduce that set.
+ types = types
+ .iter()
+ .filter(|t| {
+ // Filter out types which |self.value| is too big for.
+ t.max_size()
+ .map(|max| self.value.len() <= max)
+ .unwrap_or(true)
+ })
+ .copied()
+ .collect::<BTreeSet<_>>();
+ }
+
+ tracing::trace!("Possible types: {:?}", types);
+
+ #[allow(clippy::comparison_chain)]
+ let chosen_type = if types.len() > 1 {
+ if types.contains(&PropertyType::String) || types.contains(&PropertyType::StringArray) {
+ // Try and treat it as a string.
+ if let Ok(str) = self.as_json_string(path.clone()) {
+ return Ok(str);
+ }
+
+ let string_types =
+ BTreeSet::from([PropertyType::String, PropertyType::StringArray]);
+ // There should only be one other type.
+ let difference = types.difference(&string_types).collect::<Vec<_>>();
+ if difference.len() == 1 {
+ difference.into_iter().next().copied()
+ } else {
+ tracing::warn!(
+ "Property should only have one type left, but have these types: {:?}",
+ difference
+ );
+ return self.as_json_bytes();
+ }
+ } else {
+ None
+ }
+ } else if types.len() == 1 {
+ types.pop_first()
+ } else {
+ None
+ };
+
+ let chosen_type = if let Some(ty) = chosen_type {
+ ty
+ } else {
+ if let Ok(value) = self.as_json_string(path.clone()) {
+ return Ok(value);
+ }
+
+ if self.value.len() % 4 == 0 {
+ PropertyType::Uint32Array
+ } else {
+ return self.as_json_bytes();
+ }
+ };
+ tracing::trace!("Chosen type: {:?}", chosen_type);
+
+ if chosen_type.is_string() {
+ return self.as_json_string(path);
+ }
+
+ if chosen_type == PropertyType::Flag {
+ assert!(
+ !self.value.is_empty(),
+ "If length is zero we should always return true above"
+ );
+ tracing::warn!("Property is a flag but has data!");
+ return self.as_json_bytes();
+ }
+
+ // This is slightly different than the upstream equivalent
+ // (see https://github.com/devicetree-org/dt-schema/blob/547c943ab55f4d0b44fd88e3c36c7f6fa49c6ae2/dtschema/dtb.py#L137)
+ // but I think it is functionally the same.
+ let bytes_per_element = match chosen_type.bytes_per_element() {
+ Some(bytes) => bytes,
+ None => {
+ tracing::warn!("Type {:?} has no bytes per element", chosen_type);
+ return self.as_json_bytes();
+ }
+ };
+
+ if self.value.len() % bytes_per_element != 0 {
+ tracing::warn!(
+ "Invalid size: {} (expected a multiple of {})",
+ self.value.len(),
+ bytes_per_element
+ );
+ return self.as_json_bytes();
+ }
+
+ let values_int = self
+ .value
+ .chunks_exact(bytes_per_element)
+ .map(|chunk| BigEndian::read_uint(chunk, bytes_per_element))
+ .collect::<Vec<_>>();
+
+ let dim = if chosen_type.is_matrix() {
+ type_lookup.get_property_dimensions(&self.key)
+ } else {
+ None
+ };
+
+ if let Some(dim) = dim {
+ let stride = dim.stride(values_int.len());
+
+ return Ok(serde_json::to_value(
+ values_int
+ .chunks(stride)
+ .map(Vec::from)
+ .collect::<Vec<Vec<u64>>>(),
+ )?);
+ } else {
+ Ok(serde_json::to_value([values_int])?)
+ }
+ }
+
+ fn as_json_bytes(&self) -> Result<serde_json::Value, DevicetreeJsonError> {
+ Ok(serde_json::to_value(self.value.clone())?)
+ }
+
+ fn as_json_string(&self, path: JsonPath) -> Result<serde_json::Value, DevicetreeJsonError> {
+ let slices = self
+ .value
+ .split_inclusive(|&byte| byte == 0)
+ .map(|v| {
+ CStr::from_bytes_with_nul(v)
+ .map_err(|e| DevicetreeJsonError::InvalidString(e, path.clone()))
+ .and_then(|v| match v.to_str() {
+ Ok(str) => {
+ if str.chars().any(char::is_control) {
+ Err(DevicetreeJsonError::NonPrintableString(path.clone()))
+ } else {
+ Ok(str)
+ }
+ }
+ Err(e) => Err(e.into()),
+ })
+ })
+ .collect::<Result<Vec<&str>, DevicetreeJsonError>>()?;
+
+ Ok(serde_json::to_value(slices)?)
+ }
+
+ pub fn key(&self) -> &String {
+ &self.key
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use crate::validator::dimension::Dimension;
+
+ use super::*;
+
+ fn make_prop(key: &str, value: &[u8]) -> Property {
+ Property {
+ key: key.to_owned(),
+ value: Vec::from(value),
+ }
+ }
+
+ struct FakeTypeLookup {
+ types: BTreeSet<PropertyType>,
+ dimensions: Option<Dimension>,
+ }
+ impl FakeTypeLookup {
+ fn empty() -> Self {
+ FakeTypeLookup {
+ types: BTreeSet::new(),
+ dimensions: None,
+ }
+ }
+
+ fn new(types: &[PropertyType], dim: Option<Dimension>) -> Self {
+ FakeTypeLookup {
+ types: types.iter().copied().collect(),
+ dimensions: dim,
+ }
+ }
+ }
+
+ impl PropertyTypeLookup for FakeTypeLookup {
+ fn get_property_type(&self, _propname: &str) -> BTreeSet<PropertyType> {
+ self.types.clone()
+ }
+
+ fn get_property_dimensions(
+ &self,
+ _propname: &str,
+ ) -> Option<crate::validator::dimension::Dimension> {
+ self.dimensions
+ }
+ }
+
+ #[test]
+ fn test_flag_property() {
+ let prop = make_prop("test", &[]);
+ assert_eq!(
+ prop.value_json("test", &FakeTypeLookup::empty(), JsonPath::new())
+ .expect("valid value"),
+ json!(true)
+ );
+ }
+
+ #[test]
+ fn test_no_types() {
+ let prop = make_prop("test", &[1, 2, 3]);
+ assert_eq!(
+ prop.value_json("test", &FakeTypeLookup::empty(), JsonPath::new())
+ .expect("valid value"),
+ json!([1, 2, 3])
+ )
+ }
+
+ #[test]
+ fn test_string_array() {
+ let prop = make_prop("test_string_array", &[b'h', b'i', 0, b'h', b'i', 0]);
+ let lookup = FakeTypeLookup::new(&[PropertyType::String], None);
+
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!(["hi", "hi"])
+ );
+ }
+
+ #[test]
+ fn test_single_string() {
+ let prop = make_prop("test_single_string", &[b'h', b'i', 0]);
+ let lookup = FakeTypeLookup::new(&[PropertyType::String], None);
+
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!(["hi"])
+ );
+ }
+
+ #[test]
+ fn test_string_ambiguous() {
+ let prop = make_prop(
+ "test_string_ambiguous",
+ &[0x04, 0x07, 0x08, 0x02, 0x03, 0x09, 0x04, 0x06],
+ );
+ let lookup = FakeTypeLookup::new(&[PropertyType::String, PropertyType::Uint32Array], None);
+
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!([[0x04070802, 0x03090406]])
+ );
+ }
+
+ #[test]
+ fn test_untyped_bytes() {
+ let prop = make_prop("untyped_bytes", &[0x04, 0x05]);
+ let lookup = FakeTypeLookup::new(&[], None);
+
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!([0x04, 0x05])
+ );
+ }
+
+ #[test]
+ fn test_explicit_flag_type_with_data() {
+ let prop = make_prop("explicit_flag_type", &[0x01, 0x02]);
+ let lookup = FakeTypeLookup::new(&[PropertyType::Flag], None);
+
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!([0x01, 0x02])
+ );
+ }
+
+ #[test]
+ fn test_u64_array() {
+ let prop = make_prop(
+ "u64_array",
+ &[
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, /* */
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ ],
+ );
+
+ let lookup = FakeTypeLookup::new(&[PropertyType::Uint64Array], None);
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!([[0xff, 0x1000000000000001_u64]])
+ );
+ }
+
+ #[test]
+ fn test_u32_matrix_simple() {
+ let prop = make_prop(
+ "u32_matrix",
+ &[
+ 0x00, 0x00, 0x00, 0x01, /* */
+ 0x00, 0x00, 0x00, 0x01, /* */
+ 0x00, 0x00, 0x00, 0x04, /* */
+ 0x00, 0x00, 0x00, 0x04,
+ ],
+ );
+
+ let lookup =
+ FakeTypeLookup::new(&[PropertyType::Uint32Matrix], Some([[1, 1], [2, 2]].into()));
+
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!([[1, 1], [4, 4]])
+ );
+ }
+
+ #[test]
+ fn test_u32_matrix_variable() {
+ let prop = make_prop(
+ "u32_matrix_variable",
+ &[
+ 0x00, 0x00, 0x00, 0x01, /* */
+ 0x00, 0x00, 0x00, 0x01, /* */
+ 0x00, 0x00, 0x00, 0x04, /* */
+ 0x00, 0x00, 0x00, 0x04, /* */
+ 0x00, 0x00, 0x00, 0x05, /* */
+ 0x00, 0x00, 0x00, 0x06, /* */
+ ],
+ );
+
+ let lookup =
+ FakeTypeLookup::new(&[PropertyType::Uint32Matrix], Some([[3, 3], [1, 2]].into()));
+
+ assert_eq!(
+ prop.value_json("test", &lookup, JsonPath::new())
+ .expect("valid value"),
+ json!([[1, 1, 4], [4, 5, 6]])
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/types.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/types.rs
new file mode 100644
index 0000000..94c8865
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/types.rs
@@ -0,0 +1,14 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::BTreeSet;
+
+use crate::validator::{dimension::Dimension, property_type::PropertyType};
+
+pub trait PropertyTypeLookup {
+ fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType>;
+
+ fn get_property_dimensions(&self, propname: &str) -> Option<Dimension>;
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/util.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/util.rs
new file mode 100644
index 0000000..a266c20
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/util.rs
@@ -0,0 +1,79 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{collections::VecDeque, rc::Rc};
+
+use byteorder::{BigEndian, ByteOrder};
+
+use crate::path::JsonPath;
+
+use super::{
+ fixups::{DevicetreeLookup, PhandleError},
+ Devicetree, Node,
+};
+
+#[derive(Clone)]
+pub struct NodeAncestry<'a> {
+ devicetree: &'a Devicetree,
+ nodes: VecDeque<Rc<Node>>,
+}
+
+impl<'a> NodeAncestry<'a> {
+ pub fn new(devicetree: &'a Devicetree) -> Self {
+ NodeAncestry {
+ devicetree,
+ nodes: VecDeque::new(),
+ }
+ }
+
+ pub fn visit(&self, node: Rc<Node>) -> Self {
+ let mut new = self.clone();
+ new.nodes.push_back(node);
+ new
+ }
+
+ pub fn history(&self) -> impl Iterator<Item = &Rc<Node>> {
+ self.nodes.iter().rev()
+ }
+}
+
+impl DevicetreeLookup for NodeAncestry<'_> {
+ fn get_cells_size(
+ &self,
+ phandle_path: JsonPath,
+ phandle: u32,
+ cell_prop: &str,
+ ) -> Result<u32, PhandleError> {
+ if phandle == 0 {
+ // :(
+ return Ok(1);
+ }
+ // Resolve the phandle
+ let (node, node_path) = self
+ .devicetree
+ .by_phandle(phandle as usize)
+ .ok_or(PhandleError::InvalidPhandle(phandle, phandle_path))?;
+
+ let prop = node
+ .properties()
+ .iter()
+ .find(|v| v.key() == cell_prop)
+ .ok_or_else(|| {
+ PhandleError::NodeHadNoProperty(cell_prop.to_owned(), node_path.clone())
+ })?;
+
+ let value = BigEndian::read_u32(&prop.value);
+ Ok(value)
+ }
+
+ fn get_prop_from_parents(&self, prop: &str) -> Result<Option<u32>, PhandleError> {
+ for node in self.history() {
+ if let Some(value) = node.properties.iter().find(|k| k.key == prop) {
+ return Ok(Some(BigEndian::read_u32(&value.value)));
+ }
+ }
+
+ Ok(None)
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/logging.rs b/third_party/rust_crates/forks/dt-schema/src/logging.rs
new file mode 100644
index 0000000..27cd44b
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/logging.rs
@@ -0,0 +1,51 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use tracing::{subscriber::Interest, Subscriber};
+use tracing_subscriber::Layer;
+
+pub struct LoggingMetadata {
+ prefixes: Vec<String>,
+}
+
+impl LoggingMetadata {
+ pub fn new(prefixes: Vec<String>) -> Self {
+ LoggingMetadata { prefixes }
+ }
+
+ fn is_interesting(&self, value: &str) -> bool {
+ for prefix in self.prefixes.iter() {
+ if prefix.ends_with('$') && &prefix[..prefix.len() - 1] == value {
+ return true;
+ }
+ if value.starts_with(prefix) {
+ return true;
+ }
+ }
+ false
+ }
+}
+
+impl<S> Layer<S> for LoggingMetadata
+where
+ S: Subscriber,
+{
+ fn register_callsite(
+ &self,
+ metadata: &'static tracing::Metadata<'static>,
+ ) -> tracing::subscriber::Interest {
+ if self.prefixes.is_empty() {
+ return Interest::always();
+ }
+ if let Some(module) = metadata.module_path() {
+ if self.is_interesting(module) {
+ Interest::always()
+ } else {
+ Interest::never()
+ }
+ } else {
+ Interest::never()
+ }
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/main.rs b/third_party/rust_crates/forks/dt-schema/src/main.rs
new file mode 100644
index 0000000..1fa179f
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/main.rs
@@ -0,0 +1,189 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::BTreeSet;
+
+use anyhow::{anyhow, Context, Error};
+use argh::FromArgs;
+use logging::LoggingMetadata;
+use path::JsonPath;
+use tracing::metadata::LevelFilter;
+use tracing_subscriber::{prelude::*, Layer, Registry};
+
+mod devicetree;
+mod logging;
+mod parallel;
+mod path;
+#[cfg(test)]
+mod test_util;
+mod validator;
+
+/// STRICT_MODE determines whether or not validation occurs strictly.
+/// This MUST NOT be modified outside of |main()|.
+static mut STRICT_MODE: bool = true;
+
+pub fn strict_mode() -> bool {
+ // Safe because we only set strict mode at the start of program execution.
+ unsafe { STRICT_MODE }
+}
+
+#[derive(FromArgs)]
+/// devicetree schema validation tool.
+struct Args {
+ #[argh(subcommand)]
+ command: Subcommand,
+
+ #[argh(switch, short = 'r')]
+ /// perform relaxed validation of devicetree.
+ /// this is needed for compatibility with some Linux dtbs.
+ relaxed: bool,
+
+ #[argh(switch, short = 'v')]
+ /// be verbose.
+ verbose: bool,
+
+ #[argh(option)]
+ /// list of prefixes to output log messages from. By default all log messages will be output.
+ log_filter: Vec<String>,
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand)]
+enum Subcommand {
+ Validate(ValidateArgs),
+ DumpDtb(DumpDtbArgs),
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "validate")]
+/// Compile many devicetree schemas into one.
+struct ValidateArgs {
+ #[argh(option, short = 'd')]
+ /// device tree blob file
+ dtb: String,
+ #[argh(positional)]
+ /// files to compile
+ schemas: Vec<String>,
+
+ #[argh(option)]
+ /// for debugging, path to file to dump generated types to.
+ debug_dump_types: Option<String>,
+
+ #[argh(option)]
+ /// for debugging, path to jump json DTB to.
+ debug_dump_dtb: Option<String>,
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "dump")]
+/// Dump device tree.
+struct DumpDtbArgs {
+ #[argh(positional)]
+ /// device tree blob file
+ dtb: String,
+}
+
+fn main() {
+ let args: Args = argh::from_env();
+
+ if args.relaxed {
+ unsafe {
+ // Safe because there is no other code running yet.
+ STRICT_MODE = false;
+ }
+ }
+
+ let filter_layer = LoggingMetadata::new(args.log_filter);
+
+ let fmt_layer = tracing_subscriber::fmt::layer()
+ .with_line_number(true)
+ .with_filter(if args.verbose {
+ LevelFilter::TRACE
+ } else {
+ LevelFilter::INFO
+ })
+ .boxed();
+ let subscriber = Registry::default().with(fmt_layer).with(filter_layer);
+
+ tracing::subscriber::set_global_default(subscriber).unwrap();
+ match args.command {
+ Subcommand::Validate(validate) => match do_validate(validate) {
+ Ok(()) => println!("Success."),
+ Err(e) => println!("Error: {:?}", e),
+ },
+ Subcommand::DumpDtb(args) => println!(
+ "{:?}",
+ devicetree::Devicetree::from_reader(std::fs::File::open(args.dtb).unwrap())
+ ),
+ };
+}
+
+fn validate_subtree(
+ start: &serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+ validator: &validator::Validator,
+) -> Result<(), BTreeSet<JsonPath>> {
+ let failed = !validator.validate(&start.clone().into(), path.clone());
+ let mut errors = BTreeSet::new();
+ for (k, v) in start
+ .iter()
+ .filter_map(|(k, v)| v.as_object().map(|v| (k, v)))
+ {
+ match validate_subtree(v, path.extend(k), validator) {
+ Ok(()) => {}
+ Err(new_errors) => {
+ errors.extend(new_errors.into_iter());
+ }
+ }
+ }
+
+ if failed {
+ errors.insert(path);
+ Err(errors)
+ } else if !errors.is_empty() {
+ Err(errors)
+ } else {
+ Ok(())
+ }
+}
+
+fn do_validate(args: ValidateArgs) -> Result<(), Error> {
+ let validator =
+ validator::Validator::new_with_schemas(&args.schemas).context("Loading schemas")?;
+
+ if let Some(dest) = args.debug_dump_types {
+ let out = std::fs::File::create(dest).context("Opening file")?;
+ validator
+ .dump_properties(out)
+ .context("dumping properties")?;
+ }
+
+ let devicetree = devicetree::Devicetree::from_reader(
+ std::fs::File::open(args.dtb.clone())
+ .with_context(|| format!("Opening file {}", args.dtb))?,
+ )
+ .context("parsing device tree blob")?;
+
+ let json = devicetree.as_json(&validator)?;
+
+ if let Some(dest) = args.debug_dump_dtb {
+ let out = std::fs::File::create(dest).context("Creating dtb debug file")?;
+ serde_json::to_writer_pretty(out, &json).context("Writing dtb debug file")?;
+ }
+
+ match validate_subtree(json.as_object().unwrap(), JsonPath::new(), &validator) {
+ Ok(()) => Ok(()),
+ Err(error_locations) => {
+ let mut paths_string: Vec<String> =
+ error_locations.into_iter().map(|s| s.to_string()).collect();
+ paths_string.sort();
+ tracing::error!(
+ "Errors were encountered in the following paths: '{}'",
+ paths_string.join("', '")
+ );
+
+ Err(anyhow!("Failed to validate device tree"))
+ }
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/parallel.rs b/third_party/rust_crates/forks/dt-schema/src/parallel.rs
new file mode 100644
index 0000000..38c60ee
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/parallel.rs
@@ -0,0 +1,150 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{
+ io::Write,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc, Condvar, Mutex,
+ },
+ time::{Duration, Instant},
+};
+
+struct SharedMutableState<I, E> {
+ results: Vec<Result<Vec<I>, E>>,
+ abort: bool,
+}
+
+struct SharedState<I, E> {
+ mutable: Mutex<SharedMutableState<I, E>>,
+ abort_cv: Condvar,
+ work_done: AtomicUsize,
+}
+
+impl<I, E> SharedState<I, E> {
+ fn new() -> Arc<Self> {
+ Arc::new(SharedState {
+ mutable: Mutex::new(SharedMutableState {
+ results: vec![],
+ abort: false,
+ }),
+ abort_cv: Condvar::new(),
+ work_done: AtomicUsize::new(0),
+ })
+ }
+
+ fn add_result(&self, res: Result<Vec<I>, E>) {
+ let mut mutable = self.mutable.lock().unwrap();
+ if res.is_err() {
+ mutable.abort = true;
+ }
+ mutable.results.push(res);
+ self.abort_cv.notify_all();
+ }
+
+ fn did_work(&self) {
+ self.work_done.fetch_add(1, Ordering::Relaxed);
+ }
+
+ fn wait_for_finish(&self) -> Result<usize, ()> {
+ let lock = self.mutable.lock().unwrap();
+ let (lock, _) = self
+ .abort_cv
+ .wait_timeout(lock, Duration::from_millis(400))
+ .unwrap();
+ let should_abort = lock.abort;
+ if should_abort {
+ Err(())
+ } else {
+ Ok(self.work_done.load(Ordering::SeqCst))
+ }
+ }
+
+ fn get_results(self: Arc<Self>) -> Result<Vec<I>, E> {
+ let nested: Result<Vec<Vec<_>>, _> = Arc::try_unwrap(self)
+ .unwrap_or_else(|_| panic!("stray references")) // Safe because this is called after all other references are dealt with.
+ .mutable
+ .into_inner()
+ .unwrap()
+ .results
+ .into_iter()
+ .collect();
+ Ok(nested?.into_iter().flatten().collect())
+ }
+}
+
+pub fn parallel<'a, Arg, I, E>(
+ callback: Arc<dyn Fn(&'a Arg) -> Result<I, E> + Sync + Send + 'a>,
+ values: &'a [Arg],
+ progress_text: &str,
+) -> Result<Vec<I>, E>
+where
+ I: Send + Sync + 'a,
+ E: Send + Sync + 'a,
+ Arg: Sync,
+{
+ let threads = std::thread::available_parallelism()
+ .map(|v| v.get())
+ .unwrap_or(4);
+
+ let work_per_thread = values.len() / threads;
+ let state = SharedState::<I, E>::new();
+
+ let start = Instant::now();
+ std::thread::scope(|s| {
+ #[allow(clippy::needless_collect)]
+ let threads = values
+ .chunks(work_per_thread)
+ .map(|chunk| {
+ let f = Arc::clone(&callback);
+ let state = Arc::clone(&state);
+
+ s.spawn(move || {
+ let mut thread_results = vec![];
+ for item in chunk.iter() {
+ match f(item) {
+ Ok(v) => thread_results.push(v),
+ Err(e) => {
+ state.add_result(Err(e));
+ break;
+ }
+ }
+ state.did_work();
+ }
+ state.add_result(Ok(thread_results));
+ })
+ })
+ .collect::<Vec<_>>();
+
+ let mut should_abort = false;
+ while !should_abort {
+ should_abort = match state.wait_for_finish() {
+ Ok(progress) => {
+ print!(
+ "{}: {}/{}... ({:.02}s)\r",
+ progress_text,
+ progress,
+ values.len(),
+ start.elapsed().as_secs_f64()
+ );
+ let _ = std::io::stdout().flush();
+ progress == values.len()
+ }
+ Err(_) => true,
+ };
+ }
+
+ for thread in threads.into_iter() {
+ thread.join().unwrap();
+ }
+ });
+
+ println!(
+ "{}: done in {:.02}s ",
+ progress_text,
+ start.elapsed().as_secs_f64()
+ );
+
+ state.get_results()
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/path.rs b/third_party/rust_crates/forks/dt-schema/src/path.rs
new file mode 100644
index 0000000..f59a431
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/path.rs
@@ -0,0 +1,58 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{collections::VecDeque, fmt::Display};
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+/// Represents a path through a json object.
+pub struct JsonPath {
+ elements: VecDeque<String>,
+}
+
+impl JsonPath {
+ pub fn new() -> JsonPath {
+ JsonPath {
+ elements: VecDeque::new(),
+ }
+ }
+
+ pub fn extend(&self, elem: &str) -> Self {
+ let mut new = self.clone();
+ new.elements.push_back(elem.to_owned());
+ new
+ }
+
+ pub fn extend_index_only(&self, index: usize) -> Self {
+ let mut new = self.clone();
+ let back = new
+ .elements
+ .pop_back()
+ .unwrap_or_else(|| "/<root>".to_owned());
+ new.elements.push_back(format!("{}[{}]", back, index));
+ new
+ }
+
+ pub fn extend_array_index(&self, elem: &str, index: usize) -> Self {
+ let mut new = self.clone();
+ new.elements.push_back(format!("{}[{}]", elem, index));
+ new
+ }
+
+ pub fn back(&self) -> &str {
+ self.elements.back().map(|s| s.as_str()).unwrap_or("/")
+ }
+}
+
+impl Display for JsonPath {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if self.elements.is_empty() {
+ write!(f, "/")?;
+ }
+ for el in self.elements.iter() {
+ write!(f, "/{}", el)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/test_util.rs b/third_party/rust_crates/forks/dt-schema/src/test_util.rs
new file mode 100644
index 0000000..9526a28b
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/test_util.rs
@@ -0,0 +1,18 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use tracing::Level;
+
+#[allow(unused)]
+/// This is intended to help with debugging test failures.
+/// Call `crate::test_util::enable_all_logs()` at the start of your test to get full tracing output.
+pub fn enable_all_logs() {
+ tracing::subscriber::set_global_default(
+ tracing_subscriber::fmt()
+ .with_max_level(Level::TRACE)
+ .with_line_number(true)
+ .finish(),
+ )
+ .unwrap();
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator.rs b/third_party/rust_crates/forks/dt-schema/src/validator.rs
new file mode 100644
index 0000000..a9bb1fe
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator.rs
@@ -0,0 +1,296 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+mod array;
+mod compatible;
+pub mod dimension;
+mod error;
+mod fixups;
+pub mod property_type;
+mod property_type_info;
+mod resolver;
+mod schema;
+mod util;
+
+use serde::Serialize;
+use tracing::Level;
+use valico::json_schema::{SchemaVersion, Scope};
+
+use std::{
+ collections::{BTreeMap, BTreeSet, HashSet},
+ io::Write,
+ sync::Arc,
+};
+
+use fancy_regex::Regex;
+
+use crate::{devicetree::types::PropertyTypeLookup, parallel::parallel, path::JsonPath};
+
+use self::{
+ dimension::Dimension, error::ValidatorError, fixups::FixupError, property_type::PropertyType,
+ resolver::LocalOnlyResolver, schema::Schema,
+};
+
+#[derive(Debug, Serialize)]
+/// Note that |PartialEq| is only implemented with respect to the type and dimensions.
+pub struct GeneratedPropertyType {
+ #[serde(skip)]
+ id: HashSet<String>,
+ r#type: Option<PropertyType>,
+ #[serde(skip)]
+ regex: Option<Regex>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ dim: Option<Dimension>,
+}
+
+impl PartialEq for GeneratedPropertyType {
+ fn eq(&self, other: &Self) -> bool {
+ self.r#type == other.r#type && self.dim == other.dim
+ }
+}
+
+impl Eq for GeneratedPropertyType {
+ fn assert_receiver_is_total_eq(&self) {}
+}
+
+impl PartialOrd for GeneratedPropertyType {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ match self.r#type.partial_cmp(&other.r#type) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ self.dim.partial_cmp(&other.dim)
+ }
+}
+
+impl Ord for GeneratedPropertyType {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.partial_cmp(other).unwrap()
+ }
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum FileValidatorError {
+ #[error("validator error in file {1}: {0}")]
+ ValidatorError(ValidatorError, String),
+
+ #[error("fixup error in file {1}: {0}")]
+ FixupError(FixupError, String),
+}
+
+#[derive(Debug)]
+pub struct Validator {
+ schemas: Vec<Schema>,
+ /// Map of |property-name| to |possible-property-types|. Used when decoding the DTB.
+ property_types: BTreeMap<String, Vec<GeneratedPropertyType>>,
+ /// Map of |property-regex| to |possible-property-types|. Used when decoding the DTB.
+ pattern_property_types: BTreeMap<String, (Regex, Vec<GeneratedPropertyType>)>,
+ /// List of valid compatibles.
+ // TODO(simonshields): generate this and use it for making sure all nodes have schemas.
+ #[allow(unused)]
+ valid_compatibles: HashSet<String>,
+ /// Scope which schemas are compiled in to.
+ scope: Scope,
+}
+
+impl Validator {
+ pub fn new_with_schemas(paths: &[String]) -> Result<Self, FileValidatorError> {
+ let mut schemas = parallel(
+ Arc::new(|path: &String| -> Result<Schema, FileValidatorError> {
+ let file = std::fs::File::open(path)
+ .map_err(|v| FileValidatorError::ValidatorError(v.into(), path.clone()))?;
+
+ let pre_fixup = serde_yaml::from_reader(file)
+ .map_err(|v| FileValidatorError::ValidatorError(v.into(), path.clone()))?;
+
+ // Run fixup passes over schema before we parse it into an actual |Schema| object.
+ let value = fixups::schema_fixups::SchemaFixup::fixup(pre_fixup, path.clone())
+ .map_err(|v| FileValidatorError::FixupError(v, path.clone()))?;
+ Schema::from_value(value, path.clone())
+ .map_err(|v| FileValidatorError::ValidatorError(v, path.clone()))
+ }),
+ paths,
+ "loading schemas",
+ )?;
+
+ // Determine property types.
+ let mut property_types: BTreeMap<String, Vec<GeneratedPropertyType>> = BTreeMap::new();
+ let mut pattern_property_types: BTreeMap<String, (Regex, Vec<GeneratedPropertyType>)> =
+ BTreeMap::new();
+ for schema in schemas.iter_mut() {
+ let types = schema.generate_property_types(Some(schema), JsonPath::new());
+
+ let types = types
+ .map_err(|v| FileValidatorError::ValidatorError(v, schema.source_file().clone()))?;
+
+ // Distil types into the final output.
+ for (k, v) in types.into_iter() {
+ let ignore_untyped = |v: &GeneratedPropertyType| v.r#type.is_some();
+ if let Some(regex_item) = v.iter().find(|e| e.regex.is_some()) {
+ // We skip these overly broad property types because they
+ // cause properties to be wrongly detected as strings.
+ if k == "^[a-z][a-z0-9\\-]*$"
+ || k == "^[a-zA-Z][a-zA-Z0-9\\-_]{0,63}$"
+ || k == "^.*$"
+ || k == ".*"
+ {
+ continue;
+ }
+
+ match pattern_property_types.get_mut(&k) {
+ Some((_re, list)) => list.extend(v.into_iter().filter(ignore_untyped)),
+ None => {
+ pattern_property_types.insert(
+ k,
+ (
+ regex_item.regex.clone().unwrap(),
+ v.into_iter().filter(ignore_untyped).collect(),
+ ),
+ );
+ }
+ }
+ } else {
+ match property_types.get_mut(&k) {
+ Some(list) => list.extend(v.into_iter().filter(ignore_untyped)),
+ None => {
+ property_types
+ .insert(k, v.into_iter().filter(ignore_untyped).collect());
+ }
+ }
+ };
+ }
+ }
+
+ let mut scope = Scope::new().set_version(SchemaVersion::Draft2019_09);
+ let resolver = LocalOnlyResolver::new(&mut schemas.iter());
+ for schema in schemas.iter_mut() {
+ schema
+ .fixup_select_and_finalise(resolver.clone(), &mut scope)
+ .map_err(|e| {
+ FileValidatorError::ValidatorError(e, schema.spec_id().clone().unwrap())
+ })?;
+ }
+
+ Ok(Validator {
+ schemas,
+ property_types,
+ pattern_property_types,
+ valid_compatibles: HashSet::new(),
+ scope,
+ })
+ }
+
+ pub fn dump_properties(&self, out: impl Write) -> Result<(), ValidatorError> {
+ let props_for_dump: BTreeMap<_, _> =
+ self.property_types
+ .iter()
+ .map(|(k, v)| {
+ (
+ k.clone(),
+ BTreeSet::from_iter(v.iter().filter(|e| {
+ e.r#type.unwrap_or(PropertyType::Node) != PropertyType::Node
+ })),
+ )
+ })
+ .filter(|(_, v)| !v.is_empty())
+ .collect();
+
+ serde_yaml::to_writer(out, &props_for_dump)?;
+ Ok(())
+ }
+
+ pub fn validate(&self, value: &serde_json::Value, path: JsonPath) -> bool {
+ let schemas: Vec<_> = self
+ .schemas
+ .iter()
+ .filter(|s| s.applies(value, &self.scope))
+ .map(|s| (s, s.validate(value, &path.to_string(), &self.scope)))
+ .collect();
+
+ let disabled = value
+ .as_object()
+ .and_then(|o| o.get("status"))
+ .and_then(|v| v.as_array())
+ .map(|v| v.len() == 1 && v[0] == "disabled")
+ .unwrap_or(false);
+ let span = tracing::error_span!("validate", path = %path);
+ let _entered = span.enter();
+ let mut ok = true;
+ for (schema, status) in schemas {
+ let schema_span = tracing::error_span!("schema", file=%schema.source_file());
+ let _entered = schema_span.enter();
+ if let Some(e) = status.filter(|v| !v.is_strictly_valid()) {
+ let level = if !disabled {
+ ok = false;
+ tracing::error!("Validation failed:");
+ Level::ERROR
+ } else {
+ tracing::info!("Validation of disabled node failed (this may be expected):");
+ Level::INFO
+ };
+ for error in e.errors {
+ if level == Level::INFO {
+ tracing::event!(
+ Level::INFO,
+ "at {}: {} {}",
+ error.get_path(),
+ error.get_title(),
+ error.get_detail().unwrap_or(""),
+ );
+ } else {
+ tracing::event!(
+ Level::ERROR,
+ "at {}: {} {}",
+ error.get_path(),
+ error.get_title(),
+ error.get_detail().unwrap_or(""),
+ );
+ }
+ }
+ }
+ }
+
+ ok
+ }
+}
+
+impl PropertyTypeLookup for Validator {
+ fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType> {
+ let mut types: BTreeSet<PropertyType> = self
+ .property_types
+ .get(propname)
+ .map(|v| v.iter().filter_map(|e| e.r#type).collect())
+ .unwrap_or_default();
+
+ if types.is_empty() {
+ types.extend(
+ self.pattern_property_types
+ .iter()
+ .filter(|(_, (regex, _))| regex.is_match(propname).unwrap_or(false))
+ .flat_map(|(_, (_, ty))| ty.iter().filter_map(|e| e.r#type)),
+ );
+ }
+
+ types
+ }
+
+ fn get_property_dimensions(&self, propname: &str) -> Option<Dimension> {
+ if let Some(types) = self.property_types.get(propname) {
+ if let Some(dim) = types.iter().filter_map(|v| v.dim).next() {
+ return Some(dim);
+ }
+ }
+
+ self.pattern_property_types
+ .iter()
+ .filter_map(|(_, (regex, ty))| {
+ if regex.is_match(propname).unwrap_or(false) {
+ ty.iter().filter_map(|e| e.dim).next()
+ } else {
+ None
+ }
+ })
+ .next()
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/array.rs b/third_party/rust_crates/forks/dt-schema/src/validator/array.rs
new file mode 100644
index 0000000..91a67e7
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/array.rs
@@ -0,0 +1,179 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use fancy_regex::Regex;
+use serde::{Deserialize, Serialize};
+
+use super::util::{ContainsExt, IsSchema};
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(untagged)]
+/// Represents an "Items" declaration, which could be a bare list of items, or a dict with a min/max.
+/// This is only ever used in the context of |Array|, below.
+enum Items {
+ Array(Vec<serde_json::Value>),
+ Object(serde_json::Map<String, serde_json::Value>),
+}
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+/// Represents an array.
+pub struct Array {
+ items: Option<Items>,
+ min_items: Option<usize>,
+ max_items: Option<usize>,
+}
+
+impl Array {
+ /// Returns true if this |Array| represents an array of ints.
+ pub fn is_int_array_schema(&self, propname: &str) -> bool {
+ let unit_type_re = Regex::new("-(kBps|bits|percent|bp|m?hz|sec|ms|us|ns|ps|mm|nanoamp|(micro-)?ohms|micro(amp|watt)(-hours)?|milliwatt|microvolt|picofarads|(milli)?celsius|kelvin|kpascal)$").unwrap();
+ if unit_type_re.is_match(propname).unwrap_or(false) {
+ return true;
+ }
+ let int_array_re = Regex::new("int(8|16|32|64)-array").unwrap();
+ let map = match self.items {
+ Some(Items::Object(ref o)) => {
+ if let Some(all_of) = o.get("allOf").and_then(|v| v.as_array()) {
+ let mut ret = o;
+ for item in all_of.iter().filter_map(|v| v.as_object()) {
+ if let Some(path) = item.get("$ref").and_then(|v| v.as_str()) {
+ return int_array_re.is_match(path).unwrap_or(false);
+ }
+ if let Some(items) = item.get("items").and_then(|v| v.as_object()) {
+ ret = items;
+ }
+ }
+ ret
+ } else {
+ o
+ }
+ }
+ _ => return false,
+ };
+
+ map.is_int_schema()
+ }
+
+ /// Returns |true| if this type represents a uint32 matrix.
+ pub fn is_uint32_matrix(&self, propname: &str) -> bool {
+ if !self.is_matrix() {
+ false
+ } else {
+ let unit_type_re = Regex::new("-(kBps|bits|percent|bp|m?hz|sec|ms|us|ns|ps|mm|nanoamp|(micro-)?ohms|micro(amp|watt)(-hours)?|milliwatt|microvolt|picofarads|(milli)?celsius|kelvin|kpascal)$").unwrap();
+ unit_type_re.is_match(propname).unwrap_or(false)
+ }
+ }
+
+ /// Returns true if this array is a matrix.
+ pub fn is_matrix(&self) -> bool {
+ match &self.items {
+ Some(Items::Object(map)) => map.contains_any(&["items", "maxItems", "minItems"]),
+ Some(Items::Array(array)) => {
+ for item in array.iter().filter_map(|f| f.as_object()) {
+ if item.contains_any(&["items", "maxItems", "minItems"]) {
+ return true;
+ }
+ }
+ false
+ }
+ None => false,
+ }
+ }
+
+ /// If this array is a matrix, returns the inner dimension of the matrix.
+ pub fn get_child_dim(&self) -> Result<[usize; 2], serde_json::Error> {
+ let array: Array = match &self.items {
+ Some(Items::Array(vec)) => {
+ if vec.len() == 1 {
+ serde_json::from_value(vec[0].clone())?
+ } else {
+ return Ok([0, 0]);
+ }
+ }
+ Some(Items::Object(obj)) => serde_json::from_value(obj.clone().into())?,
+ None => return Ok([1, 0]),
+ };
+
+ Ok(array.get_dim())
+ }
+
+ /// Get the [minimum, maximum] length of this array.
+ pub fn get_dim(&self) -> [usize; 2] {
+ if let Some(Items::Array(ref items)) = self.items {
+ [self.min_items.unwrap_or(items.len()), items.len()]
+ } else {
+ [
+ self.min_items.unwrap_or(1),
+ self.max_items
+ .unwrap_or_else(|| self.min_items.unwrap_or(0)),
+ ]
+ }
+ }
+
+ /// Returns true if this array has an explicit |items| definition associated with it.
+ pub fn has_items(&self) -> bool {
+ self.items.is_some()
+ }
+
+ pub fn is_string_schema(&self) -> bool {
+ match self.items.as_ref() {
+ // TODO(simonshields): should this be less aggressive? To match the upstream code it should be
+ // array.iter().first().and_then(|v| v.as_object()).any(|v| v.is_string_schema())
+ Some(Items::Array(array)) => array
+ .iter()
+ .filter_map(|v| v.as_object())
+ .any(|v| v.is_string_schema()),
+ Some(Items::Object(map)) => map.is_string_schema(),
+ None => false,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use super::*;
+
+ fn items_with_len(len: usize) -> Items {
+ let mut vec = Vec::new();
+ vec.resize(len, json!(null));
+
+ Items::Array(vec)
+ }
+
+ #[test]
+ fn test_array_get_dim() {
+ assert_eq!(
+ Array {
+ items: None,
+ min_items: Some(2),
+ max_items: Some(3),
+ }
+ .get_dim(),
+ [2, 3]
+ );
+
+ assert_eq!(
+ Array {
+ items: Some(items_with_len(3)),
+ min_items: None,
+ max_items: None,
+ }
+ .get_dim(),
+ [3, 3]
+ );
+
+ assert_eq!(
+ Array {
+ items: Some(items_with_len(4)),
+ min_items: Some(2),
+ max_items: Some(3),
+ }
+ .get_dim(),
+ [2, 4]
+ )
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/compatible.rs b/third_party/rust_crates/forks/dt-schema/src/validator/compatible.rs
new file mode 100644
index 0000000..8032098
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/compatible.rs
@@ -0,0 +1,95 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::HashSet;
+
+fn generate_items<'a>(
+ value: &'a serde_json::Value,
+ key: &'static str,
+) -> Box<dyn Iterator<Item = &'a serde_json::Value> + 'a> {
+ match value {
+ serde_json::Value::Object(o) => Box::new(o.iter().flat_map(move |(k, v)| {
+ if k == key {
+ Box::new(Some(v).into_iter())
+ } else {
+ generate_items(v, key)
+ }
+ })),
+ serde_json::Value::Array(a) => Box::new(a.iter().flat_map(move |v| generate_items(v, key))),
+ _ => Box::new([].into_iter()),
+ }
+}
+
+/// Given |property_schema| which is the schema for a "compatible" property, returns a set
+/// of all possible compatibles the schema could accept.
+pub fn extract_node_compatibles(property_schema: &serde_json::Value) -> HashSet<String> {
+ let mut result = HashSet::new();
+
+ result.extend(
+ generate_items(property_schema, "enum")
+ .filter_map(|v| {
+ v.as_array().map(|array| {
+ array
+ .iter()
+ .filter_map(|v| v.as_str().map(|v| v.to_owned()))
+ })
+ })
+ .flatten(),
+ );
+ result.extend(
+ generate_items(property_schema, "const").filter_map(|v| v.as_str().map(|v| v.to_owned())),
+ );
+ result.extend(
+ generate_items(property_schema, "pattern").filter_map(|v| v.as_str().map(|v| v.to_owned())),
+ );
+
+ result
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use super::*;
+ #[test]
+ fn test_generate_items() {
+ let value = json!({
+ "array": [
+ {
+ "hello": "one",
+ },
+ "two",
+ "three",
+ ],
+ "oneOf": {
+ "hello": "there",
+ "ignore": "me",
+ }
+ });
+
+ let result = generate_items(&value, "hello").collect::<Vec<_>>();
+
+ assert_eq!(result, vec!["one", "there"]);
+ }
+
+ #[test]
+ fn test_extract_compatibles() {
+ let value = json!({
+ "oneOf": {
+ "enum": ["hello,there", "another-one"],
+ "const": "value",
+ "pattern": "^[oO]+h,regexp?$"
+ }
+ });
+
+ let mut result = extract_node_compatibles(&value)
+ .into_iter()
+ .collect::<Vec<_>>();
+ result.sort();
+ assert_eq!(
+ result,
+ vec!["^[oO]+h,regexp?$", "another-one", "hello,there", "value"]
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/dimension.rs b/third_party/rust_crates/forks/dt-schema/src/validator/dimension.rs
new file mode 100644
index 0000000..8e47353
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/dimension.rs
@@ -0,0 +1,162 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde::Serialize;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
+struct Range {
+ min: usize,
+ max: usize,
+}
+
+impl Range {
+ /// True if both min and max are zero.
+ fn is_zero(&self) -> bool {
+ self.min == 0 && self.max == 0
+ }
+
+ /// True if either value is not zero.
+ fn has_value(&self) -> bool {
+ self.min != 0 || self.max != 0
+ }
+
+ /// True if min == max.
+ fn is_fixed(&self) -> bool {
+ self.min == self.max
+ }
+
+ /// Return a range that encompasses |self| and |other|.
+ fn merge(&self, other: &Range) -> Range {
+ if self.is_zero() {
+ *other
+ } else if other.is_zero() {
+ *self
+ } else {
+ Range {
+ min: std::cmp::min(self.min, other.min),
+ max: std::cmp::max(self.max, other.max),
+ }
+ }
+ }
+}
+
+impl From<[usize; 2]> for Range {
+ fn from(value: [usize; 2]) -> Self {
+ Range {
+ min: value[0],
+ max: value[1],
+ }
+ }
+}
+
+impl From<Range> for [usize; 2] {
+ fn from(value: Range) -> Self {
+ [value.min, value.max]
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord, Default)]
+#[serde(from = "[[usize; 2]; 2]", into = "[[usize; 2]; 2]")]
+/// |Dimension| represents constraints on a matrix.
+pub struct Dimension {
+ /// Range of outer elements possible.
+ outer: Range,
+ /// Range of elements allowed in each inner element.
+ inner: Range,
+}
+
+impl From<[[usize; 2]; 2]> for Dimension {
+ fn from(value: [[usize; 2]; 2]) -> Self {
+ Dimension {
+ outer: value[0].into(),
+ inner: value[1].into(),
+ }
+ }
+}
+
+impl From<Dimension> for [[usize; 2]; 2] {
+ fn from(value: Dimension) -> Self {
+ [value.outer.into(), value.inner.into()]
+ }
+}
+
+impl Dimension {
+ /// Merge two dimensions into a third dimension.
+ pub fn merge(&self, other: Dimension) -> Dimension {
+ Dimension {
+ outer: self.outer.merge(&other.outer),
+ inner: self.inner.merge(&other.inner),
+ }
+ }
+
+ /// Given an array with dimensions 1xN, returns the stride
+ /// that should be used to turn it into array with dimensions matching this one.
+ /// TODO(simonshields): write tests.
+ pub fn stride(&self, array_length: usize) -> usize {
+ if !self.inner.is_zero() && self.inner.is_fixed() {
+ self.inner.max
+ } else if !self.outer.is_zero() && self.outer.is_fixed() {
+ if array_length % self.outer.max != 0 {
+ array_length
+ } else {
+ self.outer.max
+ }
+ } else if !self.inner.has_value() {
+ array_length
+ } else {
+ let mut matches = 0;
+ let mut ret = 0;
+ for dimension in self.inner.min..self.inner.max + 1 {
+ if array_length % dimension == 0 {
+ matches += 1;
+ ret = dimension;
+ }
+ }
+
+ if matches == 1 {
+ ret
+ } else {
+ array_length
+ }
+ }
+ }
+
+ pub fn is_fixed(&self) -> bool {
+ self.inner.is_fixed() || self.outer.is_fixed()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Dimension;
+ #[test]
+ fn test_dimension_merge() {
+ let a: Dimension = [[0, 0], [0, 0]].into();
+ let b: Dimension = [[1, 1], [2, 3]].into();
+ assert_eq!(a.merge(b), b);
+
+ let a: Dimension = [[1, 4], [5, 9]].into();
+ let b: Dimension = [[2, 5], [4, 7]].into();
+ assert_eq!(a.merge(b), [[1, 5], [4, 9]].into());
+ }
+
+ #[test]
+ fn test_stride_simple() {
+ let a: Dimension = [[1, 1], [4, 4]].into();
+ assert_eq!(a.stride(8), 4);
+
+ let b: Dimension = [[2, 2], [1, 2]].into();
+ assert_eq!(b.stride(4), 2);
+ assert_eq!(b.stride(3), 3);
+ }
+
+ #[test]
+ fn test_stride_variable() {
+ let a: Dimension = [[1, 2], [3, 8]].into();
+ assert_eq!(a.stride(10), 5);
+ assert_eq!(a.stride(8), 8);
+ assert_eq!(a.stride(7), 7);
+ assert_eq!(a.stride(6), 6);
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/error.rs b/third_party/rust_crates/forks/dt-schema/src/validator/error.rs
new file mode 100644
index 0000000..d84bf11
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/error.rs
@@ -0,0 +1,43 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use valico::json_schema::SchemaError;
+
+use crate::path::JsonPath;
+
+#[derive(thiserror::Error, Debug)]
+pub enum ValidatorError {
+ #[error("i/o error")]
+ IoError(#[from] std::io::Error),
+ #[error("serde yaml error")]
+ YamlError(#[from] serde_yaml::Error),
+ #[error("serde json error")]
+ JsonError(#[from] serde_json::Error),
+ #[error("invalid regular expression")]
+ RegexError(#[from] fancy_regex::Error),
+
+ #[error("invalid reference: {0}")]
+ InvalidReference(String),
+ #[error("unknown property type: {0}")]
+ UnknownPropType(String),
+
+ #[error("json schema error: {0}")]
+ SchemaError(#[from] SchemaError),
+
+ #[error("schema is missing key '{0}' at {1}")]
+ ExpectedKey(String, JsonPath),
+}
+
+/*
+impl<'a> From<jsonschema::error::ValidationError<'a>> for ValidatorError {
+ fn from(value: jsonschema::error::ValidationError<'a>) -> Self {
+ ValidatorError::SchemaError {
+ value: value.instance.into_owned(),
+ kind: value.kind,
+ instance_path: value.instance_path,
+ schema_path: value.schema_path,
+ }
+ }
+}
+*/
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups.rs
new file mode 100644
index 0000000..b1220e8
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups.rs
@@ -0,0 +1,78 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use crate::path::JsonPath;
+
+mod common_properties;
+mod empty_items;
+mod fixup_201909;
+mod int_items_to_matrix;
+mod int_minmax;
+mod interrupts;
+mod items_size;
+pub mod property_fixups;
+mod reg;
+pub mod schema_fixups;
+mod single_int;
+mod single_string;
+mod variable_matrix_fixup;
+
+#[derive(thiserror::Error, Debug)]
+pub enum FixupError {
+ #[error("JSON error {0} at {1}")]
+ JsonError(serde_json::Error, Box<std::backtrace::Backtrace>),
+
+ #[error("Could not parse schema as it was in an unexpected format: {0}. at {1}, value: {2}")]
+ UnexpectedSchemaError(String, JsonPath, serde_json::Value),
+}
+
+impl From<serde_json::Error> for FixupError {
+ fn from(value: serde_json::Error) -> Self {
+ FixupError::JsonError(value, Box::new(std::backtrace::Backtrace::force_capture()))
+ }
+}
+
+pub trait Fixup: Sized {
+ /// Make a new instance of this |fixup|. Returns None if the fixup is not applicable to this property.
+ fn new(
+ propname: &str,
+ value: &serde_json::Value,
+ path: JsonPath,
+ ) -> Result<Option<Self>, FixupError>;
+
+ /// Run the fixup and give the fixed |Value| back.
+ fn fixup(self) -> Result<serde_json::Value, FixupError>;
+}
+
+/// Helper function that does a fixup (if it is applicable, i.e. T::new() returns Ok(Some(...))), or just returns the given |value|.
+/// |path| should not include |propname|.
+fn do_fixup<T: Fixup>(
+ propname: &str,
+ value: serde_json::Value,
+ path: &JsonPath,
+) -> Result<serde_json::Value, FixupError> {
+ let extended = path.extend(propname);
+ let result = T::new(propname, &value, extended.clone())?
+ .map(|v| v.fixup())
+ .unwrap_or(Ok(value.clone()));
+ if Some(&value) != result.as_ref().ok() {
+ let string = serde_json::to_string(result.as_ref().unwrap_or(&serde_json::json!(null)))
+ .unwrap_or_else(|_| "N/A".to_owned());
+ if string.len() <= 100 {
+ tracing::trace!(
+ "applied fixup {} to {}: {}",
+ std::any::type_name::<T>(),
+ extended,
+ string,
+ );
+ } else {
+ tracing::trace!(
+ "applied fixup {} to {}: <omitted>",
+ std::any::type_name::<T>(),
+ extended,
+ );
+ }
+ }
+ result
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/common_properties.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/common_properties.rs
new file mode 100644
index 0000000..8e614eeb
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/common_properties.rs
@@ -0,0 +1,262 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::HashSet;
+
+use fancy_regex::Regex;
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::Fixup;
+
+#[derive(Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct NodeWithProperties {
+ #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
+ properties: serde_json::Map<String, serde_json::Value>,
+ #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
+ pattern_properties: serde_json::Map<String, serde_json::Value>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ unevaluated_properties: Option<serde_json::Value>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ additional_properties: Option<serde_json::Value>,
+
+ #[serde(flatten)]
+ etc: serde_json::Map<String, serde_json::Value>,
+}
+
+impl NodeWithProperties {
+ fn has_properties(&self) -> bool {
+ !self.properties.is_empty()
+ || !self.pattern_properties.is_empty()
+ || self.unevaluated_properties.is_some()
+ }
+
+ fn is_incomplete_schema(&self) -> bool {
+ self.unevaluated_properties
+ .as_ref()
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false)
+ || self
+ .additional_properties
+ .as_ref()
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false)
+ }
+}
+
+/// Add missing node properties to schemas.
+/// This includes adding phandle, status, secure-status and $nodename unconditionally,
+/// adding pinctrl properties if needed and also adding assigned-clock properties if needed.
+pub struct CommonPropertiesFixup {
+ node: NodeWithProperties,
+}
+
+impl Fixup for CommonPropertiesFixup {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ _path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ if !value.is_object() {
+ return Ok(None);
+ }
+ let value: NodeWithProperties = serde_json::from_value(value.clone())?;
+ if !value.has_properties() || value.is_incomplete_schema() {
+ Ok(None)
+ } else {
+ Ok(Some(CommonPropertiesFixup { node: value }))
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ for value in ["phandle", "status", "secure-status", "$nodename"] {
+ self.node.properties.entry(value).or_insert(json!(true));
+ }
+
+ let key_pinctrl_regex = Regex::new("^pinctrl-[0-9]").unwrap();
+ let all_keys: HashSet<String> = self
+ .node
+ .properties
+ .keys()
+ .chain(self.node.pattern_properties.keys())
+ .cloned()
+ .collect();
+
+ if !all_keys
+ .iter()
+ .any(|key| key_pinctrl_regex.is_match(key).unwrap())
+ {
+ self.node
+ .properties
+ .entry("pinctrl-names")
+ .or_insert(json!(true));
+ self.node
+ .pattern_properties
+ .insert("pinctrl-[0-9]+".to_owned(), json!(true));
+ }
+
+ if all_keys.contains("clocks") && !all_keys.contains("assigned-clocks") {
+ self.node
+ .properties
+ .insert("assigned-clocks".to_owned(), json!(true));
+ self.node
+ .properties
+ .insert("assigned-clock-rates".to_owned(), json!(true));
+ self.node
+ .properties
+ .insert("assigned-clock-parents".to_owned(), json!(true));
+ }
+
+ Ok(serde_json::to_value(self.node)?)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_incomplete() {
+ let schema = json!({
+ "properties": {
+ "clocks": 10,
+ },
+ "additionalProperties": true,
+ });
+
+ assert!(CommonPropertiesFixup::new("", &schema, JsonPath::new())
+ .expect("No errors")
+ .is_none());
+ }
+
+ #[test]
+ fn test_no_properties() {
+ let schema = json!({});
+
+ assert!(CommonPropertiesFixup::new("", &schema, JsonPath::new())
+ .expect("No errors")
+ .is_none());
+ }
+
+ #[test]
+ fn test_basic_properties() {
+ let schema = json!({"properties": { "example": true }});
+
+ let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+ .expect("No errors")
+ .expect("Applies to schema")
+ .fixup()
+ .expect("Fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "example": true,
+ "phandle": true,
+ "status": true,
+ "secure-status": true,
+ "$nodename": true,
+ "pinctrl-names": true,
+ },
+ "patternProperties": {
+ "pinctrl-[0-9]+": true,
+ }
+ })
+ );
+ }
+
+ #[test]
+ fn test_basic_properties_not_overwritten() {
+ let schema = json!({"properties": {
+ "$nodename": {"const": "foo"}
+ }});
+
+ let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+ .expect("No errors")
+ .expect("Applies to schema")
+ .fixup()
+ .expect("Fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "phandle": true,
+ "status": true,
+ "secure-status": true,
+ "$nodename": {"const": "foo"},
+ "pinctrl-names": true,
+ },
+ "patternProperties": {
+ "pinctrl-[0-9]+": true,
+ }
+ })
+ );
+ }
+
+ #[test]
+ fn test_adds_assigned_clocks() {
+ let schema = json!({"properties": {
+ "clocks": {"maxItems": 2}
+ }});
+
+ let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+ .expect("No errors")
+ .expect("Applies to schema")
+ .fixup()
+ .expect("Fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "clocks": {"maxItems": 2},
+ "phandle": true,
+ "status": true,
+ "secure-status": true,
+ "$nodename": true,
+ "pinctrl-names": true,
+ "assigned-clocks": true,
+ "assigned-clock-rates": true,
+ "assigned-clock-parents": true
+ },
+ "patternProperties": {
+ "pinctrl-[0-9]+": true,
+ }
+ })
+ );
+ }
+
+ #[test]
+ fn test_explicit_pinctrl() {
+ let schema = json!({"properties": {
+ "pinctrl-0": true,
+ "pinctrl-1": true,
+ }});
+
+ let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+ .expect("No errors")
+ .expect("Applies to schema")
+ .fixup()
+ .expect("Fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "phandle": true,
+ "status": true,
+ "secure-status": true,
+ "$nodename": true,
+ "pinctrl-0": true,
+ "pinctrl-1": true,
+ }
+ })
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/empty_items.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/empty_items.rs
new file mode 100644
index 0000000..962751d
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/empty_items.rs
@@ -0,0 +1,129 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::{Fixup, FixupError};
+
+pub struct EmptyItemsRemovalFixup {
+ object: serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+}
+
+impl EmptyItemsRemovalFixup {
+ fn fixup_one_object(
+ obj: &mut serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+ ) -> Result<(), FixupError> {
+ let fixed_item_length = match obj.get_mut("items") {
+ Some(serde_json::Value::Array(items)) => {
+ let mut has_value = false;
+ for (i, item) in items
+ .iter_mut()
+ .enumerate()
+ .filter_map(|(k, v)| v.as_object_mut().map(|v| (k, v)))
+ {
+ item.remove("description");
+ Self::fixup_one_object(item, path.extend_array_index("items", i))?;
+ if !item.is_empty() {
+ // We found a value, so we're not going to remove the "items" child from this array.
+ has_value = true;
+ break;
+ }
+ }
+
+ if has_value {
+ None
+ } else {
+ Some(items.len())
+ }
+ }
+ Some(serde_json::Value::Object(o)) => {
+ Self::fixup_one_object(o, path.extend("items"))?;
+ None
+ }
+ None => return Ok(()),
+ _ => {
+ return Err(FixupError::UnexpectedSchemaError(
+ "items should be array or object".to_owned(),
+ path.extend("items"),
+ obj.clone().into(),
+ ))
+ }
+ };
+
+ if let Some(length) = fixed_item_length {
+ obj.entry("type").or_insert(json!("array"));
+ obj.entry("maxItems").or_insert(json!(length));
+ obj.entry("minItems").or_insert(json!(length));
+ obj.remove("items");
+ }
+
+ Ok(())
+ }
+}
+
+impl Fixup for EmptyItemsRemovalFixup {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ let map_ref = value.as_object().ok_or(FixupError::UnexpectedSchemaError(
+ "EmptyItemsRemovalFixup expects an object".to_owned(),
+ path.clone(),
+ value.clone(),
+ ))?;
+
+ if !map_ref.contains_key("items") {
+ Ok(None)
+ } else {
+ Ok(Some(EmptyItemsRemovalFixup {
+ object: map_ref.clone(),
+ path,
+ }))
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ Self::fixup_one_object(&mut self.object, self.path)?;
+ Ok(self.object.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_remove_empty_items() {
+ let data = json!({
+ "items": [{
+ "items": [{
+ "description": "wow, some items",
+ }]
+ },
+ {"items": []},
+ {}
+ ]
+ });
+
+ let result = EmptyItemsRemovalFixup::new("", &data, JsonPath::new())
+ .expect("schema ok")
+ .unwrap()
+ .fixup()
+ .expect("fixup ok");
+ assert_eq!(
+ result,
+ json!({"items": [
+ {"minItems": 1, "maxItems": 1, "type": "array"},
+ {"items": []},
+ {}
+ ]})
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/fixup_201909.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/fixup_201909.rs
new file mode 100644
index 0000000..cd72b7a
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/fixup_201909.rs
@@ -0,0 +1,107 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::{Fixup, FixupError};
+
+/// Fix pre-201909 schemas to be compatible with 201909.
+/// The only transformation is splitting "dependencies" into "dependentRequired" and "dependentSchemas" per
+/// https://json-schema.org/understanding-json-schema/reference/conditionals.html (see "draft-specific info").
+pub struct Fixup201909 {
+ map: serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+}
+
+impl Fixup for Fixup201909 {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ match value {
+ serde_json::Value::Object(o) => {
+ if o.contains_key("dependencies") {
+ Ok(Some(Fixup201909 {
+ map: o.clone(),
+ path,
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+ _ => Ok(None),
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ let value = self.map.remove("dependencies").unwrap();
+ let dependencies = match value {
+ serde_json::Value::Object(o) => o,
+ _ => {
+ return Err(FixupError::UnexpectedSchemaError(
+ "dependencies should be a map".to_owned(),
+ self.path.extend("dependencies"),
+ value.clone(),
+ ));
+ }
+ };
+
+ for (k, v) in dependencies.into_iter() {
+ match v {
+ serde_json::Value::Array(array) => {
+ let dependent_required = self.map.entry("dependentRequired");
+ dependent_required
+ .or_insert(json!({}))
+ .as_object_mut()
+ .unwrap()
+ .insert(k, array.into());
+ }
+ value => {
+ let dependent_schemas = self.map.entry("dependentSchemas");
+ dependent_schemas
+ .or_insert(json!({}))
+ .as_object_mut()
+ .unwrap()
+ .insert(k, value);
+ }
+ }
+ }
+
+ Ok(self.map.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_translate_old_schema() {
+ let old_value = json!({
+ "dependencies": {
+ "propertyOne": ["propertyTwo"],
+ "propertyTwo": {
+ "propertyThree": {"type": "string"}
+ }
+ }
+ });
+
+ let result = Fixup201909::new("", &old_value, JsonPath::new())
+ .expect("Schema ok")
+ .unwrap()
+ .fixup()
+ .expect("Fixup ok");
+ assert_eq!(
+ result,
+ json!({
+ "dependentRequired": {"propertyOne": ["propertyTwo"]},
+ "dependentSchemas": {"propertyTwo": {
+ "propertyThree": {"type": "string"}
+ }}
+ })
+ );
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_items_to_matrix.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_items_to_matrix.rs
new file mode 100644
index 0000000..8883f02
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_items_to_matrix.rs
@@ -0,0 +1,173 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::BTreeMap;
+
+use serde_json::json;
+
+use crate::validator::array::Array;
+
+use super::Fixup;
+
+/// Convert an int array with an "items" declaration
+/// to a matrix.
+pub struct IntItemsToMatrix {
+ value: serde_json::Value,
+}
+
+impl Fixup for IntItemsToMatrix {
+ fn new(
+ propname: &str,
+ value: &serde_json::Value,
+ _path: crate::path::JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ let items: Array = serde_json::from_value(value.clone())?;
+ if !items.is_int_array_schema(propname) {
+ return Ok(None);
+ }
+
+ Ok(Some(IntItemsToMatrix {
+ value: value.clone(),
+ }))
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ let final_schema = if let Some(array) = self
+ .value
+ .as_object_mut()
+ .and_then(|e| e.get_mut("allOf"))
+ .and_then(|e| e.as_array_mut())
+ {
+ array
+ .iter_mut()
+ .filter_map(|v| v.as_object_mut())
+ .find(|o| o.contains_key("items"))
+ } else {
+ self.value.as_object_mut()
+ };
+
+ let object = if let Some(object) = final_schema {
+ let items: Array = serde_json::from_value(object.clone().into())?;
+ if !items.has_items() || items.is_matrix() {
+ return Ok(self.value);
+ }
+ object
+ } else {
+ return Ok(self.value);
+ };
+
+ let item_keys = ["items", "minItems", "maxItems", "uniqueItems", "default"];
+ let mut values: BTreeMap<String, serde_json::Value> = BTreeMap::new();
+ for key in item_keys {
+ if let Some(value) = object.remove(key) {
+ values.insert(key.to_owned(), value);
+ }
+ }
+
+ // Safe because we checked |items.has_items()| above.
+ match values.get_mut("items").unwrap() {
+ serde_json::Value::Array(_) => {
+ object.insert("items".to_owned(), json!([values]));
+ }
+ serde_json::Value::Object(_) => {
+ object.insert("items".to_owned(), json!(values));
+ }
+ _ => {}
+ };
+
+ Ok(self.value)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::path::JsonPath;
+
+ use super::*;
+
+ const PROP_NAME: &str = "test-percent";
+
+ #[test]
+ fn test_make_matrix_simple() {
+ let schema = json!({
+ "items": {
+ "minimum": 2,
+ "maximum": 10,
+ },
+ "maxItems": 2,
+ "minItems": 2,
+ });
+
+ let result = IntItemsToMatrix::new(PROP_NAME, &schema, JsonPath::new())
+ .expect("Valid schema")
+ .expect("Fixup applies")
+ .fixup()
+ .expect("Fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "items": {
+ "items": {
+ "minimum": 2,
+ "maximum": 10,
+ },
+ "maxItems": 2,
+ "minItems": 2,
+ },
+ })
+ )
+ }
+
+ #[test]
+ fn test_make_matrix_with_allof() {
+ let schema = json!({
+ "allOf": [
+ true,
+ {"maxItems": 2, "minItems": 1, "items": [{"const": 4}]},
+ ]
+ });
+
+ let result = IntItemsToMatrix::new(PROP_NAME, &schema, JsonPath::new())
+ .expect("Valid schema")
+ .expect("Fixup applies")
+ .fixup()
+ .expect("Fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "allOf": [
+ true,
+ {
+ "items": [{
+ "maxItems": 2,
+ "minItems": 1,
+ "items": [{"const": 4}]
+ }]
+ }
+ ]
+ })
+ );
+ }
+
+ #[test]
+ fn test_skips_existing_matrix() {
+ let schema = json!({
+ "items": {
+ "items": {
+ "minItems": 2,
+ }
+ }
+ });
+
+ let result = IntItemsToMatrix::new(PROP_NAME, &schema, JsonPath::new())
+ .expect("Valid schema")
+ .expect("Fixup applies")
+ .fixup()
+ .expect("Fixup OK");
+
+ assert_eq!(result, schema);
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_minmax.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_minmax.rs
new file mode 100644
index 0000000..d9f5f5e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_minmax.rs
@@ -0,0 +1,202 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde_json::json;
+
+use crate::{
+ path::JsonPath,
+ validator::{array::Array, util::ContainsExt},
+};
+
+use super::{items_size::ItemsSizeFixup, Fixup, FixupError};
+
+/// Convert an int array defined by "minItems" and "maxItems" to a matrix.
+pub struct IntMinMaxToMatrixFixup {
+ object: serde_json::Value,
+ path: JsonPath,
+}
+
+impl IntMinMaxToMatrixFixup {
+ // Actually perform the fixup. Split like this to make the borrow-checker happy.
+ fn do_fixup_on_object(
+ map: &mut serde_json::Map<String, serde_json::Value>,
+ path: JsonPath,
+ ) -> Result<(), FixupError> {
+ if map.get_mut("items").and_then(|v| v.as_array()).is_some() {
+ return Ok(());
+ }
+
+ let items: Array = serde_json::from_value(map.clone().into())?;
+ if items.is_matrix() {
+ return Ok(());
+ }
+
+ if map.get("maxItems").and_then(|v| v.as_u64()) == Some(1) {
+ return Ok(());
+ }
+
+ let mut tmp_schema = serde_json::Map::<String, serde_json::Value>::new();
+ let min_items = if let Some(min) = map.remove("minItems") {
+ tmp_schema.insert("minItems".to_owned(), min.clone());
+ min.as_u64().unwrap_or(0)
+ } else {
+ 0
+ };
+ if let Some(max) = map.remove("maxItems") {
+ tmp_schema.insert("maxItems".to_owned(), max);
+ }
+
+ if !tmp_schema.is_empty() {
+ let mut vec = vec![json!({"items": [tmp_schema.clone()]})];
+
+ tmp_schema.insert("items".to_owned(), json!({"maxItems": 1}));
+ if min_items == 1 {
+ tmp_schema.insert("minItems".to_owned(), json!(2));
+ }
+ vec.push(tmp_schema.into());
+
+ // We added "oneOf", so we need to manually do this fixup.
+ let value = serde_json::to_value(vec)?;
+ let size_fixup = ItemsSizeFixup::new("", &value, path.extend("oneOf"))?
+ .map(|v| v.fixup())
+ .ok_or(FixupError::UnexpectedSchemaError(
+ "item size fixup should operate after minmax fixup".to_owned(),
+ path.extend("oneOf"),
+ value,
+ ))??;
+ map.insert("oneOf".to_owned(), size_fixup);
+ }
+
+ Ok(())
+ }
+}
+
+impl Fixup for IntMinMaxToMatrixFixup {
+ fn new(
+ propname: &str,
+ value: &serde_json::Value,
+ path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ let items: Array = serde_json::from_value(value.clone())?;
+ if !items.is_int_array_schema(propname) {
+ return Ok(None);
+ }
+
+ let value = value.clone();
+ match value.as_object() {
+ Some(_) => {}
+ None => {
+ return Ok(None);
+ }
+ };
+
+ Ok(Some(IntMinMaxToMatrixFixup {
+ object: value,
+ path,
+ }))
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ // Find the actual map we want to modify.
+ let map = self.object.as_object_mut().unwrap();
+ if let Some(all_of) = map.get_mut("allOf").and_then(|v| v.as_array_mut()) {
+ for (i, item) in all_of.iter_mut().enumerate() {
+ if item
+ .as_object_mut()
+ .map(|v| v.contains_any(&["minItems", "maxItems"]))
+ .unwrap_or(false)
+ {
+ Self::do_fixup_on_object(
+ item.as_object_mut().unwrap(),
+ self.path.extend_array_index("allOf", i),
+ )?;
+ break;
+ }
+ }
+ } else {
+ Self::do_fixup_on_object(map, self.path)?;
+ };
+
+ Ok(self.object)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const PROP_NAME: &str = "test-percent";
+ #[test]
+ fn test_simple() {
+ let schema = json!({
+ "minItems": 2,
+ "maxItems": 3,
+ });
+
+ let result = IntMinMaxToMatrixFixup::new(PROP_NAME, &schema, JsonPath::new())
+ .expect("schema OK")
+ .expect("fixup applies")
+ .fixup()
+ .expect("fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "oneOf": [
+ {
+ "items": [
+ {
+ "minItems": 2,
+ "maxItems": 3,
+ }
+ ],
+ "minItems": 1,
+ "maxItems": 1,
+ "type": "array",
+ },
+ {
+ "items": {
+ "maxItems": 1,
+ "minItems": 1,
+ },
+ "minItems": 2,
+ "maxItems": 3,
+ "type": "array",
+ }
+ ]
+ })
+ );
+ }
+
+ #[test]
+ fn test_preserves_allof() {
+ let schema = json!({
+ "allOf": [
+ true,
+ {"minItems": 1, "maxItems": 4}
+ ]
+ });
+
+ let result = IntMinMaxToMatrixFixup::new(PROP_NAME, &schema, JsonPath::new())
+ .expect("schema OK")
+ .expect("fixup applies")
+ .fixup()
+ .expect("fixup OK");
+
+ assert_eq!(
+ result,
+ json!({
+ "allOf": [
+ true,
+ {
+ "oneOf": [
+ {"minItems": 1, "maxItems": 1, "items": [{"maxItems": 4, "minItems": 1}], "type": "array"},
+ {"minItems": 2, "maxItems": 4, "items": {"minItems": 1, "maxItems": 1}, "type": "array"},
+ ]
+ }
+ ]
+ })
+ )
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/interrupts.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/interrupts.rs
new file mode 100644
index 0000000..52c19a7
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/interrupts.rs
@@ -0,0 +1,233 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::HashSet;
+
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::Fixup;
+
+#[derive(Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+struct InterruptProps {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ interrupts: Option<serde_json::Value>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ interrupts_extended: Option<serde_json::Value>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ interrupt_controller: Option<serde_json::Value>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ interrupt_parent: Option<serde_json::Value>,
+
+ #[serde(flatten)]
+ etc: serde_json::Value,
+}
+
+#[derive(Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct Interrupt {
+ properties: InterruptProps,
+ #[serde(default, skip_serializing_if = "HashSet::is_empty")]
+ required: HashSet<String>,
+
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ one_of: Vec<serde_json::Value>,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ all_of: Vec<serde_json::Value>,
+
+ #[serde(flatten)]
+ etc: serde_json::Value,
+}
+
+/// |InterruptsFixup| does two things:
+/// 1. Allows any node with "interrupts" or "interrupt-controller" properties to have an "interrupt-parent"
+/// 2. Allows any node with "interrupts" to have "interrupts-extended".
+pub struct InterruptsFixup {
+ interrupt: Interrupt,
+}
+
+impl Fixup for InterruptsFixup {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ _path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ if value
+ .as_object()
+ .and_then(|v| v.get("properties"))
+ .is_some()
+ {
+ Ok(Some(InterruptsFixup {
+ interrupt: serde_json::from_value(value.clone())?,
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ let props = &mut self.interrupt.properties;
+
+ // Any node with interrupts can have 'interrupt-parent'.
+ if props
+ .interrupts
+ .as_ref()
+ .or(props.interrupt_controller.as_ref())
+ .is_some()
+ && props.interrupt_parent.is_none()
+ {
+ props.interrupt_parent = Some(json!(true));
+ }
+
+ // Any node with 'interrupts' can also have 'interrupts-extended'.
+ match (
+ props.interrupts.as_ref(),
+ props.interrupts_extended.as_ref(),
+ ) {
+ (None, _) => return Ok(serde_json::to_value(self.interrupt)?),
+ (Some(interrupts), None) => props.interrupts_extended = Some(interrupts.clone()),
+ (Some(_), Some(_)) => {}
+ }
+
+ if self.interrupt.required.remove("interrupts") {
+ let required = vec![
+ json!({"required": ["interrupts"]}),
+ json!({"required": ["interrupts-extended"]}),
+ ];
+ if !self.interrupt.one_of.is_empty() {
+ if self.interrupt.all_of.is_empty() {
+ self.interrupt.all_of.push(json!({ "oneOf": required }));
+ } else {
+ self.interrupt.all_of = vec![json!({ "oneOf": required })];
+ }
+ } else {
+ self.interrupt.one_of = required;
+ }
+ }
+ Ok(serde_json::to_value(self.interrupt)?)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_interrupt_controller() {
+ let schema = json!({
+ "properties": {
+ "interrupt-controller": true,
+ }
+ });
+
+ let result = InterruptsFixup::new("", &schema, JsonPath::new())
+ .expect("schema ok")
+ .expect("fixup applies")
+ .fixup()
+ .expect("fixup ok");
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "interrupt-controller": true,
+ "interrupt-parent": true,
+ }
+ })
+ )
+ }
+
+ #[test]
+ fn test_interrupts() {
+ let schema = json!({
+ "properties": {
+ "interrupts": {"items": [{"description": "IRQ 1"}]},
+ }
+ });
+
+ let result = InterruptsFixup::new("", &schema, JsonPath::new())
+ .expect("schema ok")
+ .expect("fixup applies")
+ .fixup()
+ .expect("fixup ok");
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "interrupt-parent": true,
+ "interrupts": {"items": [{"description": "IRQ 1"}]},
+ "interrupts-extended": {"items": [{"description": "IRQ 1"}]},
+ }
+ })
+ )
+ }
+
+ #[test]
+ fn test_interrupts_required() {
+ let schema = json!({
+ "properties": {
+ "interrupts": {"items": [{"description": "IRQ 1"}]},
+ },
+ "required": ["interrupts"]
+ });
+
+ let result = InterruptsFixup::new("", &schema, JsonPath::new())
+ .expect("schema ok")
+ .expect("fixup applies")
+ .fixup()
+ .expect("fixup ok");
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "interrupt-parent": true,
+ "interrupts": {"items": [{"description": "IRQ 1"}]},
+ "interrupts-extended": {"items": [{"description": "IRQ 1"}]},
+ },
+ "oneOf": [
+ {"required": ["interrupts"]},
+ {"required": ["interrupts-extended"]}
+ ]
+ })
+ )
+ }
+
+ #[test]
+ fn test_interrupts_required_already_oneof() {
+ let schema = json!({
+ "properties": {
+ "interrupts": {"items": [{"description": "IRQ 1"}]},
+ },
+ "required": ["interrupts"],
+ "oneOf": [{"$nodename": "hello"}, {"$nodename": "hello2"}]
+ });
+
+ let result = InterruptsFixup::new("", &schema, JsonPath::new())
+ .expect("schema ok")
+ .expect("fixup applies")
+ .fixup()
+ .expect("fixup ok");
+ assert_eq!(
+ result,
+ json!({
+ "properties": {
+ "interrupt-parent": true,
+ "interrupts": {"items": [{"description": "IRQ 1"}]},
+ "interrupts-extended": {"items": [{"description": "IRQ 1"}]},
+ },
+ "oneOf": [{"$nodename": "hello"}, {"$nodename": "hello2"}],
+ "allOf": [
+ {
+ "oneOf": [
+ {"required": ["interrupts"]},
+ {"required": ["interrupts-extended"]}
+ ]
+ },
+ ]
+ })
+ )
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/items_size.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/items_size.rs
new file mode 100644
index 0000000..192b05e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/items_size.rs
@@ -0,0 +1,141 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::{Fixup, FixupError};
+
+pub struct ItemsSizeFixup {
+ value: serde_json::Value,
+ path: JsonPath,
+}
+
+impl ItemsSizeFixup {
+ fn do_one_fixup(item: &mut serde_json::Value, path: JsonPath) -> Result<(), FixupError> {
+ match item {
+ serde_json::Value::Array(array) => {
+ for (i, item) in array.iter_mut().enumerate() {
+ Self::do_one_fixup(item, path.extend_index_only(i))?;
+ }
+ }
+ serde_json::Value::Object(obj) => {
+ obj.remove("description");
+ if let Some(items) = obj.get("items") {
+ if let Some(array) = items.as_array() {
+ let length = json!(array.len());
+ obj.entry("minItems").or_insert(length.clone());
+ obj.entry("maxItems").or_insert(length);
+ }
+ obj.insert("type".to_owned(), json!("array"));
+
+ Self::do_one_fixup(obj.get_mut("items").unwrap(), path.extend("items"))?;
+ } else {
+ match (obj.get("minItems"), obj.get("maxItems")) {
+ (Some(min), None) => obj.insert("maxItems".to_owned(), min.clone()),
+ (None, Some(max)) => obj.insert("minItems".to_owned(), max.clone()),
+ _ => None,
+ };
+ }
+ }
+ _ => {}
+ }
+
+ Ok(())
+ }
+}
+
+impl Fixup for ItemsSizeFixup {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ match value {
+ serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
+ Ok(Some(ItemsSizeFixup {
+ value: value.clone(),
+ path,
+ }))
+ }
+ _ => Ok(None),
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ Self::do_one_fixup(&mut self.value, self.path)?;
+ Ok(self.value)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_implicit_size() {
+ let array = json!({
+ "items": [{"const": "a"}, {"const": "b"}]
+ });
+ let result = ItemsSizeFixup::new("", &array, JsonPath::new())
+ .unwrap()
+ .unwrap()
+ .fixup()
+ .expect("fixup ok");
+
+ assert_eq!(
+ result,
+ json!({
+ "minItems": 2,
+ "maxItems": 2,
+ "type": "array",
+ "items": [{"const": "a"}, {"const": "b"}]
+ })
+ )
+ }
+
+ #[test]
+ fn test_implicit_max_size() {
+ let array = json!({
+ "items": [{"const": "a"}, {"const": "b"}],
+ "minItems": 1
+ });
+ let result = ItemsSizeFixup::new("", &array, JsonPath::new())
+ .unwrap()
+ .unwrap()
+ .fixup()
+ .expect("fixup ok");
+
+ assert_eq!(
+ result,
+ json!({
+ "minItems": 1,
+ "maxItems": 2,
+ "type": "array",
+ "items": [{"const": "a"}, {"const": "b"}]
+ })
+ )
+ }
+
+ #[test]
+ fn test_no_items() {
+ let array = json!({
+ "minItems": 1
+ });
+ let result = ItemsSizeFixup::new("", &array, JsonPath::new())
+ .unwrap()
+ .unwrap()
+ .fixup()
+ .expect("fixup ok");
+
+ assert_eq!(
+ result,
+ json!({
+ "minItems": 1,
+ "maxItems": 1,
+ })
+ )
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/property_fixups.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/property_fixups.rs
new file mode 100644
index 0000000..0baa49b
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/property_fixups.rs
@@ -0,0 +1,61 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use super::{
+ do_fixup, empty_items::EmptyItemsRemovalFixup, fixup_201909::Fixup201909,
+ int_items_to_matrix::IntItemsToMatrix, int_minmax::IntMinMaxToMatrixFixup,
+ items_size::ItemsSizeFixup, reg::RegFixup, single_int::SingleIntFixup,
+ single_string::SingleStringFixup, variable_matrix_fixup::VariableIntMatrixFixup, FixupError,
+};
+use crate::path::JsonPath;
+
+pub fn walk_properties(
+ propname: &str,
+ mut value: serde_json::Value,
+ prop_path: JsonPath,
+) -> Result<serde_json::Value, FixupError> {
+ if let Some(map) = value.as_object_mut() {
+ for cond in ["allOf", "anyOf", "oneOf"] {
+ if let Some(serde_json::Value::Array(array)) = map.get_mut(cond) {
+ for (i, item) in array.iter_mut().enumerate() {
+ *item = walk_properties(
+ propname,
+ item.clone(),
+ prop_path.extend_array_index(cond, i),
+ )?;
+ }
+ }
+ }
+
+ if let Some(value) = map.get_mut("then") {
+ *value = walk_properties(propname, value.clone(), prop_path.extend("then"))?;
+ }
+
+ fixup_properties(propname, value, &prop_path)
+ } else {
+ Ok(value)
+ }
+}
+
+fn fixup_properties(
+ propname: &str,
+ mut value: serde_json::Value,
+ path: &JsonPath,
+) -> Result<serde_json::Value, FixupError> {
+ value.as_object_mut().and_then(|v| v.remove("description"));
+
+ let value = do_fixup::<RegFixup>(propname, value, path)?;
+ let value = do_fixup::<EmptyItemsRemovalFixup>(propname, value, path)?;
+ let value = do_fixup::<VariableIntMatrixFixup>(propname, value, path)?;
+
+ let value = do_fixup::<IntMinMaxToMatrixFixup>(propname, value, path)?;
+ let value = do_fixup::<IntItemsToMatrix>(propname, value, path)?;
+
+ let value = do_fixup::<SingleStringFixup>(propname, value, path)?;
+ let value = do_fixup::<SingleIntFixup>(propname, value, path)?;
+ let value = do_fixup::<ItemsSizeFixup>(propname, value, path)?;
+ let value = do_fixup::<Fixup201909>(propname, value, path)?;
+
+ Ok(value)
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/reg.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/reg.rs
new file mode 100644
index 0000000..5c20d7e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/reg.rs
@@ -0,0 +1,120 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::HashMap;
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::util::IsSchema};
+
+use super::{Fixup, FixupError};
+
+/// Translate a "reg" property schema into a more fully formed schema that identifies the number
+/// of items in each reg "entry" as well as the total number of reg entries.
+pub struct RegFixup {
+ object: serde_json::Map<String, serde_json::Value>,
+}
+
+impl Fixup for RegFixup {
+ fn new(
+ propname: &str,
+ value: &serde_json::Value,
+ path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ if propname != "reg" {
+ return Ok(None);
+ }
+
+ if let Some(object) = value.as_object() {
+ Ok(Some(Self {
+ object: object.clone(),
+ }))
+ } else {
+ Err(FixupError::UnexpectedSchemaError(
+ "reg should be an object, but it was not".to_owned(),
+ path,
+ value.clone(),
+ ))
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ let map = self
+ .object
+ .get("items")
+ .and_then(|v| match v {
+ serde_json::Value::Array(array) => array.get(0),
+ other => Some(other),
+ })
+ .and_then(|v| v.as_object())
+ .unwrap_or(&self.object);
+ if !map.is_int_schema() {
+ return Ok(self.object.into());
+ }
+
+ let mut object: HashMap<String, serde_json::Value> = HashMap::new();
+ object.extend(
+ map.iter()
+ .filter(|(k, _)| {
+ let k = k.as_str();
+ k == "const" || k == "enum" || k == "minimum" || k == "maximum"
+ })
+ .map(|(k, v)| (k.clone(), v.clone())),
+ );
+
+ self.object
+ .insert("items".to_owned(), json!([{ "items": object }]));
+
+ Ok(self.object.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_reg_fixup_basic() {
+ let reg = json!({
+ "items": {
+ "minimum": 10,
+ "maximum": 12,
+ }
+ });
+
+ let result = RegFixup::new("reg", ®, JsonPath::new())
+ .expect("reg schema valid")
+ .unwrap()
+ .fixup()
+ .expect("fixup succeeds");
+
+ assert_eq!(
+ result,
+ json!({
+ "items": [{
+ "items": {"minimum": 10, "maximum": 12},
+ }]
+ })
+ );
+ }
+
+ #[test]
+ fn test_reg_fixup_list() {
+ let reg = json!({
+ "items": [{"enum": [2, 4]}]
+ });
+
+ let result = RegFixup::new("reg", ®, JsonPath::new())
+ .expect("reg schema valid")
+ .unwrap()
+ .fixup()
+ .expect("fixup succeeds");
+ assert_eq!(
+ result,
+ json!({
+ "items": [{"items": {"enum": [2, 4]}}]
+ })
+ )
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/schema_fixups.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/schema_fixups.rs
new file mode 100644
index 0000000..801963e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/schema_fixups.rs
@@ -0,0 +1,118 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::map::Entry;
+
+use super::{
+ common_properties::CommonPropertiesFixup, do_fixup, fixup_201909::Fixup201909,
+ interrupts::InterruptsFixup, property_fixups, FixupError,
+};
+use crate::path::JsonPath;
+
+pub struct SchemaFixup {}
+
+impl SchemaFixup {
+ #[tracing::instrument(level = "debug", skip(value))]
+ pub fn fixup(
+ mut value: serde_json::Value,
+ path: String,
+ ) -> Result<serde_json::Value, FixupError> {
+ let object = match value.as_object_mut() {
+ Some(o) => o,
+ None => {
+ return Err(FixupError::UnexpectedSchemaError(
+ "Schema fixup expected object".to_owned(),
+ JsonPath::new(),
+ value,
+ ));
+ }
+ };
+ object.remove("examples");
+ object.remove("maintainers");
+ object.remove("historical");
+
+ let ret = Self::fixup_subschema(value, true, JsonPath::new())?;
+ Ok(ret)
+ }
+
+ #[tracing::instrument(level = "debug", skip(subschema), fields(path=path.back()))]
+ fn fixup_subschema(
+ mut subschema: serde_json::Value,
+ is_node_property: bool,
+ path: JsonPath,
+ ) -> Result<serde_json::Value, FixupError> {
+ match subschema.as_object_mut() {
+ Some(map) => {
+ map.remove("description");
+ }
+ None => {
+ return Ok(subschema);
+ }
+ }
+
+ subschema = do_fixup::<InterruptsFixup>("", subschema, &path)?;
+ if is_node_property {
+ subschema = do_fixup::<CommonPropertiesFixup>("", subschema, &path)?;
+ }
+
+ let object = subschema.as_object_mut().unwrap();
+
+ // "additionalProperties: true" doesn't work with "unevaluatedProperties", so remove it.
+ if let Entry::Occupied(e) = object.entry("additionalProperties") {
+ if let Some(true) = e.get().as_bool() {
+ e.remove();
+ }
+ }
+
+ for k in [
+ "select",
+ "if",
+ "then",
+ "else",
+ "additionalProperties",
+ "not",
+ ] {
+ if let Some(value) = object.remove(k) {
+ object.insert(
+ k.to_owned(),
+ Self::fixup_subschema(value, false, path.extend(k))?,
+ );
+ }
+ }
+
+ for k in ["allOf", "anyOf", "oneOf"] {
+ if let Some(array) = object.get_mut(k).and_then(|v| v.as_array_mut()) {
+ for item in array.iter_mut() {
+ *item = Self::fixup_subschema(item.clone(), true, path.extend(k))?;
+ }
+ }
+ }
+
+ for k in [
+ "dependentRequired",
+ "dependentSchemas",
+ "dependencies",
+ "properties",
+ "patternProperties",
+ "$defs",
+ ] {
+ if let Some(map) = object.get_mut(k).and_then(|v| v.as_object_mut()) {
+ let path = path.extend(k);
+ for (propname, v) in map.iter_mut() {
+ let new_value = property_fixups::walk_properties(
+ propname,
+ v.clone(),
+ path.extend(propname),
+ )?;
+ *v = Self::fixup_subschema(new_value, true, path.extend(propname))?;
+ }
+ }
+ }
+
+ let subschema = do_fixup::<Fixup201909>("", subschema, &path)?;
+
+ Ok(subschema)
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_int.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_int.rs
new file mode 100644
index 0000000..94a1bdc
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_int.rs
@@ -0,0 +1,79 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::HashMap;
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::util::IsSchema};
+
+use super::Fixup;
+
+pub struct SingleIntFixup {
+ map: serde_json::Map<String, serde_json::Value>,
+}
+
+/// Convert a single int value into an array.
+impl Fixup for SingleIntFixup {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ _path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ match value.as_object() {
+ Some(o) => Ok(Some(SingleIntFixup { map: o.clone() })),
+ None => Ok(None),
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ if self.map.is_int_schema() {
+ let mut values = HashMap::new();
+ for key in ["const", "enum", "minimum", "maximum"] {
+ if let Some(value) = self.map.remove(key) {
+ values.insert(key.to_owned(), value);
+ }
+ }
+ self.map
+ .insert("items".to_owned(), json!([{ "items": [values] }]));
+ }
+
+ Ok(self.map.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_single_int_becomes_many() {
+ let single_int = json!({
+ "const": 4
+ });
+
+ let result = SingleIntFixup::new("", &single_int, JsonPath::new())
+ .expect("Valid schema")
+ .unwrap()
+ .fixup()
+ .expect("Fixup succeeds");
+ assert_eq!(result, json!({"items": [{"items": [{"const": 4}]}]}));
+ }
+
+ #[test]
+ fn test_int_list_ignored() {
+ let int_list = json!({
+ "minItems": 2,
+ "maxItems": 3,
+ });
+
+ let result = SingleIntFixup::new("", &int_list, JsonPath::new())
+ .expect("Valid schema")
+ .unwrap()
+ .fixup()
+ .expect("Fixup succeeds");
+ assert_eq!(result, int_list);
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_string.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_string.rs
new file mode 100644
index 0000000..92dc3a4
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_string.rs
@@ -0,0 +1,77 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::HashMap;
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::util::IsSchema};
+
+use super::Fixup;
+
+pub struct SingleStringFixup {
+ map: serde_json::Map<String, serde_json::Value>,
+}
+
+/// Convert a string into an array.
+impl Fixup for SingleStringFixup {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ _path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ match value.as_object() {
+ Some(o) => Ok(Some(SingleStringFixup { map: o.clone() })),
+ None => Ok(None),
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ if self.map.is_string_schema() {
+ let mut values = HashMap::new();
+ for key in ["const", "enum", "pattern"] {
+ if let Some(value) = self.map.remove(key) {
+ values.insert(key.to_owned(), value);
+ }
+ }
+ self.map.insert("items".to_owned(), json!(values));
+ }
+
+ Ok(self.map.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_single_string_becomes_many() {
+ let single_string = json!({
+ "const": "a-string"
+ });
+
+ let result = SingleStringFixup::new("", &single_string, JsonPath::new())
+ .expect("Valid schema")
+ .unwrap()
+ .fixup()
+ .expect("Fixup succeeds");
+ assert_eq!(result, json!({"items": {"const": "a-string"}}));
+ }
+
+ #[test]
+ fn test_string_list_ignored() {
+ let string_list = json!({
+ "minItems": 2,
+ "maxItems": 3,
+ });
+
+ let result = SingleStringFixup::new("", &string_list, JsonPath::new())
+ .expect("Valid schema")
+ .unwrap()
+ .fixup()
+ .expect("Fixup succeeds");
+ assert_eq!(result, string_list);
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/variable_matrix_fixup.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/variable_matrix_fixup.rs
new file mode 100644
index 0000000..afc7dc7
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/variable_matrix_fixup.rs
@@ -0,0 +1,94 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::array::Array};
+
+use super::Fixup;
+
+pub struct VariableIntMatrixFixup {
+ object: serde_json::Map<String, serde_json::Value>,
+}
+
+impl Fixup for VariableIntMatrixFixup {
+ fn new(
+ _propname: &str,
+ value: &serde_json::Value,
+ _path: JsonPath,
+ ) -> Result<Option<Self>, super::FixupError> {
+ if let Some(object) = value.as_object() {
+ Ok(Some(VariableIntMatrixFixup {
+ object: object.clone(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+ let items: Array = serde_json::from_value(self.object.clone().into()).map_err(|e| {
+ println!("{:?}", self.object);
+ e
+ })?;
+ if !items.is_matrix() {
+ return Ok(self.object.into());
+ }
+
+ let inner_dim = items.get_child_dim()?;
+ let outer_dim = items.get_dim();
+
+ if outer_dim[0] != outer_dim[1] && inner_dim[0] != inner_dim[1] {
+ self.object.remove("items");
+ self.object.remove("maxItems");
+ self.object.remove("minItems");
+ self.object.insert("type".to_owned(), json!("array"));
+ }
+
+ Ok(self.object.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_variable_matrix_fixup() {
+ let matrix = json!({
+ "items": {
+ "minItems": 2,
+ "maxItems": 3,
+ },
+ "minItems": 1,
+ "maxItems": 4,
+ });
+
+ let result = VariableIntMatrixFixup::new("", &matrix, JsonPath::new())
+ .expect("Valid schema")
+ .unwrap()
+ .fixup()
+ .expect("Fixup ok");
+ assert_eq!(result, json!({"type": "array"}));
+ }
+
+ #[test]
+ fn test_ignores_nonvariable_matrix() {
+ let matrix = json!({
+ "items": {
+ "minItems": 2,
+ "maxItems": 2,
+ },
+ "minItems": 1,
+ "maxItems": 4,
+ });
+ let result = VariableIntMatrixFixup::new("", &matrix, JsonPath::new())
+ .expect("Valid schema")
+ .unwrap()
+ .fixup()
+ .expect("Fixup ok");
+
+ assert_eq!(result, matrix);
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/property_type.rs b/third_party/rust_crates/forks/dt-schema/src/validator/property_type.rs
new file mode 100644
index 0000000..0fde771
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/property_type.rs
@@ -0,0 +1,301 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde::Serialize;
+
+use super::error::ValidatorError;
+use std::str::FromStr;
+
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)]
+/// See types.yaml in the dt-schema repo.
+pub enum PropertyType {
+ Flag,
+ NonUniqueStringArray,
+ StringArray,
+ String,
+
+ Uint8Item,
+ Uint8Matrix,
+ Uint8Array,
+ Uint8,
+ Int8Item,
+ Int8Matrix,
+ Int8Array,
+ Int8,
+
+ Uint16Item,
+ Uint16Matrix,
+ Uint16Array,
+ Uint16,
+ Int16Item,
+ Int16Matrix,
+ Int16Array,
+ Int16,
+
+ Cell,
+ Uint32Matrix,
+ Uint32Array,
+ Uint32,
+ Int32Item,
+ Int32Matrix,
+ Int32Array,
+ Int32,
+
+ Uint64Item,
+ Uint64Matrix,
+ Uint64Array,
+ Uint64,
+ Int64Item,
+ Int64Matrix,
+ Int64Array,
+ Int64,
+
+ Phandle,
+ PhandleArray,
+
+ Node,
+}
+
+impl PropertyType {
+ pub fn is_matrix(&self) -> bool {
+ matches!(
+ self,
+ PropertyType::PhandleArray
+ | PropertyType::Int8Matrix
+ | PropertyType::Int16Matrix
+ | PropertyType::Int32Matrix
+ | PropertyType::Int64Matrix
+ | PropertyType::Uint8Matrix
+ | PropertyType::Uint16Matrix
+ | PropertyType::Uint32Matrix
+ | PropertyType::Uint64Matrix
+ )
+ }
+
+ pub fn is_looser(&self, other: &PropertyType) -> bool {
+ match self {
+ PropertyType::Int8 => {
+ other == &PropertyType::Int8
+ || other == &PropertyType::Int8Item
+ || other == &PropertyType::Int8Array
+ || other == &PropertyType::Int8Matrix
+ }
+
+ PropertyType::Int16 => {
+ other == &PropertyType::Int16
+ || other == &PropertyType::Int16Item
+ || other == &PropertyType::Int16Array
+ || other == &PropertyType::Int16Matrix
+ }
+ PropertyType::Int32 => {
+ other == &PropertyType::Int32
+ || other == &PropertyType::Int32Item
+ || other == &PropertyType::Int32Array
+ || other == &PropertyType::Int32Matrix
+ }
+ PropertyType::Int64 => {
+ other == &PropertyType::Int64
+ || other == &PropertyType::Int64Item
+ || other == &PropertyType::Int64Array
+ || other == &PropertyType::Int64Matrix
+ }
+ PropertyType::Uint8 => {
+ other == &PropertyType::Uint8
+ || other == &PropertyType::Uint8Item
+ || other == &PropertyType::Uint8Array
+ || other == &PropertyType::Uint8Matrix
+ }
+
+ PropertyType::Uint16 => {
+ other == &PropertyType::Uint16
+ || other == &PropertyType::Uint16Item
+ || other == &PropertyType::Uint16Array
+ || other == &PropertyType::Uint16Matrix
+ }
+ PropertyType::Uint32 => {
+ other == &PropertyType::Uint32
+ || other == &PropertyType::Cell
+ || other == &PropertyType::Uint32Array
+ || other == &PropertyType::Uint32Matrix
+ }
+ PropertyType::Uint64 => {
+ other == &PropertyType::Uint64
+ || other == &PropertyType::Uint64Item
+ || other == &PropertyType::Uint64Array
+ || other == &PropertyType::Uint64Matrix
+ }
+ PropertyType::Phandle => {
+ other == &PropertyType::Phandle || other == &PropertyType::PhandleArray
+ }
+ _ => false,
+ }
+ }
+
+ pub fn max_size(&self) -> Option<usize> {
+ match self {
+ PropertyType::Uint8 | PropertyType::Int8 => Some(std::mem::size_of::<u8>()),
+ PropertyType::Uint16 | PropertyType::Int16 => Some(std::mem::size_of::<u16>()),
+ PropertyType::Uint32 | PropertyType::Int32 => Some(std::mem::size_of::<u32>()),
+ PropertyType::Uint64 | PropertyType::Int64 => Some(std::mem::size_of::<u64>()),
+ PropertyType::Flag => Some(0),
+ _ => None,
+ }
+ }
+
+ pub fn bytes_per_element(&self) -> Option<usize> {
+ match self {
+ PropertyType::Uint8
+ | PropertyType::Uint8Array
+ | PropertyType::Uint8Matrix
+ | PropertyType::Int8
+ | PropertyType::Int8Array
+ | PropertyType::Int8Matrix => Some(std::mem::size_of::<u8>()),
+ PropertyType::Uint16
+ | PropertyType::Uint16Array
+ | PropertyType::Uint16Matrix
+ | PropertyType::Int16
+ | PropertyType::Int16Array
+ | PropertyType::Int16Matrix => Some(std::mem::size_of::<u16>()),
+ PropertyType::Uint32
+ | PropertyType::Uint32Array
+ | PropertyType::Uint32Matrix
+ | PropertyType::PhandleArray
+ | PropertyType::Phandle
+ | PropertyType::Int32
+ | PropertyType::Int32Array
+ | PropertyType::Int32Matrix => Some(std::mem::size_of::<u32>()),
+ PropertyType::Uint64
+ | PropertyType::Uint64Array
+ | PropertyType::Uint64Matrix
+ | PropertyType::Int64
+ | PropertyType::Int64Array
+ | PropertyType::Int64Matrix => Some(std::mem::size_of::<u64>()),
+ _ => None,
+ }
+ }
+
+ pub fn is_string(&self) -> bool {
+ matches!(
+ self,
+ PropertyType::NonUniqueStringArray | PropertyType::String | PropertyType::StringArray
+ )
+ }
+}
+
+impl FromStr for PropertyType {
+ type Err = ValidatorError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "flag" => PropertyType::Flag,
+ "non-unique-string-array" => PropertyType::NonUniqueStringArray,
+ "string-array" => PropertyType::StringArray,
+ "string" => PropertyType::String,
+
+ "uint8-item" => PropertyType::Uint8Item,
+ "uint8-matrix" => PropertyType::Uint8Matrix,
+ "uint8-array" => PropertyType::Uint8Array,
+ "uint8" => PropertyType::Uint8,
+ "int8-item" => PropertyType::Int8Item,
+ "int8-matrix" => PropertyType::Int8Matrix,
+ "int8-array" => PropertyType::Int8Array,
+ "int8" => PropertyType::Int8,
+
+ "uint16-item" => PropertyType::Uint16Item,
+ "uint16-matrix" => PropertyType::Uint16Matrix,
+ "uint16-array" => PropertyType::Uint16Array,
+ "uint16" => PropertyType::Uint16,
+ "int16-item" => PropertyType::Int16Item,
+ "int16-matrix" => PropertyType::Int16Matrix,
+ "int16-array" => PropertyType::Int16Array,
+ "int16" => PropertyType::Int16,
+
+ "cell" => PropertyType::Cell,
+ "uint32-matrix" => PropertyType::Uint32Matrix,
+ "uint32-array" => PropertyType::Uint32Array,
+ "uint32" => PropertyType::Uint32,
+ "int32-item" => PropertyType::Int32Item,
+ "int32-matrix" => PropertyType::Int32Matrix,
+ "int32-array" => PropertyType::Int32Array,
+ "int32" => PropertyType::Int32,
+
+ "uint64-item" => PropertyType::Uint64Item,
+ "uint64-matrix" => PropertyType::Uint64Matrix,
+ "uint64-array" => PropertyType::Uint64Array,
+ "uint64" => PropertyType::Uint64,
+ "int64-item" => PropertyType::Int64Item,
+ "int64-matrix" => PropertyType::Int64Matrix,
+ "int64-array" => PropertyType::Int64Array,
+ "int64" => PropertyType::Int64,
+
+ "phandle" => PropertyType::Phandle,
+ "phandle-array" => PropertyType::PhandleArray,
+
+ "node" => PropertyType::Node,
+ _ => return Err(ValidatorError::UnknownPropType(s.to_owned())),
+ })
+ }
+}
+
+impl ToString for PropertyType {
+ fn to_string(&self) -> String {
+ match self {
+ PropertyType::Flag => "flag",
+ PropertyType::NonUniqueStringArray => "non-unique-string-array",
+ PropertyType::StringArray => "string-array",
+ PropertyType::String => "string",
+
+ PropertyType::Uint8Item => "uint8-item",
+ PropertyType::Uint8Matrix => "uint8-matrix",
+ PropertyType::Uint8Array => "uint8-array",
+ PropertyType::Uint8 => "uint8",
+ PropertyType::Int8Item => "int8-item",
+ PropertyType::Int8Matrix => "int8-matrix",
+ PropertyType::Int8Array => "int8-array",
+ PropertyType::Int8 => "int8",
+
+ PropertyType::Uint16Item => "uint16-item",
+ PropertyType::Uint16Matrix => "uint16-matrix",
+ PropertyType::Uint16Array => "uint16-array",
+ PropertyType::Uint16 => "uint16",
+ PropertyType::Int16Item => "int16-item",
+ PropertyType::Int16Matrix => "int16-matrix",
+ PropertyType::Int16Array => "int16-array",
+ PropertyType::Int16 => "int16",
+
+ PropertyType::Cell => "cell",
+ PropertyType::Uint32Matrix => "uint32-matrix",
+ PropertyType::Uint32Array => "uint32-array",
+ PropertyType::Uint32 => "uint32",
+ PropertyType::Int32Item => "int32-item",
+ PropertyType::Int32Matrix => "int32-matrix",
+ PropertyType::Int32Array => "int32-array",
+ PropertyType::Int32 => "int32",
+
+ PropertyType::Uint64Item => "uint64-item",
+ PropertyType::Uint64Matrix => "uint64-matrix",
+ PropertyType::Uint64Array => "uint64-array",
+ PropertyType::Uint64 => "uint64",
+ PropertyType::Int64Item => "int64-item",
+ PropertyType::Int64Matrix => "int64-matrix",
+ PropertyType::Int64Array => "int64-array",
+ PropertyType::Int64 => "int64",
+
+ PropertyType::Phandle => "phandle",
+ PropertyType::PhandleArray => "phandle-array",
+
+ PropertyType::Node => "node",
+ }
+ .to_owned()
+ }
+}
+
+impl Serialize for PropertyType {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ serializer.serialize_str(&self.to_string())
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/property_type_info.rs b/third_party/rust_crates/forks/dt-schema/src/validator/property_type_info.rs
new file mode 100644
index 0000000..8f5a385
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/property_type_info.rs
@@ -0,0 +1,222 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use fancy_regex::Regex;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::str::FromStr;
+
+use crate::path::JsonPath;
+
+use super::{
+ array::Array, error::ValidatorError, property_type::PropertyType, GeneratedPropertyType, Schema,
+};
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(untagged)]
+pub enum SchemaType {
+ Single(String),
+ Many(Vec<String>),
+}
+
+impl SchemaType {
+ pub fn is_exactly(&self, want: &str) -> bool {
+ match self {
+ SchemaType::Single(v) => v == want,
+ SchemaType::Many(_) => false,
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+/// Represents the structured data we need to determine the type of a schema.
+pub struct PropertyTypeInfo {
+ r#type: Option<SchemaType>,
+
+ #[serde(rename = "$ref")]
+ dollar_ref: Option<String>,
+
+ #[serde(flatten)]
+ array: Array,
+ dim: Option<Vec<Vec<u32>>>,
+
+ // These are actually used by |Schema| to recurse.
+ // TODO(simonshields): consider making Schema just poke |etc| directly.
+ pub one_of: Option<Vec<serde_json::Value>>,
+ pub any_of: Option<Vec<serde_json::Value>>,
+ pub all_of: Option<Vec<serde_json::Value>>,
+
+ #[serde(flatten)]
+ pub etc: HashMap<String, serde_json::Value>,
+}
+
+impl PropertyTypeInfo {
+ /// Determine type information represented by this |PropertyTypeInfo|.
+ #[tracing::instrument(level = "debug", skip(self, schema), fields(path=%path))]
+ pub fn extract_type(
+ &self,
+ schema: &Schema,
+ name: &str,
+ is_pattern: bool,
+ path: JsonPath,
+ ) -> Result<Option<GeneratedPropertyType>, ValidatorError> {
+ if name.starts_with('$') {
+ return Ok(None);
+ }
+
+ let type_re = Regex::new(
+ "(flag|u?int(8|16|32|64)(-(array|matrix))?|string(-array)?|phandle(-array)?)",
+ )
+ .unwrap();
+ // Determine type.
+ let prop_type = if self
+ .r#type
+ .as_ref()
+ .map(|x| x.is_exactly("object"))
+ .unwrap_or(false)
+ {
+ Some(PropertyType::Node)
+ } else if let Ok(Some(result)) =
+ type_re.find(&self.dollar_ref.clone().unwrap_or_else(|| "".to_owned()))
+ {
+ // Try and guess the reference type based on |type_re|.
+ tracing::debug!("Type found based on $ref");
+ Some(PropertyType::from_str(result.as_str())?)
+ } else if self
+ .r#type
+ .as_ref()
+ .map(|x| x.is_exactly("boolean"))
+ .unwrap_or(false)
+ {
+ Some(PropertyType::Flag)
+ } else if self.array.has_items() {
+ if self.array.is_string_schema() {
+ Some(PropertyType::StringArray)
+ } else if self.array.is_uint32_matrix(name) {
+ Some(PropertyType::Uint32Matrix)
+ } else {
+ None
+ }
+ } else if Regex::new("\\.yaml#?$")
+ .unwrap()
+ .is_match(&self.dollar_ref.clone().unwrap_or_else(|| "na".to_owned()))?
+ {
+ // Looks like a reference to another schema type.
+ Some(PropertyType::Node)
+ } else {
+ None
+ };
+
+ tracing::debug!(name = name, "Found type type={:?}", prop_type);
+
+ // If this is a matrix, determine its dimensions.
+ let dim = match prop_type.map(|v| v.is_matrix()) {
+ Some(true) => Some([self.array.get_dim(), self.array.get_child_dim()?].into()),
+ _ => None,
+ };
+
+ let new_prop = GeneratedPropertyType {
+ r#type: prop_type,
+ id: vec![schema.spec_id().clone().ok_or(ValidatorError::ExpectedKey(
+ "spec_id".to_owned(),
+ JsonPath::new(),
+ ))?]
+ .into_iter()
+ .collect(),
+ dim,
+ regex: if is_pattern {
+ Some(Regex::new(name)?)
+ } else {
+ None
+ },
+ };
+
+ Ok(Some(new_prop))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use super::*;
+ fn fake_schema() -> Schema {
+ Schema::from_value(
+ json!({
+ "$id": "fake-test-schema",
+ "properties": {
+ "my_reference_property": {
+ "$ref": "#/properties/my_other_prop",
+ },
+ "my_other_prop": {
+ "type": "boolean"
+ },
+ "my_external_ref": {
+ "$ref": "types.yaml#/definitions/string-array"
+ },
+ "implicit_string_array": {
+ "items": {
+ "enum": ["a", "b"]
+ }
+ },
+ "implicit_u32_matrix-bits": {
+ "items": {
+ "items": [{}]
+ }
+ },
+ "other_schema_ref": {
+ "$ref": "my-type.yaml"
+ }
+ }
+
+ }),
+ "test".to_owned(),
+ )
+ .unwrap()
+ }
+
+ fn get_property_decl(schema: &Schema, name: &str) -> PropertyTypeInfo {
+ serde_json::from_value(schema.properties().get(name).unwrap().clone()).unwrap()
+ }
+
+ #[test]
+ fn test_external_ref() {
+ let schema = fake_schema();
+ let val = get_property_decl(&schema, "my_external_ref")
+ .extract_type(&schema, "my_external_ref", false, JsonPath::new())
+ .unwrap();
+ assert_eq!(val.map(|v| v.r#type), Some(Some(PropertyType::StringArray)));
+ }
+
+ #[test]
+ fn test_implicit_string_array() {
+ let schema = fake_schema();
+ let val = get_property_decl(&schema, "implicit_string_array")
+ .extract_type(&schema, "implicit_string_array", false, JsonPath::new())
+ .unwrap();
+ assert_eq!(val.map(|v| v.r#type), Some(Some(PropertyType::StringArray)));
+ }
+
+ #[test]
+ fn test_implicit_u32_matrix() {
+ let schema = fake_schema();
+ let val = get_property_decl(&schema, "implicit_u32_matrix-bits")
+ .extract_type(&schema, "implicit_u32_matrix-bits", false, JsonPath::new())
+ .unwrap();
+ assert_eq!(
+ val.map(|v| v.r#type),
+ Some(Some(PropertyType::Uint32Matrix))
+ );
+ }
+
+ #[test]
+ fn test_other_schema_ref() {
+ let schema = fake_schema();
+ let val = get_property_decl(&schema, "other_schema_ref")
+ .extract_type(&schema, "other_schema_ref", false, JsonPath::new())
+ .unwrap();
+ assert_eq!(val.map(|v| v.r#type), Some(Some(PropertyType::Node)));
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/resolver.rs b/third_party/rust_crates/forks/dt-schema/src/validator/resolver.rs
new file mode 100644
index 0000000..c3e1dd5
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/resolver.rs
@@ -0,0 +1,58 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// TODO implement and wire up.
+
+use std::{collections::HashMap, sync::Arc};
+
+//use jsonschema::SchemaResolver;
+
+use super::schema::Schema;
+
+#[derive(Clone)]
+pub struct LocalOnlyResolver {
+ inner: Arc<Inner>,
+}
+
+struct Inner {
+ schemas: HashMap<String, Arc<serde_json::Value>>,
+}
+
+impl LocalOnlyResolver {
+ pub fn new(schemas: &mut dyn Iterator<Item = &Schema>) -> Self {
+ LocalOnlyResolver {
+ inner: Arc::new(Inner {
+ schemas: schemas
+ .map(|i| {
+ (
+ i.spec_id()
+ .as_ref()
+ .unwrap()
+ .trim_end_matches('#')
+ .to_owned(),
+ Arc::new(i.raw_schema()),
+ )
+ })
+ .collect(),
+ }),
+ }
+ }
+}
+
+/*
+impl SchemaResolver for LocalOnlyResolver {
+ fn resolve(
+ &self,
+ _root_schema: &serde_json::Value,
+ url: &url::Url,
+ original_reference: &str,
+ ) -> Result<std::sync::Arc<serde_json::Value>, jsonschema::SchemaResolverError> {
+ tracing::trace!("resolve {} ref={}", url, original_reference);
+ if let Some(value) = self.inner.schemas.get(url.as_str()) {
+ return Ok(value.clone());
+ }
+ return Err(anyhow::anyhow!("not supported"));
+ }
+}
+*/
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/schema.rs b/third_party/rust_crates/forks/dt-schema/src/validator/schema.rs
new file mode 100644
index 0000000..5439cb5
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/schema.rs
@@ -0,0 +1,394 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//use jsonschema::{Draft, JSONSchema};
+use serde::Deserialize;
+use serde_json::json;
+use std::collections::HashMap;
+use valico::json_schema::{Schema as ValicoSchema, SchemaVersion, ValidationState};
+
+use crate::{
+ path::JsonPath,
+ validator::{
+ property_type::PropertyType, property_type_info::PropertyTypeInfo, util::Mergeable,
+ },
+};
+
+use super::{
+ compatible::extract_node_compatibles, error::ValidatorError, resolver::LocalOnlyResolver,
+ GeneratedPropertyType,
+};
+
+#[derive(Deserialize, Debug)]
+#[serde(untagged)]
+enum AdditionalProperties {
+ Bool(bool),
+ Object(serde_json::Value),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Schema {
+ /// The select schema for this schema.
+ /// We validate this schema against a node to see if the node should be matched to the rest of this schema.
+ select: Option<serde_json::Value>,
+
+ /// Properties with regular expression-matched names.
+ #[serde(default)]
+ pattern_properties: HashMap<String, serde_json::Value>,
+ /// Properties with constant names.
+ #[serde(default)]
+ properties: HashMap<String, serde_json::Value>,
+
+ /// Additional properties.
+ additional_properties: Option<AdditionalProperties>,
+
+ #[serde(rename = "$id")]
+ spec_id: Option<String>,
+
+ /// Other data. Because properties are able to reference things in the schema,
+ /// we manually deserialize the schema object into this hashmap.
+ #[serde(skip)]
+ etc: serde_json::Map<String, serde_json::Value>,
+
+ #[serde(skip)]
+ select_id: Option<url::Url>,
+
+ #[serde(skip)]
+ schema_id: Option<url::Url>,
+
+ #[serde(skip)]
+ source_file: String,
+}
+
+impl Schema {
+ pub fn from_value(
+ value: serde_json::Value,
+ source_file: String,
+ ) -> Result<Self, ValidatorError> {
+ let mut ret: Schema = serde_json::from_value(value.clone())?;
+ ret.etc = serde_json::from_value(value)?;
+ ret.source_file = source_file;
+ Ok(ret)
+ }
+
+ pub fn spec_id(&self) -> &Option<String> {
+ &self.spec_id
+ }
+
+ pub fn source_file(&self) -> &String {
+ &self.source_file
+ }
+
+ #[cfg(test)]
+ pub fn properties(&self) -> &HashMap<String, serde_json::Value> {
+ &self.properties
+ }
+
+ pub(super) fn raw_schema(&self) -> serde_json::Value {
+ self.etc.clone().into()
+ }
+
+ fn generate_select(&mut self) -> Result<Option<serde_json::Value>, ValidatorError> {
+ if self.select.is_some() {
+ return Ok(None);
+ }
+
+ if self.properties.is_empty() {
+ return Ok(Some(json!(false)));
+ }
+
+ if let Some(compatibles) = self.properties.get("compatible") {
+ let mut compatible_list = extract_node_compatibles(compatibles);
+ // Remove meaningless compatibles.
+ compatible_list.remove("syscon");
+ compatible_list.remove("simple-mfd");
+
+ if !compatible_list.is_empty() {
+ let mut compatible_list = compatible_list.into_iter().collect::<Vec<_>>();
+ compatible_list.sort();
+ return Ok(Some(json!({
+ "required": ["compatible"],
+ "properties": {
+ "compatible": {
+ "contains": {
+ "enum": compatible_list,
+ }
+ }
+ }
+ })));
+ }
+ }
+
+ // Find a more interesting nodename schema than just "$nodename: true"
+ if let Some(nodename) = self
+ .properties
+ .get("$nodename")
+ .filter(|&v| !v.as_bool().unwrap_or(false))
+ {
+ return Ok(Some(json!({
+ "required": ["$nodename"],
+ "properties": {
+ "$nodename": nodename,
+ }
+ })));
+ }
+
+ Ok(Some(json!(false)))
+ }
+
+ pub fn fixup_select_and_finalise(
+ &mut self,
+ resolver: LocalOnlyResolver,
+ scope: &mut valico::json_schema::Scope,
+ ) -> Result<(), ValidatorError> {
+ let select = self.generate_select()?;
+ if let Some(new_select) = select {
+ self.select = Some(new_select);
+ }
+
+ if let Some(ref select) = self.select {
+ self.etc.insert("select".to_owned(), select.clone());
+ let mut scope_url =
+ url::Url::parse(self.spec_id.as_ref().expect("have spec id")).unwrap();
+ scope_url.set_fragment(Some("/select"));
+ self.select_id = Some(scope_url);
+ }
+
+ self.schema_id = Some(scope.compile(self.etc.clone().into(), false)?);
+ Ok(())
+ }
+
+ pub fn applies(&self, value: &serde_json::Value, scope: &valico::json_schema::Scope) -> bool {
+ self.select_id
+ .as_ref()
+ .and_then(|url| scope.resolve(url))
+ .map(|schema| schema.validate(value).is_strictly_valid())
+ .unwrap_or(false)
+ }
+
+ pub fn validate<'a>(
+ &'a self,
+ value: &'a serde_json::Value,
+ path: &str,
+ scope: &valico::json_schema::Scope,
+ ) -> Option<ValidationState> {
+ scope
+ .resolve(self.schema_id.as_ref().unwrap())
+ .map(|v| v.validate_in(value, path))
+ }
+
+ fn resolve(&self, path: &str) -> Result<Option<&serde_json::Value>, ValidatorError> {
+ tracing::debug!("resolve {}", path);
+ if path.starts_with("#/") {
+ let mut iter = path.split('/').skip(1);
+ let path = iter
+ .next()
+ .ok_or(ValidatorError::InvalidReference(path.to_owned()))?;
+ let mut s = self
+ .etc
+ .get(path)
+ .ok_or(ValidatorError::InvalidReference(path.to_owned()))?;
+ for p in iter {
+ s = s
+ .as_object()
+ .and_then(|obj| obj.get(p))
+ .ok_or(ValidatorError::InvalidReference(path.to_owned()))?;
+ }
+ Ok(Some(s))
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Determine type of a single property.
+ /// |k|: name of property
+ /// |v|: value of property
+ /// |prop_types|: map of prop-name to list of types for that property so far.
+ /// |is_pattern|: true if this is in `patternProperties`.
+ fn handle_one_property<'a>(
+ &self,
+ root_schema: &'a Schema,
+ k: &String,
+ mut v: &'a serde_json::Value,
+ prop_types: &mut HashMap<String, Vec<GeneratedPropertyType>>,
+ is_pattern: bool,
+ path: JsonPath,
+ ) -> Result<(), ValidatorError> {
+ // If there's a reference to some other type in the schema, pull the information from there.
+ // Note that we only support references within the same file.
+ // There's logic in |PropertyTypeInfo::extract_type| to "guess" what type a reference to another file refers to.
+ while let Some(reffed) = v
+ .as_object()
+ .and_then(|e| e.get("$ref"))
+ .and_then(|v| v.as_str())
+ .map(|v| root_schema.resolve(v))
+ {
+ let reffed = match reffed? {
+ Some(r) => r,
+ None => break,
+ };
+
+ v = reffed;
+ }
+ if !v.is_object() {
+ return Ok(());
+ }
+ let info: PropertyTypeInfo = serde_json::from_value(v.clone())?;
+ // Recurse into all/any/one of:
+ for (key, item) in [
+ ("allOf", &info.all_of),
+ ("anyOf", &info.any_of),
+ ("oneOf", &info.one_of),
+ ]
+ .into_iter()
+ .filter(|(_, v)| v.is_some())
+ {
+ for (index, value) in item.as_ref().unwrap().iter().enumerate() {
+ self.handle_one_property(
+ root_schema,
+ k,
+ value,
+ prop_types,
+ is_pattern,
+ path.extend_array_index(key, index),
+ )?;
+ }
+ }
+
+ // and look for a type definition for this value.
+ let mut type_info = match info.extract_type(root_schema, k, is_pattern, path.clone())? {
+ Some(info) => info,
+ None => {
+ tracing::debug!("no type for {}", k);
+ return Ok(());
+ }
+ };
+ tracing::debug!("type for {}: {:?}", k, type_info);
+
+ // Grab the property type that corresponds to the final value.
+ let vec = match prop_types.get_mut(k) {
+ Some(v) => v,
+ None => {
+ prop_types.insert(k.clone(), vec![]);
+ prop_types.get_mut(k).unwrap()
+ }
+ };
+
+ // If we failed to infer a type, only store this value if we have no other information.
+ if type_info.r#type.is_none() {
+ if vec.is_empty() {
+ vec.push(type_info);
+ }
+ return Ok(());
+ }
+
+ // We know what the type is. Now we reconcile our "new" type with what we already know.
+ let new_type = type_info.r#type.unwrap();
+ let mut index_to_remove = None;
+ for (index, item) in vec.iter_mut().enumerate() {
+ // This has no known type, remove it.
+ if item.r#type.is_none() {
+ // remove a value with |type == None|.
+ index_to_remove = Some(index);
+ break;
+ }
+ let item_type = item.r#type.unwrap();
+
+ // Merge the two dimensions, if |item| is a matrix type.
+ if let Some(dim) = type_info.dim {
+ if item.r#type.map(|v| v.is_matrix()).unwrap_or(false) {
+ match item.dim {
+ None => item.dim = type_info.dim,
+ Some(existing) => {
+ if existing != dim {
+ item.dim = Some(existing.merge(dim));
+ }
+ }
+ }
+ }
+ }
+
+ // Already have the same or looser type, so just add our id.
+ if item_type.is_looser(&new_type) {
+ item.id.insert(self.spec_id.clone().unwrap());
+
+ if new_type == PropertyType::Node {
+ // Descend into child schemas if we haven't seen this node already.
+ break;
+ }
+ } else if new_type.is_looser(&item_type) {
+ // Replace the scalar type with looser type.
+ type_info.id.extend(item.id.iter().cloned());
+ index_to_remove = Some(index);
+ break;
+ }
+ }
+
+ // If we found an existing item, remove it.
+ if let Some(remove) = index_to_remove {
+ vec.swap_remove(remove);
+ }
+
+ // Add our new type info.
+ vec.push(type_info);
+
+ // Descend and merge the subschemas in to our discovered types.
+ for (prop_name, value) in info.etc.iter().filter(|(k, _)| {
+ k == &"properties" || k == &"additionalProperties" || k == &"patternProperties"
+ }) {
+ if let Ok(mut schema) =
+ Schema::from_value(json!({prop_name: value.clone()}), self.source_file.clone())
+ {
+ tracing::debug!("descending to {}/{}", k, prop_name);
+ if schema.spec_id.is_none() {
+ schema.spec_id = self.spec_id.clone();
+ }
+ let new_types =
+ schema.generate_property_types(Some(root_schema), path.extend(prop_name))?;
+ prop_types.merge(new_types);
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Extract property types from this schema.
+ /// Returns a mapping of String (the property or pattern for the property) to a vector of possible property types.
+ pub fn generate_property_types(
+ &self,
+ root_schema: Option<&Schema>,
+ path: JsonPath,
+ ) -> Result<HashMap<String, Vec<GeneratedPropertyType>>, ValidatorError> {
+ let root_schema = root_schema.unwrap_or(self);
+ let mut prop_types = HashMap::new();
+ if let Some(AdditionalProperties::Object(ref extras)) = self.additional_properties {
+ let mut extras = Schema::from_value(extras.clone(), self.source_file.clone())?;
+ if extras.spec_id.is_none() {
+ extras.spec_id = self.spec_id.clone();
+ }
+ prop_types = extras
+ .generate_property_types(Some(root_schema), path.extend("additionalProperties"))?;
+ }
+
+ let prop_path = path.extend("properties");
+ for (k, v) in self.properties.iter() {
+ self.handle_one_property(
+ root_schema,
+ k,
+ v,
+ &mut prop_types,
+ false,
+ prop_path.extend(k),
+ )?;
+ }
+
+ let pat_path = path.extend("patternProperties");
+ for (k, v) in self.pattern_properties.iter() {
+ self.handle_one_property(root_schema, k, v, &mut prop_types, true, pat_path.extend(k))?;
+ }
+
+ Ok(prop_types)
+ }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/util.rs b/third_party/rust_crates/forks/dt-schema/src/validator/util.rs
new file mode 100644
index 0000000..c596189
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/util.rs
@@ -0,0 +1,90 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{collections::HashMap, hash::Hash};
+
+pub trait Mergeable {
+ /// We use this to merge hashmaps with vector values.
+ /// e.g.
+ /// {"a": [1]}.merge({"a": [2]}) => {"a": [1, 2]}
+ fn merge(&mut self, other: Self);
+}
+
+impl<K: Hash + Eq, V> Mergeable for HashMap<K, Vec<V>> {
+ fn merge(&mut self, other: Self) {
+ for (k, v) in other {
+ match self.get_mut(&k) {
+ Some(value) => value.extend(v),
+ None => {
+ self.insert(k, v);
+ }
+ }
+ }
+ }
+}
+
+pub trait ContainsExt<K> {
+ fn contains_any(&self, keys: &[K]) -> bool;
+ fn contains_all(&self, keys: &[K]) -> bool;
+}
+
+impl<V> ContainsExt<&str> for HashMap<String, V> {
+ fn contains_any(&self, keys: &[&str]) -> bool {
+ keys.iter().any(|&k| self.contains_key(k))
+ }
+
+ fn contains_all(&self, keys: &[&str]) -> bool {
+ keys.iter().all(|&k| self.contains_key(k))
+ }
+}
+
+impl ContainsExt<&str> for serde_json::Map<String, serde_json::Value> {
+ fn contains_any(&self, keys: &[&str]) -> bool {
+ keys.iter().any(|&k| self.contains_key(k))
+ }
+
+ fn contains_all(&self, keys: &[&str]) -> bool {
+ keys.iter().all(|&k| self.contains_key(k))
+ }
+}
+
+pub trait IsSchema {
+ fn is_string_schema(&self) -> bool;
+ fn is_int_schema(&self) -> bool;
+}
+
+impl IsSchema for serde_json::Map<String, serde_json::Value> {
+ fn is_string_schema(&self) -> bool {
+ for (_, v) in self
+ .iter()
+ .filter(|(k, _)| *k == "const" || *k == "enum" || *k == "pattern")
+ {
+ if let Some(array) = v.as_array() {
+ if array.iter().all(|e| e.is_string()) {
+ return true;
+ }
+ } else if v.is_string() {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ fn is_int_schema(&self) -> bool {
+ for (_, v) in self
+ .iter()
+ .filter(|(k, _)| *k == "const" || *k == "enum" || *k == "minimum" || *k == "maximum")
+ {
+ if let Some(array) = v.as_array() {
+ if array.iter().all(|e| e.is_i64()) {
+ return true;
+ }
+ } else if v.is_i64() {
+ return true;
+ }
+ }
+ false
+ }
+}