diff --git a/Cargo.lock b/Cargo.lock index dc13c04..43fe49f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.51" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" +checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" [[package]] name = "async-stream" @@ -42,9 +42,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" @@ -54,19 +54,17 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake2" -version = "0.9.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "crypto-mac", "digest", - "opaque-debug", ] [[package]] @@ -77,25 +75,18 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "block-padding", "generic-array", ] -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byteorder" @@ -111,15 +102,15 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-expr" -version = "0.8.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e" +checksum = "5e068cb2806bbc15b439846dc16c5f89f8599f2c3e4d73d4449d38f9b2f0b6c5" dependencies = [ "smallvec", ] @@ -195,9 +186,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -244,13 +235,13 @@ dependencies = [ ] [[package]] -name = "crypto-mac" -version = "0.8.0" +name = "crypto-common" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array", - "subtle", + "typenum", ] [[package]] @@ -290,11 +281,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.9.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "generic-array", + "block-buffer", + "crypto-common", + "subtle", ] [[package]] @@ -303,6 +296,15 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "fnv" version = "1.0.7" @@ -336,9 +338,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -350,9 +352,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -360,15 +362,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -377,27 +379,27 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-sink" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-core", "futures-sink", @@ -409,9 +411,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -419,9 +421,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if", "libc", @@ -430,9 +432,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.14.8" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711c3632b3ebd095578a9c091418d10fed492da9443f58ebc8f45efbeb215cb0" +checksum = "96efd8a1c00d890f6b45671916e165b5e43ccec61957d443aff6d7e44f62d348" dependencies = [ "bitflags", "futures-channel", @@ -447,9 +449,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.14.0" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa" +checksum = "1d0fa5052773f5a56b8ae47dab09d040f5d9ce1311f4f99006e16e9a08269296" dependencies = [ "glib-sys", "gobject-sys", @@ -460,9 +462,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.14.8" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c515f1e62bf151ef6635f528d05b02c11506de986e43b34a5c920ef0b3796a4" +checksum = "aa570813c504bdf7539a9400180c2dd4b789a819556fb86da7226d7d1b037b49" dependencies = [ "bitflags", "futures-channel", @@ -476,16 +478,17 @@ dependencies = [ "log", "once_cell", "smallvec", + "thiserror", ] [[package]] name = "glib-macros" -version = "0.14.1" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518" +checksum = "41bfd8d227dead0829ac142454e97531b93f576d0805d779c42bfd799c65c572" dependencies = [ "anyhow", - "heck", + "heck 0.4.0", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -495,9 +498,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.14.0" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae" +checksum = "f4366377bd56697de8aaee24e673c575d2694d72e7756324ded2b0428829a7b8" dependencies = [ "libc", "system-deps", @@ -505,9 +508,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.14.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5" +checksum = "df6859463843c20cf3837e3a9069b6ab2051aeeadf4c899d33344f4aea83189a" dependencies = [ "glib-sys", "libc", @@ -533,9 +536,9 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.17.4" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a255f142048ba2c4a4dce39106db1965abe355d23f4b5335edea43a553faa4" +checksum = "8564e2f284af8050f02252c5bb563ef8cbd4363403013ad4755852f743ca8c43" dependencies = [ "bitflags", "cfg-if", @@ -549,6 +552,7 @@ dependencies = [ "num-integer", "num-rational", "once_cell", + "option-operations", "paste", "pretty-hex", "thiserror", @@ -556,9 +560,9 @@ dependencies = [ [[package]] name = "gstreamer-base-sys" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28169a7b58edb93ad8ac766f0fa12dcd36a2af4257a97ee10194c7103baf3e27" +checksum = "a083493c3c340e71fa7c66eebda016e9fafc03eb1b4804cf9b2bad61994b078e" dependencies = [ "glib-sys", "gobject-sys", @@ -569,22 +573,23 @@ dependencies = [ [[package]] name = "gstreamer-rtp" -version = "0.17.0" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99254d60381f29ce9674e4361fc777f8e40ad26ac4f9072f6d7f084447058fc" +checksum = "3d1a7fca987a2f01b555df2556ce7b64af546cb4a0c4376c84a816924278ae06" dependencies = [ "bitflags", "glib", "gstreamer", "gstreamer-rtp-sys", + "libc", "once_cell", ] [[package]] name = "gstreamer-rtp-sys" -version = "0.17.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b12af6ebd69df19b4bed0d2d38c25cc93b7b687088ab199113ffac1994dd59e" +checksum = "2afde26d03b4146a39d07f576082f97b3775e7884ef3ccef5c4b7c0917e17469" dependencies = [ "glib-sys", "gstreamer-base-sys", @@ -595,9 +600,9 @@ dependencies = [ [[package]] name = "gstreamer-sys" -version = "0.17.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a81704feeb3e8599913bdd1e738455c2991a01ff4a1780cb62200993e454cc3e" +checksum = "e3517a65d3c2e6f8905b456eba5d53bda158d664863aef960b44f651cb7d33e2" dependencies = [ "glib-sys", "gobject-sys", @@ -614,6 +619,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -631,20 +642,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", - "itoa 0.4.8", + "itoa", ] [[package]] name = "httparse" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" [[package]] name = "ident_case" @@ -681,12 +692,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.1" @@ -694,20 +699,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] -name = "jid-gst-meet" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e0935b518d535e6de070bdcfc9ab8970cefc57b94ef13b26baf6967cf65a73" +name = "jid" +version = "0.9.2" +source = "git+https://gitlab.com/xmpp-rs/xmpp-rs.git#e8359cffdc8a4596b23b4b7b3aecaf053bb46840" dependencies = [ - "minidom-gst-meet", - "serde", + "minidom", +] + +[[package]] +name = "jitsi-xmpp-parsers" +version = "0.1.0" +dependencies = [ + "jid", + "minidom", + "xmpp-parsers", ] [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -731,6 +743,7 @@ dependencies = [ "anyhow", "async-stream", "async-trait", + "base64", "bytes", "colibri", "futures", @@ -739,6 +752,7 @@ dependencies = [ "gstreamer-rtp", "hex", "itertools", + "jitsi-xmpp-parsers", "libc", "maplit", "nice-gst-meet", @@ -755,7 +769,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", - "xmpp-parsers-gst-meet", + "xmpp-parsers", ] [[package]] @@ -772,15 +786,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.112" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -831,19 +845,18 @@ dependencies = [ ] [[package]] -name = "minidom-gst-meet" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b030fa990eb06a9113da7e2f16e276a9bdcdbb372bda59b715e5b8aab8b50d4" +name = "minidom" +version = "0.14.0" +source = "git+https://gitlab.com/xmpp-rs/xmpp-rs.git#e8359cffdc8a4596b23b4b7b3aecaf053bb46840" dependencies = [ "quick-xml", ] [[package]] name = "mio" -version = "0.7.14" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", "log", @@ -910,9 +923,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.2" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3bb9a13fa32bc5aeb64150cd3f32d6cf4c748f8f8a417cce5d2eb976a8370ba" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", @@ -923,9 +936,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi", ] @@ -962,14 +975,23 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", ] +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -981,15 +1003,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "openssl" @@ -1007,15 +1023,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.16.0+1.1.1l" +version = "111.17.0+1.1.1m" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab2173f69416cf3ec12debb5823d244127d23a9b127d5a5189aa97c5fa2859f" +checksum = "05d6a336abd10814198f66e2a91ccd7336611f30334119ca8ce300536666fcf4" dependencies = [ "cc", ] @@ -1035,28 +1051,35 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.11.2" +name = "option-operations" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "95d6113415f41b268f1195907427519769e40ee6f28cbb053795098a2c16f447" +dependencies = [ + "paste", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-sys", ] [[package]] @@ -1067,13 +1090,11 @@ checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "pem" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06673860db84d02a63942fa69cd9543f2624a5df3aea7f33173048fa7ad5cf1a" +checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947" dependencies = [ "base64", - "once_cell", - "regex", ] [[package]] @@ -1084,9 +1105,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -1102,9 +1123,9 @@ checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty-hex" @@ -1114,9 +1135,9 @@ checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" [[package]] name = "proc-macro-crate" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", @@ -1148,41 +1169,40 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quick-xml" -version = "0.20.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26aab6b48e2590e4a64d1ed808749ba06257882b461d01ca71baeb747074a6dd" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -1204,50 +1224,26 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "rcgen" -version = "0.8.14" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5911d1403f4143c9d56a702069d593e8d0f3fab880a85e103604d0893ea31ba7" +checksum = "d7fa2d386df8533b02184941c76ae2e0d0c1d053f5d43339169d80f21275fc5e" dependencies = [ - "chrono", "ring", + "time", "yasna", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" dependencies = [ "bitflags", ] -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1274,9 +1270,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ "log", "ring", @@ -1345,9 +1341,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.3.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -1358,9 +1354,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -1368,18 +1364,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.131" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.131" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -1388,20 +1384,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ - "itoa 1.0.1", + "itoa", "ryu", "serde", ] [[package]] name = "serde_with" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6056b4cb69b6e43e3a0f055def223380baecc99da683884f205bf347f7c4b3" +checksum = "ec1e6ec4d8950e5b1e894eac0d360742f3b1407a6078a604a731c4b3f49cefbc" dependencies = [ "rustversion", "serde", @@ -1422,40 +1418,34 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.8" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ - "block-buffer", "cfg-if", "cpufeatures", "digest", - "opaque-debug", ] [[package]] name = "sha2" -version = "0.9.8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ - "block-buffer", "cfg-if", "cpufeatures", "digest", - "opaque-debug", ] [[package]] name = "sha3" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" dependencies = [ - "block-buffer", "digest", "keccak", - "opaque-debug", ] [[package]] @@ -1469,9 +1459,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook-registry" @@ -1490,9 +1480,19 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "spin" @@ -1508,9 +1508,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "structopt" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ "clap", "lazy_static", @@ -1523,31 +1523,13 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", "syn", ] -[[package]] -name = "strum" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" - -[[package]] -name = "strum_macros" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "subtle" version = "2.4.1" @@ -1556,9 +1538,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", @@ -1567,31 +1549,26 @@ dependencies = [ [[package]] name = "system-deps" -version = "3.2.0" +version = "6.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6" +checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" dependencies = [ - "anyhow", "cfg-expr", - "heck", - "itertools", + "heck 0.4.0", "pkg-config", - "strum", - "strum_macros", - "thiserror", "toml", "version-compare", ] [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", + "fastrand", "libc", - "rand", "redox_syscall", "remove_dir_all", "winapi", @@ -1628,13 +1605,23 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "libc", + "num_threads", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -1652,11 +1639,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.14.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ - "autocfg", "bytes", "libc", "memchr", @@ -1665,15 +1651,16 @@ dependencies = [ "once_cell", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "winapi", ] [[package]] name = "tokio-macros" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -1692,9 +1679,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4baa378e417d780beff82bf54ceb0d195193ea6a00c14e22359e7f39456b5689" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" dependencies = [ "rustls", "tokio", @@ -1714,9 +1701,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e80b39df6afcc12cdf752398ade96a6b9e99c903dfdc36e53ad10b9c366bca72" +checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" dependencies = [ "futures-util", "log", @@ -1742,9 +1729,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" dependencies = [ "cfg-if", "pin-project-lite", @@ -1754,9 +1741,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" dependencies = [ "proc-macro2", "quote", @@ -1765,11 +1752,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" dependencies = [ "lazy_static", + "valuable", ] [[package]] @@ -1785,9 +1773,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245da694cc7fc4729f3f418b304cb57789f1bed2a78c575407ab8a23f53cb4d3" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" dependencies = [ "parking_lot", "sharded-slab", @@ -1799,9 +1787,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.16.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" dependencies = [ "base64", "byteorder", @@ -1821,9 +1809,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-bidi" @@ -1842,9 +1830,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -1891,6 +1879,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1899,15 +1893,15 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version-compare" -version = "0.0.11" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" +checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" @@ -1917,9 +1911,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1927,9 +1921,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -1942,9 +1936,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1952,9 +1946,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -1965,15 +1959,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" dependencies = [ "js-sys", "wasm-bindgen", @@ -1991,9 +1985,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c475786c6f47219345717a043a37ec04cb4bc185e28853adcc4fa0a947eba630" +checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" dependencies = [ "webpki", ] @@ -2021,15 +2015,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "xmpp-parsers-gst-meet" -version = "0.18.2" +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + +[[package]] +name = "xmpp-parsers" +version = "0.18.1" +source = "git+https://gitlab.com/xmpp-rs/xmpp-rs.git#e8359cffdc8a4596b23b4b7b3aecaf053bb46840" dependencies = [ "base64", "blake2", "chrono", "digest", - "jid-gst-meet", - "minidom-gst-meet", + "jid", + "minidom", "sha-1", "sha2", "sha3", @@ -2037,9 +2075,9 @@ dependencies = [ [[package]] name = "yasna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +checksum = "346d34a236c9d3e5f3b9b74563f238f955bbd05fa0b8b4efa53c130c43982f4c" dependencies = [ - "chrono", + "time", ] diff --git a/Cargo.toml b/Cargo.toml index 47ae19e..1f7e36e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,9 @@ members = [ "lib-gst-meet-c", "nice-gst-meet", "nice-gst-meet-sys", + "jitsi-xmpp-parsers", ] + +[patch.crates-io] +jid = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git" } +minidom = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git" } diff --git a/gst-meet/Cargo.toml b/gst-meet/Cargo.toml index e154955..afa6f39 100644 --- a/gst-meet/Cargo.toml +++ b/gst-meet/Cargo.toml @@ -10,8 +10,8 @@ authors = ["Jasper Hugo "] anyhow = { version = "1", default-features = false, features = ["std"] } colibri = { version = "0.1", default-features = false } futures = { version = "0.3", default-features = false } -glib = { version = "0.14", default-features = false, features = ["log"] } -gstreamer = { version = "0.17", default-features = false, features = ["v1_16"] } +glib = { version = "0.15", default-features = false, features = ["log"] } +gstreamer = { version = "0.18", default-features = false, features = ["v1_20"] } lib-gst-meet = { version = "0.4", path = "../lib-gst-meet", default-features = false, features = ["tracing-subscriber"] } structopt = { version = "0.3", default-features = false } tokio = { version = "1", default-features = false, features = ["macros", "rt-multi-thread", "signal", "sync", "time"] } diff --git a/gst-meet/src/main.rs b/gst-meet/src/main.rs index 983479e..06d216d 100644 --- a/gst-meet/src/main.rs +++ b/gst-meet/src/main.rs @@ -270,7 +270,7 @@ async fn main_inner() -> Result<()> { bin.set_property( "name", format!("participant_{}", participant.muc_jid.resource), - )?; + ); conference.add_bin(&bin).await?; } else { diff --git a/jitsi-xmpp-parsers/Cargo.toml b/jitsi-xmpp-parsers/Cargo.toml new file mode 100644 index 0000000..96036b9 --- /dev/null +++ b/jitsi-xmpp-parsers/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "jitsi-xmpp-parsers" +description = "Provides types for non-standard XMPP elements used by Jitsi Meet" +version = "0.1.0" +edition = "2018" +license = "MPL-2.0" +readme = "../README.md" +repository = "https://github.com/avstack/gst-meet" +documentation = "https://docs.rs/jitsi-xmpp-parsers/" +authors = ["Jasper Hugo "] + +[dependencies] +jid = { version = "0.9", default-features = false, features = ["minidom"] } +minidom = { version = "0.14", default-features = false } +xmpp-parsers = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git", default-features = false, features = ["disable-validation"] } + +[package.metadata.docs.rs] +rustdoc-args = [ "--sort-modules-by-appearance", "-Zunstable-options" ] diff --git a/jitsi-xmpp-parsers/src/helpers.rs b/jitsi-xmpp-parsers/src/helpers.rs new file mode 100644 index 0000000..919c323 --- /dev/null +++ b/jitsi-xmpp-parsers/src/helpers.rs @@ -0,0 +1,26 @@ +use xmpp_parsers::Error; + +/// Codec for colon-separated bytes of uppercase hexadecimal. +pub struct ColonSeparatedHex; + +impl ColonSeparatedHex { + pub fn decode(s: &str) -> Result, Error> { + let mut bytes = vec![]; + for i in 0..(1 + s.len()) / 3 { + let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?; + if 3 * i + 2 < s.len() { + assert_eq!(&s[3 * i + 2..3 * i + 3], ":"); + } + bytes.push(byte); + } + Ok(bytes) + } + + pub fn encode(b: &[u8]) -> Option { + let mut bytes = vec![]; + for byte in b { + bytes.push(format!("{:02X}", byte)); + } + Some(bytes.join(":")) + } +} diff --git a/jitsi-xmpp-parsers/src/jingle.rs b/jitsi-xmpp-parsers/src/jingle.rs new file mode 100644 index 0000000..ef4a297 --- /dev/null +++ b/jitsi-xmpp-parsers/src/jingle.rs @@ -0,0 +1,381 @@ +use std::convert::TryFrom; + +use jid::Jid; +use xmpp_parsers::{ + iq::IqSetPayload, + jingle::{ContentId, Creator, Disposition, ReasonElement, Senders, SessionId}, + jingle_ibb::Transport as IbbTransport, + jingle_grouping::Group, + jingle_s5b::Transport as Socks5Transport, + ns::{JINGLE, JINGLE_GROUPING, JINGLE_IBB, JINGLE_ICE_UDP, JINGLE_RTP, JINGLE_S5B}, + Element, + Error, +}; + +use crate::{ + jingle_ice_udp::Transport as IceUdpTransport, + jingle_rtp::Description as RtpDescription, +}; + +generate_attribute!( + /// The action attribute. + Action, "action", { + /// Accept a content-add action received from another party. + ContentAccept => "content-accept", + + /// Add one or more new content definitions to the session. + ContentAdd => "content-add", + + /// Change the directionality of media sending. + ContentModify => "content-modify", + + /// Reject a content-add action received from another party. + ContentReject => "content-reject", + + /// Remove one or more content definitions from the session. + ContentRemove => "content-remove", + + /// Exchange information about parameters for an application type. + DescriptionInfo => "description-info", + + /// Exchange information about security preconditions. + SecurityInfo => "security-info", + + /// Definitively accept a session negotiation. + SessionAccept => "session-accept", + + /// Send session-level information, such as a ping or a ringing message. + SessionInfo => "session-info", + + /// Request negotiation of a new Jingle session. + SessionInitiate => "session-initiate", + + /// End an existing session. + SessionTerminate => "session-terminate", + + /// Accept a transport-replace action received from another party. + TransportAccept => "transport-accept", + + /// Exchange transport candidates. + TransportInfo => "transport-info", + + /// Reject a transport-replace action received from another party. + TransportReject => "transport-reject", + + /// Redefine a transport method or replace it with a different method. + TransportReplace => "transport-replace", + + /// --- Non-standard values used by Jitsi Meet: --- + + /// Add a source to existing content. + SourceAdd => "source-add", + } +); + +/// The main Jingle container, to be included in an iq stanza. +#[derive(Debug, Clone, PartialEq)] +pub struct Jingle { + /// The action to execute on both ends. + pub action: Action, + + /// Who the initiator is. + pub initiator: Option, + + /// Who the responder is. + pub responder: Option, + + /// Unique session identifier between two entities. + pub sid: SessionId, + + /// A list of contents to be negotiated in this session. + pub contents: Vec, + + /// An optional reason. + pub reason: Option, + + /// An optional grouping. + pub group: Option, + + /// Payloads to be included. + pub other: Vec, +} + +impl IqSetPayload for Jingle {} + +impl Jingle { + /// Create a new Jingle element. + pub fn new(action: Action, sid: SessionId) -> Jingle { + Jingle { + action, + sid, + initiator: None, + responder: None, + contents: Vec::new(), + reason: None, + group: None, + other: Vec::new(), + } + } + + /// Set the initiator’s JID. + pub fn with_initiator(mut self, initiator: Jid) -> Jingle { + self.initiator = Some(initiator); + self + } + + /// Set the responder’s JID. + pub fn with_responder(mut self, responder: Jid) -> Jingle { + self.responder = Some(responder); + self + } + + /// Add a content to this Jingle container. + pub fn add_content(mut self, content: Content) -> Jingle { + self.contents.push(content); + self + } + + /// Set the reason in this Jingle container. + pub fn set_reason(mut self, reason: ReasonElement) -> Jingle { + self.reason = Some(reason); + self + } + + /// Set the grouping in this Jingle container. + pub fn set_group(mut self, group: Group) -> Jingle { + self.group = Some(group); + self + } +} + +impl TryFrom for Jingle { + type Error = Error; + + fn try_from(root: Element) -> Result { + check_self!(root, "jingle", JINGLE, "Jingle"); + + let mut jingle = Jingle { + action: get_attr!(root, "action", Required), + initiator: get_attr!(root, "initiator", Option), + responder: get_attr!(root, "responder", Option), + sid: get_attr!(root, "sid", Required), + contents: vec![], + reason: None, + group: None, + other: vec![], + }; + + for child in root.children().cloned() { + if child.is("content", JINGLE) { + let content = Content::try_from(child)?; + jingle.contents.push(content); + } else if child.is("reason", JINGLE) { + if jingle.reason.is_some() { + return Err(Error::ParseError( + "Jingle must not have more than one reason.", + )); + } + let reason = ReasonElement::try_from(child)?; + jingle.reason = Some(reason); + } else if child.is("group", JINGLE_GROUPING) { + if jingle.group.is_some() { + return Err(Error::ParseError( + "Jingle must not have more than one grouping.", + )); + } + let group = Group::try_from(child)?; + jingle.group = Some(group); + } else { + jingle.other.push(child); + } + } + + Ok(jingle) + } +} + +impl From for Element { + fn from(jingle: Jingle) -> Element { + Element::builder("jingle", JINGLE) + .attr("action", jingle.action) + .attr("initiator", jingle.initiator) + .attr("responder", jingle.responder) + .attr("sid", jingle.sid) + .append_all(jingle.contents) + .append_all(jingle.reason.map(Element::from)) + .append_all(jingle.group.map(Element::from)) + .build() + } +} + +/// Enum wrapping all of the various supported descriptions of a Content. +#[derive(Debug, Clone, PartialEq)] +pub enum Description { + /// Jingle RTP Sessions (XEP-0167) description. + Rtp(RtpDescription), + + /// To be used for any description that isn’t known at compile-time. + Unknown(Element), +} + +impl TryFrom for Description { + type Error = Error; + + fn try_from(elem: Element) -> Result { + Ok(if elem.is("description", JINGLE_RTP) { + Description::Rtp(RtpDescription::try_from(elem)?) + } else { + Description::Unknown(elem) + }) + } +} + +impl From for Description { + fn from(desc: RtpDescription) -> Description { + Description::Rtp(desc) + } +} + +impl From for Element { + fn from(desc: Description) -> Element { + match desc { + Description::Rtp(desc) => desc.into(), + Description::Unknown(elem) => elem, + } + } +} + +/// Enum wrapping all of the various supported transports of a Content. +#[derive(Debug, Clone, PartialEq)] +pub enum Transport { + /// Jingle ICE-UDP Bytestreams (XEP-0176) transport. + IceUdp(IceUdpTransport), + + /// Jingle In-Band Bytestreams (XEP-0261) transport. + Ibb(IbbTransport), + + /// Jingle SOCKS5 Bytestreams (XEP-0260) transport. + Socks5(Socks5Transport), + + /// To be used for any transport that isn’t known at compile-time. + Unknown(Element), +} + +impl TryFrom for Transport { + type Error = Error; + + fn try_from(elem: Element) -> Result { + Ok(if elem.is("transport", JINGLE_ICE_UDP) { + Transport::IceUdp(IceUdpTransport::try_from(elem)?) + } else if elem.is("transport", JINGLE_IBB) { + Transport::Ibb(IbbTransport::try_from(elem)?) + } else if elem.is("transport", JINGLE_S5B) { + Transport::Socks5(Socks5Transport::try_from(elem)?) + } else { + Transport::Unknown(elem) + }) + } +} + +impl From for Transport { + fn from(transport: IceUdpTransport) -> Transport { + Transport::IceUdp(transport) + } +} + +impl From for Transport { + fn from(transport: IbbTransport) -> Transport { + Transport::Ibb(transport) + } +} + +impl From for Transport { + fn from(transport: Socks5Transport) -> Transport { + Transport::Socks5(transport) + } +} + +impl From for Element { + fn from(transport: Transport) -> Element { + match transport { + Transport::IceUdp(transport) => transport.into(), + Transport::Ibb(transport) => transport.into(), + Transport::Socks5(transport) => transport.into(), + Transport::Unknown(elem) => elem, + } + } +} + +generate_element!( + /// Describes a session’s content, there can be multiple content in one + /// session. + Content, "content", JINGLE, + attributes: [ + /// Who created this content. + creator: Option = "creator", + + /// How the content definition is to be interpreted by the recipient. + disposition: Default = "disposition", + + /// A per-session unique identifier for this content. + name: Required = "name", + + /// Who can send data for this content. + senders: Default = "senders", + ], + children: [ + /// What to send. + description: Option = ("description", *) => Description, + + /// How to send it. + transport: Option = ("transport", *) => Transport, + + /// With which security. + security: Option = ("security", JINGLE) => Element + ] +); + +impl Content { + /// Create a new content. + pub fn new(creator: Creator, name: ContentId) -> Content { + Content { + creator: Some(creator), + name, + disposition: Disposition::Session, + senders: Senders::Both, + description: None, + transport: None, + security: None, + } + } + + /// Set how the content is to be interpreted by the recipient. + pub fn with_disposition(mut self, disposition: Disposition) -> Content { + self.disposition = disposition; + self + } + + /// Specify who can send data for this content. + pub fn with_senders(mut self, senders: Senders) -> Content { + self.senders = senders; + self + } + + /// Set the description of this content. + pub fn with_description>(mut self, description: D) -> Content { + self.description = Some(description.into()); + self + } + + /// Set the transport of this content. + pub fn with_transport>(mut self, transport: T) -> Content { + self.transport = Some(transport.into()); + self + } + + /// Set the security of this content. + pub fn with_security(mut self, security: Element) -> Content { + self.security = Some(security); + self + } +} diff --git a/jitsi-xmpp-parsers/src/jingle_dtls_srtp.rs b/jitsi-xmpp-parsers/src/jingle_dtls_srtp.rs new file mode 100644 index 0000000..077e519 --- /dev/null +++ b/jitsi-xmpp-parsers/src/jingle_dtls_srtp.rs @@ -0,0 +1,47 @@ +use xmpp_parsers::{ + hashes::{Algo, Hash}, + jingle_dtls_srtp::Setup, + ns::JINGLE_DTLS, + Error, +}; + +use crate::helpers::ColonSeparatedHex; + +generate_element!( + /// Fingerprint of the key used for a DTLS handshake. + Fingerprint, "fingerprint", JINGLE_DTLS, + attributes: [ + /// The hash algorithm used for this fingerprint. + hash: Required = "hash", + + /// Indicates which of the end points should initiate the TCP connection establishment. + setup: Option = "setup" + ], + text: ( + /// Hash value of this fingerprint. + value: ColonSeparatedHex> + ) +); + +impl Fingerprint { + /// Create a new Fingerprint from a Setup and a Hash. + pub fn from_hash(setup: Setup, hash: Hash) -> Fingerprint { + Fingerprint { + hash: hash.algo, + setup: Some(setup), + value: hash.hash, + } + } + + /// Create a new Fingerprint from a Setup and parsing the hash. + pub fn from_colon_separated_hex( + setup: Setup, + algo: &str, + hash: &str, + ) -> Result { + let algo = algo.parse()?; + let hash = Hash::from_colon_separated_hex(algo, hash)?; + Ok(Fingerprint::from_hash(setup, hash)) + } +} + diff --git a/jitsi-xmpp-parsers/src/jingle_ice_udp.rs b/jitsi-xmpp-parsers/src/jingle_ice_udp.rs new file mode 100644 index 0000000..3aa7aa7 --- /dev/null +++ b/jitsi-xmpp-parsers/src/jingle_ice_udp.rs @@ -0,0 +1,60 @@ +use xmpp_parsers::{ + jingle_ice_udp::Candidate, + ns::{JINGLE_DTLS, JINGLE_ICE_UDP}, +}; + +use crate::{ + jingle_dtls_srtp::Fingerprint, + ns::JITSI_COLIBRI, +}; + +generate_element!( + /// Wrapper element for an ICE-UDP transport. + #[derive(Default)] + Transport, "transport", JINGLE_ICE_UDP, + attributes: [ + /// A Password as defined in ICE-CORE. + pwd: Option = "pwd", + + /// A User Fragment as defined in ICE-CORE. + ufrag: Option = "ufrag", + ], + children: [ + /// List of candidates for this ICE-UDP session. + candidates: Vec = ("candidate", JINGLE_ICE_UDP) => Candidate, + + /// Fingerprint of the key used for the DTLS handshake. + fingerprint: Option = ("fingerprint", JINGLE_DTLS) => Fingerprint, + + /// Details of the Colibri WebSocket + web_socket: Option = ("web-socket", JITSI_COLIBRI) => WebSocket + ] +); + +impl Transport { + /// Create a new ICE-UDP transport. + pub fn new() -> Transport { + Default::default() + } + + /// Add a candidate to this transport. + pub fn add_candidate(mut self, candidate: Candidate) -> Self { + self.candidates.push(candidate); + self + } + + /// Set the DTLS-SRTP fingerprint of this transport. + pub fn with_fingerprint(mut self, fingerprint: Fingerprint) -> Self { + self.fingerprint = Some(fingerprint); + self + } +} + +generate_element!( + /// Colibri WebSocket details + WebSocket, "web-socket", JITSI_COLIBRI, + attributes: [ + /// The WebSocket URL + url: Required = "url", + ] +); diff --git a/jitsi-xmpp-parsers/src/jingle_rtp.rs b/jitsi-xmpp-parsers/src/jingle_rtp.rs new file mode 100644 index 0000000..143d1a7 --- /dev/null +++ b/jitsi-xmpp-parsers/src/jingle_rtp.rs @@ -0,0 +1,56 @@ +use xmpp_parsers::{ + jingle_rtcp_fb::RtcpFb, + jingle_rtp::{PayloadType, RtcpMux}, + jingle_rtp_hdrext::RtpHdrext, + ns::{JINGLE_RTP, JINGLE_RTP_HDREXT, JINGLE_SSMA}, +}; + +use crate::jingle_ssma::{Group, Source}; + +generate_element!( + /// Wrapper element describing an RTP session. + Description, "description", JINGLE_RTP, + attributes: [ + /// Namespace of the encryption scheme used. + media: Required = "media", + + /// User-friendly name for the encryption scheme, should be `None` for OTR, + /// legacy OpenPGP and OX. + // XXX: is this a String or an u32?! Refer to RFC 3550. + ssrc: Option = "ssrc", + ], + children: [ + /// List of encodings that can be used for this RTP stream. + payload_types: Vec = ("payload-type", JINGLE_RTP) => PayloadType, + + /// Specifies the ability to multiplex RTP Data and Control Packets on a single port as + /// described in RFC 5761. + rtcp_mux: Option = ("rtcp-mux", JINGLE_RTP) => RtcpMux, + + /// List of ssrc-group. + ssrc_groups: Vec = ("ssrc-group", JINGLE_SSMA) => Group, + + /// List of ssrc. + ssrcs: Vec = ("source", JINGLE_SSMA) => Source, + + /// List of header extensions. + hdrexts: Vec = ("rtp-hdrext", JINGLE_RTP_HDREXT) => RtpHdrext + + // TODO: Add support for and . + ] +); + +impl Description { + /// Create a new RTP description. + pub fn new(media: String) -> Description { + Description { + media, + ssrc: None, + payload_types: Vec::new(), + rtcp_mux: None, + ssrc_groups: Vec::new(), + ssrcs: Vec::new(), + hdrexts: Vec::new(), + } + } +} diff --git a/jitsi-xmpp-parsers/src/jingle_ssma.rs b/jitsi-xmpp-parsers/src/jingle_ssma.rs new file mode 100644 index 0000000..59eefea --- /dev/null +++ b/jitsi-xmpp-parsers/src/jingle_ssma.rs @@ -0,0 +1,54 @@ +use xmpp_parsers::{jingle_ssma::{Parameter, Semantics}, ns::JINGLE_SSMA}; + +use crate::ns::JITSI_MEET; + +generate_element!( + /// Source element for the ssrc SDP attribute. + Source, "source", JINGLE_SSMA, + attributes: [ + /// Maps to the ssrc-id parameter. + id: Required = "ssrc", + ], + children: [ + /// List of attributes for this source. + parameters: Vec = ("parameter", JINGLE_SSMA) => Parameter, + + /// --- Non-standard attributes used by Jitsi Meet: --- + + /// ssrc-info for this source. + info: Option = ("ssrc-info", JITSI_MEET) => SsrcInfo + ] +); + +impl Source { + /// Create a new SSMA Source element. + pub fn new(id: u32) -> Source { + Source { + id, + parameters: Vec::new(), + info: None, + } + } +} + +generate_element!( + /// ssrc-info associated with a ssrc. + SsrcInfo, "ssrc-info", JITSI_MEET, + attributes: [ + /// The owner of the ssrc. + owner: Required = "owner" + ] +); + +generate_element!( + /// Element grouping multiple ssrc. + Group, "ssrc-group", JINGLE_SSMA, + attributes: [ + /// The semantics of this group. + semantics: Required = "semantics", + ], + children: [ + /// The various ssrc concerned by this group. + sources: Vec = ("source", JINGLE_SSMA) => Source + ] +); diff --git a/jitsi-xmpp-parsers/src/lib.rs b/jitsi-xmpp-parsers/src/lib.rs new file mode 100644 index 0000000..aa49317 --- /dev/null +++ b/jitsi-xmpp-parsers/src/lib.rs @@ -0,0 +1,10 @@ +#[macro_use] +mod macros; + +mod helpers; +pub mod jingle; +pub mod jingle_dtls_srtp; +pub mod jingle_ice_udp; +pub mod jingle_rtp; +pub mod jingle_ssma; +pub mod ns; diff --git a/jitsi-xmpp-parsers/src/macros.rs b/jitsi-xmpp-parsers/src/macros.rs new file mode 100644 index 0000000..394d5cc --- /dev/null +++ b/jitsi-xmpp-parsers/src/macros.rs @@ -0,0 +1,718 @@ +// This file is copied from xmpp-parsers + +// Copyright (c) 2017-2018 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +macro_rules! get_attr { + ($elem:ident, $attr:tt, $type:tt) => { + get_attr!($elem, $attr, $type, value, value.parse()?) + }; + ($elem:ident, $attr:tt, OptionEmpty, $value:ident, $func:expr) => { + match $elem.attr($attr) { + Some("") => None, + Some($value) => Some($func), + None => None, + } + }; + ($elem:ident, $attr:tt, Option, $value:ident, $func:expr) => { + match $elem.attr($attr) { + Some($value) => Some($func), + None => None, + } + }; + ($elem:ident, $attr:tt, Required, $value:ident, $func:expr) => { + match $elem.attr($attr) { + Some($value) => $func, + None => { + return Err(xmpp_parsers::Error::ParseError(concat!( + "Required attribute '", + $attr, + "' missing." + ))); + } + } + }; + ($elem:ident, $attr:tt, RequiredNonEmpty, $value:ident, $func:expr) => { + match $elem.attr($attr) { + Some("") => { + return Err(xmpp_parsers::Error::ParseError(concat!( + "Required attribute '", + $attr, + "' must not be empty." + ))); + } + Some($value) => $func, + None => { + return Err(xmpp_parsers::Error::ParseError(concat!( + "Required attribute '", + $attr, + "' missing." + ))); + } + } + }; + ($elem:ident, $attr:tt, Default, $value:ident, $func:expr) => { + match $elem.attr($attr) { + Some($value) => $func, + None => ::std::default::Default::default(), + } + }; +} + +macro_rules! generate_attribute { + ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}) => ( + generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+}); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}, Default = $default:ident) => ( + generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+}, Default = $default); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub enum $elem { + $( + $(#[$a_meta])* + $a + ),+ + } + impl ::std::str::FromStr for $elem { + type Err = xmpp_parsers::Error; + fn from_str(s: &str) -> Result<$elem, xmpp_parsers::Error> { + Ok(match s { + $($b => $elem::$a),+, + _ => return Err(xmpp_parsers::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), + }) + } + } + impl std::fmt::Display for $elem { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!(fmt, "{}", match self { + $($elem::$a => $b),+ + }) + } + } + impl ::minidom::IntoAttributeValue for $elem { + fn into_attribute_value(self) -> Option { + Some(String::from(match self { + $($elem::$a => $b),+ + })) + } + } + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}, Default = $default:ident) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub enum $elem { + $( + $(#[$a_meta])* + $a + ),+ + } + impl ::std::str::FromStr for $elem { + type Err = xmpp_parsers::Error; + fn from_str(s: &str) -> Result<$elem, xmpp_parsers::Error> { + Ok(match s { + $($b => $elem::$a),+, + _ => return Err(xmpp_parsers::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), + }) + } + } + impl ::minidom::IntoAttributeValue for $elem { + #[allow(unreachable_patterns)] + fn into_attribute_value(self) -> Option { + Some(String::from(match self { + $elem::$default => return None, + $($elem::$a => $b),+ + })) + } + } + impl ::std::default::Default for $elem { + fn default() -> $elem { + $elem::$default + } + } + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, ($(#[$meta_symbol:meta])* $symbol:ident => $value:tt)) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub enum $elem { + $(#[$meta_symbol])* + $symbol, + /// Value when absent. + None, + } + impl ::std::str::FromStr for $elem { + type Err = xmpp_parsers::Error; + fn from_str(s: &str) -> Result { + Ok(match s { + $value => $elem::$symbol, + _ => return Err(xmpp_parsers::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), + }) + } + } + impl ::minidom::IntoAttributeValue for $elem { + fn into_attribute_value(self) -> Option { + match self { + $elem::$symbol => Some(String::from($value)), + $elem::None => None + } + } + } + impl ::std::default::Default for $elem { + fn default() -> $elem { + $elem::None + } + } + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, bool) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub enum $elem { + /// True value, represented by either 'true' or '1'. + True, + /// False value, represented by either 'false' or '0'. + False, + } + impl ::std::str::FromStr for $elem { + type Err = xmpp_parsers::Error; + fn from_str(s: &str) -> Result { + Ok(match s { + "true" | "1" => $elem::True, + "false" | "0" => $elem::False, + _ => return Err(xmpp_parsers::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), + }) + } + } + impl ::minidom::IntoAttributeValue for $elem { + fn into_attribute_value(self) -> Option { + match self { + $elem::True => Some(String::from("true")), + $elem::False => None + } + } + } + impl ::std::default::Default for $elem { + fn default() -> $elem { + $elem::False + } + } + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $type:tt, Default = $default:expr) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub struct $elem(pub $type); + impl ::std::str::FromStr for $elem { + type Err = xmpp_parsers::Error; + fn from_str(s: &str) -> Result { + Ok($elem($type::from_str(s)?)) + } + } + impl ::minidom::IntoAttributeValue for $elem { + fn into_attribute_value(self) -> Option { + match self { + $elem($default) => None, + $elem(value) => Some(format!("{}", value)), + } + } + } + impl ::std::default::Default for $elem { + fn default() -> $elem { + $elem($default) + } + } + ); +} + +macro_rules! generate_element_enum { + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => ( + generate_element_enum!($(#[$meta])* $elem, $name, $ns, {$($(#[$enum_meta])* $enum => $enum_name),+}); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub enum $elem { + $( + $(#[$enum_meta])* + $enum + ),+ + } + impl ::std::convert::TryFrom for $elem { + type Error = xmpp_parsers::Error; + fn try_from(elem: xmpp_parsers::Element) -> Result<$elem, xmpp_parsers::Error> { + check_ns_only!(elem, $name, $ns); + Ok(match elem.name() { + $($enum_name => $elem::$enum,)+ + _ => return Err(xmpp_parsers::Error::ParseError(concat!("This is not a ", $name, " element."))), + }) + } + } + impl From<$elem> for xmpp_parsers::Element { + fn from(elem: $elem) -> xmpp_parsers::Element { + xmpp_parsers::Element::builder( + match elem { + $($elem::$enum => $enum_name,)+ + }, + $ns, + ) + .build() + } + } + ); +} + +macro_rules! generate_attribute_enum { + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => ( + generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+}); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub enum $elem { + $( + $(#[$enum_meta])* + $enum + ),+ + } + impl ::std::convert::TryFrom for $elem { + type Error = xmpp_parsers::Error; + fn try_from(elem: xmpp_parsers::Element) -> Result<$elem, xmpp_parsers::Error> { + check_ns_only!(elem, $name, $ns); + Ok(match get_attr!(elem, $attr, Required) { + $($enum_name => $elem::$enum,)+ + _ => return Err(xmpp_parsers::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))), + }) + } + } + impl From<$elem> for xmpp_parsers::Element { + fn from(elem: $elem) -> xmpp_parsers::Element { + xmpp_parsers::Element::builder($name, $ns) + .attr($attr, match elem { + $($elem::$enum => $enum_name,)+ + }) + .build() + } + } + ); +} + +macro_rules! check_self { + ($elem:ident, $name:tt, $ns:ident) => { + check_self!($elem, $name, $ns, $name); + }; + ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => { + if !$elem.is($name, $ns) { + return Err(xmpp_parsers::Error::ParseError(concat!( + "This is not a ", + $pretty_name, + " element." + ))); + } + }; +} + +macro_rules! check_ns_only { + ($elem:ident, $name:tt, $ns:ident) => { + if !$elem.has_ns($ns) { + return Err(xmpp_parsers::Error::ParseError(concat!( + "This is not a ", + $name, + " element." + ))); + } + }; +} + +macro_rules! generate_empty_element { + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub struct $elem; + + impl ::std::convert::TryFrom for $elem { + type Error = xmpp_parsers::Error; + + fn try_from(elem: xmpp_parsers::Element) -> Result<$elem, xmpp_parsers::Error> { + check_self!(elem, $name, $ns); + Ok($elem) + } + } + + impl From<$elem> for xmpp_parsers::Element { + fn from(_: $elem) -> xmpp_parsers::Element { + xmpp_parsers::Element::builder($name, $ns) + .build() + } + } + ); +} + +macro_rules! generate_id { + ($(#[$meta:meta])* $elem:ident) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct $elem(pub String); + impl ::std::str::FromStr for $elem { + type Err = xmpp_parsers::Error; + fn from_str(s: &str) -> Result<$elem, xmpp_parsers::Error> { + // TODO: add a way to parse that differently when needed. + Ok($elem(String::from(s))) + } + } + impl ::minidom::IntoAttributeValue for $elem { + fn into_attribute_value(self) -> Option { + Some(self.0) + } + } + ); +} + +macro_rules! generate_elem_id { + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => ( + generate_elem_id!($(#[$meta])* $elem, $name, $ns, String); + impl ::std::str::FromStr for $elem { + type Err = xmpp_parsers::Error; + fn from_str(s: &str) -> Result<$elem, xmpp_parsers::Error> { + // TODO: add a way to parse that differently when needed. + Ok($elem(String::from(s))) + } + } + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $type:ty) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct $elem(pub $type); + impl ::std::convert::TryFrom for $elem { + type Error = xmpp_parsers::Error; + fn try_from(elem: xmpp_parsers::Element) -> Result<$elem, xmpp_parsers::Error> { + check_self!(elem, $name, $ns); + // TODO: add a way to parse that differently when needed. + Ok($elem(elem.text().parse()?)) + } + } + impl From<$elem> for xmpp_parsers::Element { + fn from(elem: $elem) -> xmpp_parsers::Element { + xmpp_parsers::Element::builder($name, $ns) + .append(elem.0.to_string()) + .build() + } + } + ); +} + +macro_rules! decl_attr { + (OptionEmpty, $type:ty) => ( + Option<$type> + ); + (Option, $type:ty) => ( + Option<$type> + ); + (Required, $type:ty) => ( + $type + ); + (RequiredNonEmpty, $type:ty) => ( + $type + ); + (Default, $type:ty) => ( + $type + ); +} + +macro_rules! start_decl { + (Vec, $type:ty) => ( + Vec<$type> + ); + (Option, $type:ty) => ( + Option<$type> + ); + (Required, $type:ty) => ( + $type + ); + (Present, $type:ty) => ( + bool + ); +} + +macro_rules! start_parse_elem { + ($temp:ident: Vec) => { + let mut $temp = Vec::new(); + }; + ($temp:ident: Option) => { + let mut $temp = None; + }; + ($temp:ident: Required) => { + let mut $temp = None; + }; + ($temp:ident: Present) => { + let mut $temp = false; + }; +} + +macro_rules! do_parse { + ($elem:ident, Element) => { + $elem.clone() + }; + ($elem:ident, String) => { + $elem.text() + }; + ($elem:ident, $constructor:ident) => { + $constructor::try_from($elem.clone())? + }; +} + +macro_rules! do_parse_elem { + ($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { + $temp.push(do_parse!($elem, $constructor)); + }; + ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { + if $temp.is_some() { + return Err(xmpp_parsers::Error::ParseError(concat!( + "Element ", + $parent_name, + " must not have more than one ", + $name, + " child." + ))); + } + $temp = Some(do_parse!($elem, $constructor)); + }; + ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { + if $temp.is_some() { + return Err(xmpp_parsers::Error::ParseError(concat!( + "Element ", + $parent_name, + " must not have more than one ", + $name, + " child." + ))); + } + $temp = Some(do_parse!($elem, $constructor)); + }; + ($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { + if $temp { + return Err(xmpp_parsers::Error::ParseError(concat!( + "Element ", + $parent_name, + " must not have more than one ", + $name, + " child." + ))); + } + $temp = true; + }; +} + +macro_rules! finish_parse_elem { + ($temp:ident: Vec = $name:tt, $parent_name:tt) => { + $temp + }; + ($temp:ident: Option = $name:tt, $parent_name:tt) => { + $temp + }; + ($temp:ident: Required = $name:tt, $parent_name:tt) => { + $temp.ok_or(xmpp_parsers::Error::ParseError(concat!( + "Missing child ", + $name, + " in ", + $parent_name, + " element." + )))? + }; + ($temp:ident: Present = $name:tt, $parent_name:tt) => { + $temp + }; +} + +macro_rules! generate_serialiser { + ($builder:ident, $parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => { + $builder.append( + xmpp_parsers::Element::builder($name, $ns) + .append(::minidom::Node::Text($parent.$elem)), + ) + }; + ($builder:ident, $parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => { + $builder.append_all($parent.$elem.map(|elem| { + xmpp_parsers::Element::builder($name, $ns).append(::minidom::Node::Text(elem)) + })) + }; + ($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, *)) => { + $builder.append_all( + $parent + .$elem + .map(|elem| ::minidom::Node::Element(xmpp_parsers::Element::from(elem))), + ) + }; + ($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, $ns:ident)) => { + $builder.append_all( + $parent + .$elem + .map(|elem| ::minidom::Node::Element(xmpp_parsers::Element::from(elem))), + ) + }; + ($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => { + $builder.append_all($parent.$elem.into_iter()) + }; + ($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => { + $builder.append(::minidom::Node::Element( + xmpp_parsers::Element::builder($name, $ns).build(), + )) + }; + ($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => { + $builder.append(::minidom::Node::Element(xmpp_parsers::Element::from( + $parent.$elem, + ))) + }; +} + +macro_rules! generate_child_test { + ($child:ident, $name:tt, *) => { + $child.is($name, ::minidom::NSChoice::Any) + }; + ($child:ident, $name:tt, $ns:tt) => { + $child.is($name, $ns) + }; +} + +macro_rules! generate_element { + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+,]) => ( + generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: []); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+]) => ( + generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: []); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*]) => ( + generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),*,], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*]) => ( + generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => ( + generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>)); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => ( + generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>)); + ); + ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),*], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*] $(, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >))*) => ( + $(#[$meta])* + #[derive(Debug, Clone, PartialEq)] + pub struct $elem { + $( + $(#[$attr_meta])* + pub $attr: decl_attr!($attr_action, $attr_type), + )* + $( + $(#[$child_meta])* + pub $child_ident: start_decl!($coucou, $child_type), + )* + $( + $(#[$text_meta])* + pub $text_ident: $text_type, + )* + } + + impl ::std::convert::TryFrom for $elem { + type Error = xmpp_parsers::Error; + + fn try_from(elem: xmpp_parsers::Element) -> Result<$elem, xmpp_parsers::Error> { + check_self!(elem, $name, $ns); + $( + start_parse_elem!($child_ident: $coucou); + )* + for _child in elem.children() { + $( + if generate_child_test!(_child, $child_name, $child_ns) { + do_parse_elem!($child_ident: $coucou = $child_constructor => _child, $child_name, $name); + continue; + } + )* + } + Ok($elem { + $( + $attr: get_attr!(elem, $attr_name, $attr_action), + )* + $( + $child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name), + )* + $( + $text_ident: $codec::decode(&elem.text())?, + )* + }) + } + } + + impl From<$elem> for xmpp_parsers::Element { + fn from(elem: $elem) -> xmpp_parsers::Element { + let mut builder = xmpp_parsers::Element::builder($name, $ns); + $( + builder = builder.attr($attr_name, elem.$attr); + )* + $( + builder = generate_serialiser!(builder, elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns)); + )* + $( + builder = builder.append_all($codec::encode(&elem.$text_ident).map(::minidom::Node::Text).into_iter()); + )* + + builder.build() + } + } + ); +} + +#[cfg(test)] +macro_rules! assert_size ( + ($t:ty, $sz:expr) => ( + assert_eq!(::std::mem::size_of::<$t>(), $sz); + ); +); + +// TODO: move that to src/pubsub/mod.rs, once we figure out how to use macros from there. +macro_rules! impl_pubsub_item { + ($item:ident, $ns:ident) => { + impl ::std::convert::TryFrom for $item { + type Error = Error; + + fn try_from(elem: xmpp_parsers::Element) -> Result<$item, Error> { + check_self!(elem, "item", $ns); + let mut payloads = elem.children().cloned().collect::>(); + let payload = payloads.pop(); + if !payloads.is_empty() { + return Err(Error::ParseError( + "More than a single payload in item element.", + )); + } + Ok($item(xmpp_parsers::pubsub::Item { + id: get_attr!(elem, "id", Option), + publisher: get_attr!(elem, "publisher", Option), + payload, + })) + } + } + + impl From<$item> for xmpp_parsers::Element { + fn from(item: $item) -> xmpp_parsers::Element { + xmpp_parsers::Element::builder("item", $ns) + .attr("id", item.0.id) + .attr("publisher", item.0.publisher) + .append_all(item.0.payload) + .build() + } + } + + impl ::std::ops::Deref for $item { + type Target = xmpp_parsers::pubsub::Item; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl ::std::ops::DerefMut for $item { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + }; +} diff --git a/jitsi-xmpp-parsers/src/ns.rs b/jitsi-xmpp-parsers/src/ns.rs new file mode 100644 index 0000000..a5e06b5 --- /dev/null +++ b/jitsi-xmpp-parsers/src/ns.rs @@ -0,0 +1,5 @@ +/// Jitsi Meet general namespace +pub const JITSI_MEET: &str = "http://jitsi.org/jitmeet"; + +/// Jitsi Meet Colibri namespace +pub const JITSI_COLIBRI: &str = "http://jitsi.org/protocol/colibri"; diff --git a/lib-gst-meet-c/Cargo.toml b/lib-gst-meet-c/Cargo.toml index 3ed29e5..7614d57 100644 --- a/lib-gst-meet-c/Cargo.toml +++ b/lib-gst-meet-c/Cargo.toml @@ -8,8 +8,8 @@ authors = ["Jasper Hugo "] [dependencies] anyhow = { version = "1", default-features = false } -glib = { version = "0.14", default-features = false } -gstreamer = { version = "0.17", default-features = false } +glib = { version = "0.15", default-features = false } +gstreamer = { version = "0.18", default-features = false } lib-gst-meet = { version = "0.4", path = "../lib-gst-meet", default-features = false, features = ["tracing-subscriber"] } tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] } tracing = { version = "0.1", default-features = false } diff --git a/lib-gst-meet/Cargo.toml b/lib-gst-meet/Cargo.toml index df71770..69bcc49 100644 --- a/lib-gst-meet/Cargo.toml +++ b/lib-gst-meet/Cargo.toml @@ -13,27 +13,29 @@ authors = ["Jasper Hugo "] anyhow = { version = "1", default-features = false, features = ["std"] } async-stream = { version = "0.3", default-features = false } async-trait = { version = "0.1", default-features = false } +base64 = { version = "0.13", default-features = false } bytes = { version = "1", default-features = false, features = ["std"] } colibri = { version = "0.1", default-features = false } futures = { version = "0.3", default-features = false } -glib = { version = "0.14", default-features = false } -gstreamer = { version = "0.17", default-features = false, features = ["v1_20"] } -gstreamer-rtp = { version = "0.17", default-features = false, features = ["v1_20"] } +glib = { version = "0.15", default-features = false } +gstreamer = { version = "0.18", default-features = false, features = ["v1_20"] } +gstreamer-rtp = { version = "0.18", default-features = false, features = ["v1_20"] } hex = { version = "0.4", default-features = false, features = ["std"] } itertools = { version = "0.10", default-features = false, features = ["use_std"] } +jitsi-xmpp-parsers = { version = "0.1", path = "../jitsi-xmpp-parsers", default-features = false } libc = { version = "0.2", default-features = false } maplit = { version = "1", default-features = false } -nice-gst-meet = { version = "0.1", path = "../nice-gst-meet", default-features = false, features = ["v0_1_16"] } +nice-gst-meet = { version = "0.1", path = "../nice-gst-meet", default-features = false, features = ["v0_1_18"] } once_cell = { version = "1", default-features = false, features = ["std"] } pem = { version = "1", default-features = false } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } -rcgen = { version = "0.8", default-features = false } +rcgen = { version = "0.9", default-features = false } ring = { version = "0.16", default-features = false } serde = { version = "1", default-features = false, features = ["derive"] } serde_json = { version = "1", default-features = false, features = ["std"] } tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "sync", "time"] } tokio-stream = { version = "0.1", default-features = false, features = ["time"] } -tokio-tungstenite = { version = "0.16", default-features = false, features = ["connect"] } +tokio-tungstenite = { version = "0.17", default-features = false, features = ["connect"] } tracing = { version = "0.1", default-features = false, features = ["attributes", "std"] } tracing-subscriber = { version = "0.3", optional = true, default-features = false, features = [ "fmt", @@ -43,11 +45,11 @@ tracing-subscriber = { version = "0.3", optional = true, default-features = fals "tracing-log", ] } uuid = { version = "0.8", default-features = false, features = ["v4"] } -xmpp-parsers = { path = "../xmpp-parsers", package = "xmpp-parsers-gst-meet", version = "0.18", default-features = false } +xmpp-parsers = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git", default-features = false, features = ["disable-validation"] } [features] default = ["tls-native"] tls-native = ["tokio-tungstenite/native-tls"] tls-native-vendored = ["tokio-tungstenite/native-tls-vendored"] tls-rustls-native-roots = ["tokio-tungstenite/rustls-tls-native-roots"] -tls-rustls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots"] \ No newline at end of file +tls-rustls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots"] diff --git a/lib-gst-meet/src/colibri.rs b/lib-gst-meet/src/colibri.rs index 6d06475..bbff0cc 100644 --- a/lib-gst-meet/src/colibri.rs +++ b/lib-gst-meet/src/colibri.rs @@ -1,25 +1,63 @@ -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; -use anyhow::Result; +use anyhow::{Context, Result}; use colibri::ColibriMessage; use futures::{ sink::SinkExt, stream::{StreamExt, TryStreamExt}, }; -use tokio::sync::{mpsc, Mutex}; +use rand::{RngCore, thread_rng}; +use tokio::{ + sync::{mpsc, Mutex}, + time::sleep, +}; use tokio_stream::wrappers::ReceiverStream; -use tokio_tungstenite::tungstenite::{http::Request, Message}; +use tokio_tungstenite::tungstenite::{ + http::{Request, Uri}, + Message, +}; use tracing::{debug, error, info, warn}; +const MAX_CONNECT_RETRIES: u8 = 3; +const CONNECT_RETRY_SLEEP: Duration = Duration::from_secs(3); + pub(crate) struct ColibriChannel { send_tx: mpsc::Sender, recv_tx: Arc>>>, } impl ColibriChannel { - pub(crate) async fn new(colibri_url: &str) -> Result { - let request = Request::get(colibri_url).body(())?; - let (colibri_websocket, _response) = tokio_tungstenite::connect_async(request).await?; + pub(crate) async fn new(uri: &str) -> Result { + let uri: Uri = uri.parse()?; + let host = uri.host().context("invalid WebSocket URL: missing host")?; + + let mut retries = 0; + let colibri_websocket = loop { + let mut key = [0u8; 16]; + thread_rng().fill_bytes(&mut key); + let request = Request::get(&uri) + .header("sec-websocket-key", base64::encode(&key)) + .header("sec-websocket-version", "13") + .header("host", host) + // TODO: the server should probably not enforce this since non-browser clients are now possible + .header("origin", format!("https://{}", host)) + .header("connection", "Upgrade") + .header("upgrade", "websocket") + .body(())?; + match tokio_tungstenite::connect_async(request).await { + Ok((websocket, _)) => break websocket, + Err(e) => { + if retries < MAX_CONNECT_RETRIES { + warn!("Failed to connect Colibri WebSocket, will retry: {:?}", e); + sleep(CONNECT_RETRY_SLEEP).await; + retries += 1; + } + else { + return Err(e).context("Failed to connect Colibri WebSocket"); + } + } + } + }; info!("Connected Colibri WebSocket"); @@ -37,7 +75,7 @@ impl ColibriChannel { let mut txs = recv_tx.lock().await; let txs_clone = txs.clone(); for (i, tx) in txs_clone.iter().enumerate().rev() { - if let Err(_) = tx.send(colibri_msg.clone()).await { + if tx.send(colibri_msg.clone()).await.is_err() { debug!("colibri subscriber closed, removing"); txs.remove(i); } @@ -53,12 +91,12 @@ impl ColibriChannel { "received unexpected {} byte binary frame on colibri websocket", data.len() ), - Message::Ping(_) | Message::Pong(_) => {}, // handled automatically by tungstenite Message::Close(_) => { debug!("received close frame on colibri websocket"); // TODO reconnect break; }, + Message::Frame(_) | Message::Ping(_) | Message::Pong(_) => {}, // handled automatically by tungstenite } } Ok::<_, anyhow::Error>(()) diff --git a/lib-gst-meet/src/conference.rs b/lib-gst-meet/src/conference.rs index a31e5c8..8a308d3 100644 --- a/lib-gst-meet/src/conference.rs +++ b/lib-gst-meet/src/conference.rs @@ -5,6 +5,7 @@ use async_trait::async_trait; use colibri::ColibriMessage; use futures::stream::StreamExt; use gstreamer::prelude::{ElementExt, ElementExtManual, GstBinExt}; +use jitsi_xmpp_parsers::jingle::{Action, Jingle}; use maplit::hashmap; use once_cell::sync::Lazy; use serde::Serialize; @@ -19,7 +20,6 @@ use xmpp_parsers::{ ecaps2::{self, ECaps2}, hashes::{Algo, Hash}, iq::{Iq, IqType}, - jingle::{Action, Jingle}, message::{Message, MessageType}, muc::{Muc, MucUser, user::Status as MucStatus}, nick::Nick, @@ -391,14 +391,12 @@ impl JitsiConference { if let Err(e) = f(self.clone(), participant.clone()).await { warn!("on_participant failed: {:?}", e); } - else { - if let Ok(pipeline) = self.pipeline().await { - gstreamer::debug_bin_to_dot_file( - &pipeline, - gstreamer::DebugGraphDetails::ALL, - &format!("participant-added-{}", participant.muc_jid.resource), - ); - } + else if let Ok(pipeline) = self.pipeline().await { + gstreamer::debug_bin_to_dot_file( + &pipeline, + gstreamer::DebugGraphDetails::ALL, + &format!("participant-added-{}", participant.muc_jid.resource), + ); } } } @@ -425,14 +423,12 @@ impl JitsiConference { if let Err(e) = f(self.clone(), participant.clone()).await { warn!("on_participant failed: {:?}", e); } - else { - if let Ok(pipeline) = self.pipeline().await { - gstreamer::debug_bin_to_dot_file( - &pipeline, - gstreamer::DebugGraphDetails::ALL, - &format!("participant-added-{}", participant.muc_jid.resource), - ); - } + else if let Ok(pipeline) = self.pipeline().await { + gstreamer::debug_bin_to_dot_file( + &pipeline, + gstreamer::DebugGraphDetails::ALL, + &format!("participant-added-{}", participant.muc_jid.resource), + ); } } } @@ -542,8 +538,8 @@ impl StanzaFilter for JitsiConference { } } }, - IqType::Set(element) => { - if let Ok(jingle) = Jingle::try_from(element) { + IqType::Set(element) => match Jingle::try_from(element) { + Ok(jingle) => { if let Some(Jid::Full(from_jid)) = iq.from { if jingle.action == Action::SessionInitiate { if from_jid.resource == "focus" { @@ -580,10 +576,8 @@ impl StanzaFilter for JitsiConference { else { debug!("Received Jingle IQ from invalid JID: {:?}", iq.from); } - } - else { - debug!("Received non-Jingle IQ"); - } + }, + Err(e) => debug!("IQ did not successfully parse as Jingle: {:?}", e), }, IqType::Result(_) => { if let Some(jingle_session) = self.jingle_session.lock().await.as_mut() { @@ -696,14 +690,12 @@ impl StanzaFilter for JitsiConference { if let Err(e) = f(self.clone(), participant.clone()).await { warn!("on_participant failed: {:?}", e); } - else { - if let Some(jingle_session) = self.jingle_session.lock().await.as_ref() { - gstreamer::debug_bin_to_dot_file( - &jingle_session.pipeline(), - gstreamer::DebugGraphDetails::ALL, - &format!("participant-added-{}", participant.muc_jid.resource), - ); - } + else if let Some(jingle_session) = self.jingle_session.lock().await.as_ref() { + gstreamer::debug_bin_to_dot_file( + &jingle_session.pipeline(), + gstreamer::DebugGraphDetails::ALL, + &format!("participant-added-{}", participant.muc_jid.resource), + ); } } } diff --git a/lib-gst-meet/src/jingle.rs b/lib-gst-meet/src/jingle.rs index ac796a2..1ec1c41 100644 --- a/lib-gst-meet/src/jingle.rs +++ b/lib-gst-meet/src/jingle.rs @@ -5,6 +5,13 @@ use futures::stream::StreamExt; use glib::{ObjectExt, ToValue}; use gstreamer::prelude::{ElementExt, GObjectExtManualGst, GstBinExt, PadExt}; use gstreamer_rtp::{prelude::RTPHeaderExtensionExt, RTPHeaderExtension}; +use jitsi_xmpp_parsers::{ + jingle::{Action, Content, Description, Jingle, Transport}, + jingle_dtls_srtp::Fingerprint, + jingle_ice_udp::Transport as IceUdpTransport, + jingle_rtp::Description as RtpDescription, + jingle_ssma, +}; use nice_gst_meet as nice; use pem::Pem; use rand::random; @@ -16,14 +23,14 @@ use uuid::Uuid; use xmpp_parsers::{ hashes::Algo, iq::Iq, - jingle::{Action, Content, Creator, Description, Jingle, Senders, Transport}, - jingle_dtls_srtp::{Fingerprint, Setup}, - jingle_grouping::{self, Semantics}, - jingle_ice_udp::{self, Transport as IceUdpTransport}, + jingle::{Creator, Senders}, + jingle_dtls_srtp::Setup, + jingle_grouping::{self, Content as GroupContent}, + jingle_ice_udp, jingle_rtcp_fb::RtcpFb, - jingle_rtp::{self, Description as RtpDescription, PayloadType, RtcpMux}, + jingle_rtp::{self, PayloadType, RtcpMux}, jingle_rtp_hdrext::RtpHdrext, - jingle_ssma::{self, Parameter}, + jingle_ssma::{Parameter, Semantics}, Jid, }; @@ -117,9 +124,9 @@ impl Codec { struct ParsedRtpDescription { codecs: Vec, - audio_hdrext_ssrc_audio_level: Option, - audio_hdrext_transport_cc: Option, - video_hdrext_transport_cc: Option, + audio_hdrext_ssrc_audio_level: Option, + audio_hdrext_transport_cc: Option, + video_hdrext_transport_cc: Option, } pub(crate) struct JingleSession { @@ -156,7 +163,7 @@ impl JingleSession { pub(crate) fn pause_all_sinks(&self) { if let Some(rtpbin) = self.pipeline.by_name("rtpbin") { rtpbin.foreach_src_pad(|_, pad| { - let pad_name: String = pad.property("name").unwrap().get().unwrap(); + let pad_name: String = pad.property("name"); if pad_name.starts_with("recv_rtp_src_0_") { if let Some(peer_pad) = pad.peer() { if let Some(element) = peer_pad.parent_element() { @@ -185,28 +192,21 @@ impl JingleSession { if description.media == "audio" { for pt in description.payload_types.iter() { // We don’t support any static codec, so name MUST be set. - if let Some(name) = &pt.name { - match name.as_str() { - "opus" => { - opus = Some(Codec { - name: CodecName::Opus, - pt: pt.id, - rtx_pt: None, - rtcp_fbs: pt.rtcp_fbs.clone(), - }); - } - _ => (), - } + if pt.name.as_deref() == Some("opus") { + opus = Some(Codec { + name: CodecName::Opus, + pt: pt.id, + rtx_pt: None, + rtcp_fbs: pt.rtcp_fbs.clone(), + }); } } for hdrext in description.hdrexts.iter() { - // TODO: .parse::() won’t be needed after updating xmpp-parsers, it is now a u16 as - // defined in the XEP and related RFC. if hdrext.uri == RTP_HDREXT_SSRC_AUDIO_LEVEL { - audio_hdrext_ssrc_audio_level = Some(hdrext.id.parse::()?); + audio_hdrext_ssrc_audio_level = Some(hdrext.id); } else if hdrext.uri == RTP_HDREXT_TRANSPORT_CC { - audio_hdrext_transport_cc = Some(hdrext.id.parse::()?); + audio_hdrext_transport_cc = Some(hdrext.id); } } } @@ -273,7 +273,7 @@ impl JingleSession { // TODO: .parse::() won’t be needed after updating xmpp-parsers, it is now a u16 as // defined in the XEP and related RFC. if hdrext.uri == RTP_HDREXT_TRANSPORT_CC { - video_hdrext_transport_cc = Some(hdrext.id.parse::()?); + video_hdrext_transport_cc = Some(hdrext.id); } } } @@ -294,9 +294,9 @@ impl JingleSession { debug!("adding ssrc to remote_ssrc_map: {:?}", ssrc); remote_ssrc_map.insert( - ssrc.id.parse()?, + ssrc.id, Source { - ssrc: ssrc.id.parse()?, + ssrc: ssrc.id, participant_id: if owner == "jvb" { None } @@ -532,34 +532,34 @@ impl JingleSession { let rtpbin = gstreamer::ElementFactory::make("rtpbin", Some("rtpbin"))?; rtpbin.set_property_from_str("rtp-profile", "savpf"); - rtpbin.set_property("autoremove", true)?; + rtpbin.set_property("autoremove", true); pipeline.add(&rtpbin)?; let nicesrc = gstreamer::ElementFactory::make("nicesrc", None)?; - nicesrc.set_property("stream", ice_stream_id)?; - nicesrc.set_property("component", ice_component_id)?; - nicesrc.set_property("agent", &ice_agent)?; + nicesrc.set_property("stream", ice_stream_id); + nicesrc.set_property("component", ice_component_id); + nicesrc.set_property("agent", &ice_agent); pipeline.add(&nicesrc)?; let nicesink = gstreamer::ElementFactory::make("nicesink", None)?; - nicesink.set_property("stream", ice_stream_id)?; - nicesink.set_property("component", ice_component_id)?; - nicesink.set_property("agent", &ice_agent)?; + nicesink.set_property("stream", ice_stream_id); + nicesink.set_property("component", ice_component_id); + nicesink.set_property("agent", &ice_agent); pipeline.add(&nicesink)?; let dtls_srtp_connection_id = "gst-meet"; let dtlssrtpenc = gstreamer::ElementFactory::make("dtlssrtpenc", None)?; - dtlssrtpenc.set_property("connection-id", dtls_srtp_connection_id)?; - dtlssrtpenc.set_property("is-client", true)?; + dtlssrtpenc.set_property("connection-id", dtls_srtp_connection_id); + dtlssrtpenc.set_property("is-client", true); pipeline.add(&dtlssrtpenc)?; let dtlssrtpdec = gstreamer::ElementFactory::make("dtlssrtpdec", None)?; - dtlssrtpdec.set_property("connection-id", dtls_srtp_connection_id)?; + dtlssrtpdec.set_property("connection-id", dtls_srtp_connection_id); dtlssrtpdec.set_property( "pem", format!("{}\n{}", dtls_cert_pem, dtls_private_key_pem), - )?; + ); pipeline.add(&dtlssrtpdec)?; { @@ -619,7 +619,7 @@ impl JingleSession { None }, } - })?; + }); } let handle = Handle::current(); @@ -652,7 +652,7 @@ impl JingleSession { debug!("jitterbuffer is for remote source: {:?}", source); if source.media_type == MediaType::Video && source.participant_id.is_some() { debug!("enabling RTX for ssrc {}", ssrc); - rtpjitterbuffer.set_property("do-retransmission", true)?; + rtpjitterbuffer.set_property("do-retransmission", true); } Ok::<_, anyhow::Error>(()) }; @@ -660,17 +660,11 @@ impl JingleSession { warn!("new-jitterbuffer: {:?}", e); } None - })?; + }); let pts: Vec<(String, u32)> = codecs.iter() .filter(|codec| codec.is_video()) - .flat_map(|codec| { - if let Some(rtx_pt) = codec.rtx_pt { - Some((codec.pt.to_string(), rtx_pt as u32)) - } else { - None - } - }) + .flat_map(|codec| codec.rtx_pt.map(|rtx_pt| (codec.pt.to_string(), rtx_pt as u32))) .collect(); { let pts = pts.clone(); @@ -686,8 +680,8 @@ impl JingleSession { ssrc_map = ssrc_map.field(&video_ssrc.to_string(), &(video_rtx_ssrc as u32)); let bin = gstreamer::Bin::new(None); let rtx_sender = gstreamer::ElementFactory::make("rtprtxsend", None)?; - rtx_sender.set_property("payload-type-map", pt_map.build())?; - rtx_sender.set_property("ssrc-map", ssrc_map.build())?; + rtx_sender.set_property("payload-type-map", pt_map.build()); + rtx_sender.set_property("ssrc-map", ssrc_map.build()); bin.add(&rtx_sender)?; bin.add_pad(&gstreamer::GhostPad::with_target( Some(&format!("src_{}", session)), @@ -710,7 +704,7 @@ impl JingleSession { None }, } - })?; + }); } rtpbin.connect("request-aux-receiver", false, move |values| { @@ -723,7 +717,7 @@ impl JingleSession { } let bin = gstreamer::Bin::new(None); let rtx_receiver = gstreamer::ElementFactory::make("rtprtxreceive", None)?; - rtx_receiver.set_property("payload-type-map", pt_map.build())?; + rtx_receiver.set_property("payload-type-map", pt_map.build()); bin.add(&rtx_receiver)?; bin.add_pad(&gstreamer::GhostPad::with_target( Some(&format!("src_{}", session)), @@ -746,7 +740,7 @@ impl JingleSession { None }, } - })?; + }); { let handle = Handle::current(); @@ -759,7 +753,7 @@ impl JingleSession { let f = || { debug!("rtpbin pad-added {:?}", values); let pad: gstreamer::Pad = values[1].get()?; - let pad_name: String = pad.property("name")?.get()?; + let pad_name: String = pad.property("name"); if pad_name.starts_with("recv_rtp_src_0_") { let mut parts = pad_name.split('_').skip(4); let ssrc: u32 = parts.next().context("malformed pad name")?.parse()?; @@ -798,9 +792,7 @@ impl JingleSession { .filter(|codec| codec.is_video()) .find(|codec| codec.is(pt)); if let Some(codec) = codec { - let element = gstreamer::ElementFactory::make(codec.make_depay_name(), None)?; - element.set_property("request-keyframe", true)?; - element + gstreamer::ElementFactory::make(codec.make_depay_name(), None)? } else { bail!("received video with unsupported PT {}", pt); @@ -808,7 +800,7 @@ impl JingleSession { }, }; - source_element.set_property("auto-header-extension", false)?; + source_element.set_property("auto-header-extension", false); source_element.connect("request-extension", false, move |values| { let f = || { let ext_id: u32 = values[1].get()?; @@ -817,14 +809,14 @@ impl JingleSession { let hdrext = RTPHeaderExtension::create_from_uri(&ext_uri) .context("failed to create hdrext")?; hdrext.set_id(ext_id); - if ext_uri == RTP_HDREXT_SSRC_AUDIO_LEVEL { - } - else if ext_uri == RTP_HDREXT_TRANSPORT_CC { - // hdrext.set_property("n-streams", 2u32)?; - } - else { - bail!("unknown rtp hdrext: {}", ext_uri); - }; + // if ext_uri == RTP_HDREXT_SSRC_AUDIO_LEVEL { + // } + // else if ext_uri == RTP_HDREXT_TRANSPORT_CC { + // // hdrext.set_property("n-streams", 2u32); + // } + // else { + // bail!("unknown rtp hdrext: {}", ext_uri); + // }; Ok::<_, anyhow::Error>(hdrext) }; match f() { @@ -834,7 +826,8 @@ impl JingleSession { None }, } - })?; + }); + pipeline .add(&source_element) .context("failed to add depayloader to pipeline")?; @@ -901,21 +894,21 @@ impl JingleSession { error!("handling pad-added: {:?}", e); } None - })?; + }); } let opus = codecs.iter().find(|codec| codec.name == CodecName::Opus); let audio_sink_element = if let Some(opus) = opus { let audio_sink_element = gstreamer::ElementFactory::make(opus.make_pay_name(), None)?; - audio_sink_element.set_property("pt", opus.pt as u32)?; + audio_sink_element.set_property("pt", opus.pt as u32); audio_sink_element } else { bail!("no opus payload type in jingle session-initiate"); }; - audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000)?; - audio_sink_element.set_property("ssrc", audio_ssrc)?; + audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000); + audio_sink_element.set_property("ssrc", audio_ssrc); if audio_sink_element.has_property("auto-header-extension", None) { - audio_sink_element.set_property("auto-header-extension", false)?; + audio_sink_element.set_property("auto-header-extension", false); audio_sink_element.connect("request-extension", false, move |values| { let f = || { let ext_id: u32 = values[1].get()?; @@ -927,14 +920,14 @@ impl JingleSession { let hdrext = RTPHeaderExtension::create_from_uri(&ext_uri).context("failed to create hdrext")?; hdrext.set_id(ext_id); - if ext_uri == RTP_HDREXT_SSRC_AUDIO_LEVEL { - } - else if ext_uri == RTP_HDREXT_TRANSPORT_CC { - // hdrext.set_property("n-streams", 2u32)?; - } - else { - bail!("unknown rtp hdrext: {}", ext_uri); - } + // if ext_uri == RTP_HDREXT_SSRC_AUDIO_LEVEL { + // } + // else if ext_uri == RTP_HDREXT_TRANSPORT_CC { + // // hdrext.set_property("n-streams", 2u32); + // } + // else { + // bail!("unknown rtp hdrext: {}", ext_uri); + // } Ok::<_, anyhow::Error>(hdrext) }; match f() { @@ -944,7 +937,7 @@ impl JingleSession { None }, } - })?; + }); } else { debug!("audio payloader: no rtp header extension support"); @@ -955,7 +948,7 @@ impl JingleSession { let codec = codecs.iter().find(|codec| codec.is_codec(codec_name)); let video_sink_element = if let Some(codec) = codec { let element = gstreamer::ElementFactory::make(codec.make_pay_name(), None)?; - element.set_property("pt", codec.pt as u32)?; + element.set_property("pt", codec.pt as u32); if codec.name == CodecName::H264 { element.set_property_from_str("aggregate-mode", "zero-latency"); } @@ -967,9 +960,9 @@ impl JingleSession { else { bail!("unsupported video codec: {}", codec_name); }; - video_sink_element.set_property("ssrc", video_ssrc)?; + video_sink_element.set_property("ssrc", video_ssrc); if video_sink_element.has_property("auto-header-extension", None) { - video_sink_element.set_property("auto-header-extension", false)?; + video_sink_element.set_property("auto-header-extension", false); video_sink_element.connect("request-extension", false, move |values| { let f = || { let ext_id: u32 = values[1].get()?; @@ -982,7 +975,7 @@ impl JingleSession { RTPHeaderExtension::create_from_uri(&ext_uri).context("failed to create hdrext")?; hdrext.set_id(ext_id); if ext_uri == RTP_HDREXT_TRANSPORT_CC { - // hdrext.set_property("n-streams", 2u32)?; + // hdrext.set_property("n-streams", 2u32); } else { bail!("unknown rtp hdrext: {}", ext_uri); @@ -996,7 +989,7 @@ impl JingleSession { None }, } - })?; + }); } else { debug!("video payloader: no rtp header extension support"); @@ -1134,12 +1127,12 @@ impl JingleSession { }); description.ssrcs = if initiate_content.name.0 == "audio" { - vec![jingle_ssma::Source::new(audio_ssrc.to_string())] + vec![jingle_ssma::Source::new(audio_ssrc)] } else { vec![ - jingle_ssma::Source::new(video_ssrc.to_string()), - jingle_ssma::Source::new(video_rtx_ssrc.to_string()), + jingle_ssma::Source::new(video_ssrc), + jingle_ssma::Source::new(video_rtx_ssrc), ] }; @@ -1159,10 +1152,10 @@ impl JingleSession { } else { vec![jingle_ssma::Group { - semantics: "FID".to_owned(), + semantics: Semantics::Fid, sources: vec![ - jingle_ssma::Source::new(video_ssrc.to_string()), - jingle_ssma::Source::new(video_rtx_ssrc.to_string()), + jingle_ssma::Source::new(video_ssrc), + jingle_ssma::Source::new(video_rtx_ssrc), ], }] }; @@ -1170,13 +1163,13 @@ impl JingleSession { if initiate_content.name.0 == "audio" { if let Some(hdrext) = audio_hdrext_ssrc_audio_level { description.hdrexts.push(RtpHdrext::new( - hdrext.to_string(), + hdrext, RTP_HDREXT_SSRC_AUDIO_LEVEL.to_owned(), )); } if let Some(hdrext) = audio_hdrext_transport_cc { description.hdrexts.push(RtpHdrext::new( - hdrext.to_string(), + hdrext, RTP_HDREXT_TRANSPORT_CC.to_owned(), )); } @@ -1184,7 +1177,7 @@ impl JingleSession { else if initiate_content.name.0 == "video" { if let Some(hdrext) = video_hdrext_transport_cc { description.hdrexts.push(RtpHdrext::new( - hdrext.to_string(), + hdrext, RTP_HDREXT_TRANSPORT_CC.to_owned(), )); } @@ -1194,7 +1187,7 @@ impl JingleSession { hash: Algo::Sha_256, setup: Some(Setup::Active), value: fingerprint.clone(), - required: Some(true.to_string()), + // required: Some(true.to_string()), }); transport.ufrag = Some(ice_local_ufrag.clone()); transport.pwd = Some(ice_local_pwd.clone()); @@ -1233,10 +1226,10 @@ impl JingleSession { } jingle_accept = jingle_accept.set_group(jingle_grouping::Group { - semantics: Semantics::Bundle, + semantics: jingle_grouping::Semantics::Bundle, contents: vec![ - jingle_grouping::Content::new("video"), - jingle_grouping::Content::new("audio"), + GroupContent::new("video"), + GroupContent::new("audio"), ], }); @@ -1272,9 +1265,9 @@ impl JingleSession { debug!("adding ssrc to remote_ssrc_map: {:?}", ssrc); self.remote_ssrc_map.insert( - ssrc.id.parse()?, + ssrc.id, Source { - ssrc: ssrc.id.parse()?, + ssrc: ssrc.id, participant_id: if owner == "jvb" { None } diff --git a/lib-gst-meet/src/xmpp/connection.rs b/lib-gst-meet/src/xmpp/connection.rs index 3f14061..ea4e3cd 100644 --- a/lib-gst-meet/src/xmpp/connection.rs +++ b/lib-gst-meet/src/xmpp/connection.rs @@ -5,6 +5,7 @@ use futures::{ sink::{Sink, SinkExt}, stream::{Stream, StreamExt, TryStreamExt}, }; +use rand::{RngCore, thread_rng}; use tokio::sync::{mpsc, oneshot, Mutex}; use tokio_stream::wrappers::ReceiverStream; use tokio_tungstenite::tungstenite::{ @@ -76,8 +77,15 @@ impl Connection { let xmpp_domain: BareJid = xmpp_domain.parse().context("invalid XMPP domain")?; info!("Connecting XMPP WebSocket to {}", websocket_url); - let request = Request::get(websocket_url) - .header("Sec-Websocket-Protocol", "xmpp") + let mut key = [0u8; 16]; + thread_rng().fill_bytes(&mut key); + let request = Request::get(&websocket_url) + .header("sec-websocket-protocol", "xmpp") + .header("sec-websocket-key", base64::encode(&key)) + .header("sec-websocket-version", "13") + .header("host", websocket_url.host().context("invalid WebSocket URL: missing host")?) + .header("connection", "Upgrade") + .header("upgrade", "websocket") .body(()) .context("failed to build WebSocket request")?; let (websocket, _response) = tokio_tungstenite::connect_async(request) diff --git a/nice-gst-meet-sys/Cargo.toml b/nice-gst-meet-sys/Cargo.toml index f1b47bb..5468c93 100644 --- a/nice-gst-meet-sys/Cargo.toml +++ b/nice-gst-meet-sys/Cargo.toml @@ -50,11 +50,11 @@ name = "nice_sys" [dependencies] libc = { version = "0.2", default-features = false } -glib = { version = "0.14", default-features = false } -gio = { version = "0.14", default-features = false } +glib = { version = "0.15", default-features = false } +gio = { version = "0.15", default-features = false } [build-dependencies] -system-deps = { version = "3", default-features = false } +system-deps = { version = "6", default-features = false } [dev-dependencies] shell-words = { version = "1", default-features = false } diff --git a/nice-gst-meet/Cargo.toml b/nice-gst-meet/Cargo.toml index 7c4ae1b..75623be 100644 --- a/nice-gst-meet/Cargo.toml +++ b/nice-gst-meet/Cargo.toml @@ -8,10 +8,10 @@ authors = ["Jasper Hugo "] [dependencies] bitflags = { version = "1", default-features = false, optional = true } -glib = { version = "0.14", default-features = false } +glib = { version = "0.15", default-features = false } libc = { version = "0.2", default-features = false } nice-gst-meet-sys = { version = "0.1", path = "../nice-gst-meet-sys", default-features = false } -nix = { version = "0.22", default-features = false } +nix = { version = "0.23", default-features = false } [features] v0_1_4 = ["nice-gst-meet-sys/v0_1_4"] diff --git a/nice-gst-meet/src/agent.rs b/nice-gst-meet/src/agent.rs index d2a8e0f..b6e28ec 100644 --- a/nice-gst-meet/src/agent.rs +++ b/nice-gst-meet/src/agent.rs @@ -5,7 +5,7 @@ use std::{ boxed::Box as Box_, fmt, - mem::{self, transmute}, + mem::transmute, ptr, slice, }; @@ -48,7 +48,7 @@ extern "C" fn attach_recv_cb( user_data: gpointer, ) { if !user_data.is_null() { - let closure: &mut Box = unsafe { mem::transmute(user_data) }; + let closure: &mut Box = unsafe { &mut *(user_data as *mut _) }; let slice = unsafe { slice::from_raw_parts(data, len as usize) }; let bytes: Vec<_> = slice.iter().map(|b| *b as u8).collect(); if let Ok(s) = std::str::from_utf8(&bytes) { @@ -419,6 +419,7 @@ impl Agent { } } + #[allow(clippy::too_many_arguments)] #[doc(alias = "nice_agent_set_relay_info")] pub fn set_relay_info( &self, diff --git a/nice-gst-meet/src/candidate.rs b/nice-gst-meet/src/candidate.rs index 3e368ad..6383572 100644 --- a/nice-gst-meet/src/candidate.rs +++ b/nice-gst-meet/src/candidate.rs @@ -47,7 +47,7 @@ impl<'a> ToGlibPtr<'a, *mut ffi::NiceCandidate> for Candidate { #[inline] fn to_glib_none(&'a self) -> Stash<'a, *mut ffi::NiceCandidate, Self> { - Stash(&*self.0 as *const _ as *mut _, self) + Stash(&*self.inner as *const _ as *mut _, self) } } @@ -58,26 +58,26 @@ impl Candidate { } pub fn type_(&self) -> CandidateType { - unsafe { CandidateType::from_glib(self.0.type_) } + unsafe { CandidateType::from_glib(self.inner.type_) } } #[cfg(any(feature = "v0_1_18", feature = "dox"))] #[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))] pub fn transport(&self) -> CandidateTransport { - unsafe { CandidateTransport::from_glib(self.0.transport) } + unsafe { CandidateTransport::from_glib(self.inner.transport) } } #[cfg(any(feature = "v0_1_18", feature = "dox"))] #[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))] pub fn set_transport(&mut self, transport: CandidateTransport) { - self.0.transport = transport.into_glib(); + self.inner.transport = transport.into_glib(); } pub fn addr(&self) -> SocketAddr { unsafe { - match AddressFamily::from_i32(self.0.addr.s.addr.sa_family as i32).unwrap() { - AddressFamily::Inet => InetAddr::V4(self.0.addr.s.ip4).to_std(), - AddressFamily::Inet6 => InetAddr::V6(self.0.addr.s.ip6).to_std(), + match AddressFamily::from_i32(self.inner.addr.s.addr.sa_family as i32).unwrap() { + AddressFamily::Inet => InetAddr::V4(self.inner.addr.s.ip4).to_std(), + AddressFamily::Inet6 => InetAddr::V6(self.inner.addr.s.ip6).to_std(), other => panic!("unsupported address family: {:?}", other), } } @@ -87,21 +87,21 @@ impl Candidate { match InetAddr::from_std(&addr) { InetAddr::V4(ip4) => unsafe { ffi::nice_address_set_ipv4( - &mut self.0.addr as *mut _, + &mut self.inner.addr as *mut _, u32::from_be(ip4.sin_addr.s_addr), ); ffi::nice_address_set_port( - &mut self.0.addr as *mut _, + &mut self.inner.addr as *mut _, u16::from_be(ip4.sin_port) as u32, ); }, InetAddr::V6(ip6) => unsafe { ffi::nice_address_set_ipv6( - &mut self.0.addr as *mut _, + &mut self.inner.addr as *mut _, &ip6.sin6_addr.s6_addr as *const _, ); ffi::nice_address_set_port( - &mut self.0.addr as *mut _, + &mut self.inner.addr as *mut _, u16::from_be(ip6.sin6_port) as u32, ); }, @@ -109,31 +109,31 @@ impl Candidate { } pub fn priority(&self) -> u32 { - self.0.priority + self.inner.priority } pub fn set_priority(&mut self, priority: u32) { - self.0.priority = priority; + self.inner.priority = priority; } pub fn stream_id(&self) -> u32 { - self.0.stream_id + self.inner.stream_id } pub fn set_stream_id(&mut self, stream_id: u32) { - self.0.stream_id = stream_id; + self.inner.stream_id = stream_id; } pub fn component_id(&self) -> u32 { - self.0.component_id + self.inner.component_id } pub fn set_component_id(&mut self, component_id: u32) { - self.0.component_id = component_id; + self.inner.component_id = component_id; } pub fn foundation(&self) -> Result<&str, std::str::Utf8Error> { - unsafe { CStr::from_ptr(&self.0.foundation as *const c_char).to_str() } + unsafe { CStr::from_ptr(&self.inner.foundation as *const c_char).to_str() } } pub fn set_foundation(&mut self, foundation: &str) { @@ -144,33 +144,33 @@ impl Candidate { .map(|c| *c as c_char) .collect(); bytes.resize(33, 0); - self.0.foundation.copy_from_slice(&bytes); + self.inner.foundation.copy_from_slice(&bytes); } pub fn username(&self) -> Result<&str, std::str::Utf8Error> { - if self.0.username.is_null() { + if self.inner.username.is_null() { Ok("") } else { - unsafe { CStr::from_ptr(self.0.username).to_str() } + unsafe { CStr::from_ptr(self.inner.username).to_str() } } } pub fn set_username(&mut self, username: &str) { - self.0.username = username.to_owned().to_glib_full(); + self.inner.username = username.to_owned().to_glib_full(); } pub fn password(&self) -> Result<&str, std::str::Utf8Error> { - if self.0.password.is_null() { + if self.inner.password.is_null() { Ok("") } else { - unsafe { CStr::from_ptr(self.0.password).to_str() } + unsafe { CStr::from_ptr(self.inner.password).to_str() } } } pub fn set_password(&mut self, password: &str) { - self.0.password = password.to_owned().to_glib_full(); + self.inner.password = password.to_owned().to_glib_full(); } #[cfg(any(feature = "v0_1_15", feature = "dox"))] diff --git a/shell.nix b/shell.nix index b303701..dbb9e9f 100644 --- a/shell.nix +++ b/shell.nix @@ -1,71 +1,14 @@ with import {}; let - meson-patched = meson.overridePythonAttrs(old: rec { - version = "0.59.4"; - src = pythonPackages.fetchPypi { - inherit version; - pname = old.pname; - sha256 = "a77988cc50554f73ede075bc9bf77a2d7ecb6ff892f2a0180d4940920eaaec84"; - }; - patches = builtins.filter (patch: baseNameOf patch != "gir-fallback-path.patch") old.patches; - }); - gstreamer = (gst_all_1.gstreamer.override { - meson = meson-patched; - }).overrideAttrs(old: rec { - version = "1.19.3"; - src = fetchurl { - url = "https://gstreamer.freedesktop.org/src/${old.pname}/${old.pname}-${version}.tar.xz"; - sha256 = "906d7d4bf92f941586c0cbce717d9cad6aac36994e16fa6f2f153e07e3221bca"; - }; - patches = []; - mesonFlags = old.mesonFlags ++ ["-Dorc=disabled"]; - }); - gst-plugins-base = (gst_all_1.gst-plugins-base.override { - meson = meson-patched; - gstreamer = gstreamer; - }).overrideAttrs(old: rec { - version = "1.19.3"; - src = fetchurl { - url = "https://gstreamer.freedesktop.org/src/${old.pname}/${old.pname}-${version}.tar.xz"; - sha256 = "e277f198623a26c1b0a1e19734656392e9368bebf3677cd94262a1316a960827"; - }; - patches = []; - mesonFlags = old.mesonFlags ++ ["-Dorc=disabled"]; - }); - gst-plugins-good = (gst_all_1.gst-plugins-good.override { - meson = meson-patched; - gst-plugins-base = gst-plugins-base; - }).overrideAttrs(old: rec { - version = "1.19.3"; - src = fetchurl { - url = "https://gstreamer.freedesktop.org/src/${old.pname}/${old.pname}-${version}.tar.xz"; - sha256 = "79ea32a77fa47e6596530e38113bf97c113fd95658087d9a91ffb8af47d11d07"; - }; - patches = []; - mesonFlags = old.mesonFlags ++ ["-Dorc=disabled"]; - }); - gst-plugins-bad = (gst_all_1.gst-plugins-bad.override { - meson = meson-patched; - gst-plugins-base = gst-plugins-base; - }).overrideAttrs(old: rec { - version = "1.19.3"; - src = fetchurl { - url = "https://gstreamer.freedesktop.org/src/${old.pname}/${old.pname}-${version}.tar.xz"; - sha256 = "50193a23b13713ccb32ee5d1852faeeaed29b91f8398285acdfd522fa3e16835"; - }; - patches = []; - mesonFlags = old.mesonFlags ++ ["-Dorc=disabled" "-Dgs=disabled" "-Disac=disabled" "-Dldac=disabled" "-Donnx=disabled" "-Dopenaptx=disabled" "-Dqroverlay=disabled" "-Dtests=disabled" "-Dfaad=disabled" "-Dmpeg2enc=disabled" "-Dmplex=disabled" "-Dresindvd=disabled" "-Dx265=disabled"]; - }); - libnice-patched = (libnice.override { - meson = meson-patched; - }).overrideAttrs(old: rec { + libnice-patched = libnice.overrideAttrs(old: rec { buildInputs = [ - gstreamer - gst-plugins-base + gst_all_1.gstreamer + gst_all_1.gst-plugins-base openssl ]; outputs = [ "bin" "out" "dev" ]; mesonFlags = old.mesonFlags ++ ["-Dgupnp=disabled" "-Dgtk_doc=disabled"]; + meta.platforms = lib.platforms.unix; }); in mkShell { @@ -76,10 +19,10 @@ mkShell { openssl glib glib-networking - gstreamer - gst-plugins-base - gst-plugins-good - gst-plugins-bad + gst_all_1.gstreamer + gst_all_1.gst-plugins-base + gst_all_1.gst-plugins-good + gst_all_1.gst-plugins-bad libnice-patched ] ++ (if stdenv.isDarwin then [ darwin.apple_sdk.frameworks.AppKit diff --git a/xmpp-parsers/Cargo.toml b/xmpp-parsers/Cargo.toml deleted file mode 100644 index c2e8fd3..0000000 --- a/xmpp-parsers/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "xmpp-parsers-gst-meet" -version = "0.18.2" -authors = [ - "Emmanuel Gil Peyrot ", - "Maxime “pep” Buquet ", -] -description = "Collection of parsers and serialisers for XMPP extensions" -homepage = "https://gitlab.com/xmpp-rs/xmpp-rs" -repository = "https://gitlab.com/xmpp-rs/xmpp-rs" -keywords = ["xmpp", "jabber", "xml"] -categories = ["parsing", "network-programming"] -license = "MPL-2.0" -edition = "2018" - -[dependencies] -minidom = { package = "minidom-gst-meet", version = "0.13" } -jid = { package = "jid-gst-meet", version = "0.9", features = ["minidom"] } -base64 = "0.13" -digest = "0.9" -sha-1 = "0.9" -sha2 = "0.9" -sha3 = "0.9" -blake2 = "0.9" -chrono = { version = "0.4.5", default-features = false, features = ["std"] } - -[features] -# Build xmpp-parsers to make components instead of clients. -component = [] -# Disable validation of unknown attributes. -disable-validation = [] -serde = ["jid/serde"] - -[package.metadata.docs.rs] -rustdoc-args = [ "--sort-modules-by-appearance", "-Zunstable-options" ] diff --git a/xmpp-parsers/ChangeLog b/xmpp-parsers/ChangeLog deleted file mode 100644 index 0329f69..0000000 --- a/xmpp-parsers/ChangeLog +++ /dev/null @@ -1,365 +0,0 @@ -Version 0.18.0: -2021-01-13 Emmanuel Gil Peyrot - * Bugfixes: - - Bump minidom to 0.13, as 0.12.1 got yanked. - -Version 0.18.0: -2021-01-13 Emmanuel Gil Peyrot - * New parsers/serialisers: - - Jingle Raw UDP Transport Method (XEP-0177). - - Jingle RTP Header Extensions Negotiation (XEP-0294). - - Jingle Grouping Framework (XEP-0338). - - Mediated Information eXchange (MIX) (XEP-0369). - * Improvements: - - Everything is now PartialEq! - - Add "serde" feature to enable "jid/serde". - - Implement more of XEP-0060. - - Bump XEP-0167 to version 1.2.0, adding rtcp-mux. - - Bump XEP-0176 to version 1.1, fixing interoperability with other - clients. - - Bump XEP-0402 to version 1.1.1, bumping its namespace and adding - support for extension data. - - Bump all dependencies to their latest version. - - Some more helper constructors. - - Make public some stuff that should have been public from the very - beginning. - * Bugfixes: - - Jingle::set_reason() does what it says now (copy/paste error). - - Bookmarks’ names are now optional like they should. - -Version 0.17.0: -2020-02-15 Emmanuel Gil Peyrot , Maxime “pep” Buquet , Paul Fariello - * Improvements: - - Add serialization tests where possible - - Use minidom's NSChoice API for Jingle parser - - Remove NamespaceAwareCompare. Move to minidom - * Breaking changes: - - Prevent generate_serializer macro from adding another layer of Node. - Fixes some serializers. - - ecaps2: Use the Error type instead of () - -Version 0.16.0: -2019-10-15 Emmanuel Gil Peyrot - * New parsers/serialisers: - - Client Certificate Management for SASL EXTERNAL (XEP-0257) - - JID Prep (XEP-0328) - - Client State Indication (XEP-0352) - - OpenPGP for XMPP (XEP-0373) - - Bookmarks 2 (This Time it's Serious) (XEP-0402) - - Anonymous unique occupant identifiers for MUCs (XEP-0421) - - Source-Specific Media Attributes in Jingle (XEP-0339) - - Jingle RTP Feedback Negotiation (XEP-0293) - * Breaking changes: - - Presence constructors now take Into and assume Some. - * Improvements: - - CI: refactor, add caching - - Update jid-rs to 0.8 - -Version 0.15.0: -2019-09-06 Emmanuel Gil Peyrot - * New parsers/serialisers: - - XHTML-IM (XEP-0071) - - User Tune (XEP-0118) - - Bits of Binary (XEP-0231) - - Message Carbons (XEP-0280) - * Breaking changes: - - Stop reexporting TryFrom and TryInto, they are available in - std::convert nowadays. - - Bind has been split into BindQuery and BindResponse. - * Improvements: - - New DOAP file for a machine-readable description of the features. - - Add various parser and formatter helpers on Hash. - -Version 0.14.0: -2019-07-13 Emmanuel Gil Peyrot , Maxime “pep” Buquet - * New parsers/serialisers: - - Entity Time (XEP-0202). - * Improvements: - - Microblog NS (XEP-0227). - - Update jid-rs dependency with jid split change (Jid, FullJid, - BareJid) and reexport them. - - Fix rustdoc options in Cargo.toml for docs.rs - * Breaking changes: - - Presence's show attribute is now Option and Show::None is no - more. - -Version 0.13.1: -2019-04-12 Emmanuel Gil Peyrot - * Bugfixes: - - Fix invalid serialisation of priority in presence. - - Bump image size to u16 from u8, as per XEP-0084 version 1.1.2. - * Improvements: - - Drop try_from dependency, as std::convert::TryFrom got - stabilised. - -Version 0.13.0: -2019-03-20 Emmanuel Gil Peyrot - * New parsers/serialisers: - - User Avatar (XEP-0084). - - Contact Addresses for XMPP Services (XEP-0157). - - Jingle RTP Sessions (XEP-0167). - - Jingle ICE-UDP Transport Method (XEP-0176). - - Use of DTLS-SRTP in Jingle Sessions (XEP-0320). - * Breaking changes: - - Make 'id' required on iq, as per RFC6120 §8.1.3. - - Refactor PubSub to have more type-safety. - - Treat FORM_TYPE as a special case in data forms, to avoid - duplicating it into a field. - - Add forgotten i18n to Jingle text element. - * Improvements: - - Add various helpers for hash representations. - - Add helpers constructors for multiple extensions (disco, caps, - pubsub, stanza_error). - - Use Into in more constructors. - - Internal change on attribute declaration in macros. - - Reexport missing try_from::TryInto. - -Version 0.12.2: -2019-01-16 Emmanuel Gil Peyrot - * Improvements: - - Reexport missing util::error::Error and try_from::TryFrom. - -Version 0.12.1: -2019-01-16 Emmanuel Gil Peyrot - * Improvements: - - Reexport missing JidParseError from the jid crate. - -Version 0.12.0: -2019-01-16 Emmanuel Gil Peyrot - * Breaking changes: - - Update dependencies. - - Switch to git, upstream is now available at - https://gitlab.com/xmpp-rs/xmpp-parsers - - Switch to Edition 2018, this removes support for rustc - versions older than 1.31. - - Implement support for XEP-0030 2.5rc3, relaxing the ordering - of children in disco#info. - * Improvements: - - Test for struct size, to keep them known and avoid bloat. - - Add various constructors to make the API easier to use. - - Reexport Jid from the jid crate, to avoid any weird issue on - using different incompatible versions of the same crate. - - Add forgotten 'ask' attribute on roster item (thanks O01eg!). - - Use cargo-fmt on the codebase, to lower the barrier of entry. - - Add a disable-validation feature, disabling many checks - xmpp-parsers is doing. This should be used for software - which want to let invalid XMPP pass through instead of being - rejected as invalid (thanks Astro-!). - -Version 0.11.1: -2018-09-20 Emmanuel Gil Peyrot - * Improvements: - - Document all of the modules. - -Version 0.11.0: -2018-08-03 Emmanuel Gil Peyrot - * Breaking changes: - - Split Software Version (XEP-0092) into a query and response - elements. - - Split RSM (XEP-0059) into a query and response elements. - - Fix type safety and spec issues in RSM and MAM (XEP-0313). - - Remove item@node and EmptyItems from PubSub events - (XEP-0060). - * Improvements: - - Document many additional modules. - - Add the SASL nonza, as well as the SCRAM-SHA-256 - and the two -PLUS mechanisms. - -Version 0.10.0: -2018-07-31 Emmanuel Gil Peyrot - * New parsers/serialisers: - - Added , SASL and bind (RFC6120) parsers. - - Added a WebSocket (RFC7395) implementation. - - Added a Jabber Component (XEP-0114). - - Added support for User Nickname (XEP-0172). - - Added support for Stream Management (XEP-0198). - - Added support for Bookmarks (XEP-0048). - - Publish-Subscribe (XEP-0060) now supports requests in - addition to events. - * Breaking changes: - - Switch from std::error to failure to report better errors. - - Bump to minidom 0.9.1, and reexport minidom::Element. - * Improvements: - - Add getters for the best body and subject in message, to make - it easier to determine which one the user wants based on - their language preferences. - - Add constructors and setters for most Jingle elements, to - ease their creation. - - Add constructors for hash, MUC item, iq and more. - - Use more macros to simplify and factorise the code. - - Use traits to define iq payloads. - - Document more modules. - -Version 0.9.0: -2017-10-31 Emmanuel Gil Peyrot - * New parsers/serialisers: - - Blocking Command (XEP-0191) has been added. - - Date and Time Profiles (XEP-0082) has been added, replacing - ad-hoc use of chrono in various places. - - User Mood (XEP-0107) has been added. - * Breaking changes: - - Fix subscription="none" not being the default. - - Add more type safety to pubsub#event. - - Reuse Jingle’s ContentId type in JingleFT. - - Import the disposition attribute values in Jingle. - * Improvements: - - Refactor a good part of the code using macros. - - Simplify the parsing code wherever it makes sense. - - Check for children ordering in disco#info result. - - Finish implementation of , and - in JingleFT. - - Correctly serialise , and test it. - -Version 0.8.0: -2017-08-27 Emmanuel Gil Peyrot - * New parsers/serialisers: - - iq:version (XEP-0092) has been added. - - Finally implement extension serialisation in disco. - * Breaking changes: - - Wrap even more elements into their own type, in jingle, - jingle_ft, roster, message. - - Split loose enums into multiple structs where it makes sense, - such as for IBB, StanzaId, Receipts. - - Split disco query and answer elements into their own struct, - to enforce more guarantees on both. - * Improvements: - - Use Vec::into_iter() more to avoid references and clones. - - Make data_forms propagate a media_element error. - - Document more of disco, roster, chatstates. - - Use the minidom feature of jid, for IntoAttributeValue. - - Add a component feature, changing the default namespace to - jabber:component:accept. - - Add support for indicating ranged transfers in jingle_ft. - -Version 0.7.1: -2017-07-24 Emmanuel Gil Peyrot - * Hotfixes: - - Stub out blake2 support, since the blake2 crate broke its API - between their 0.6.0 and 0.6.1 releases… - -Version 0.7.0: -2017-07-23 Emmanuel Gil Peyrot - * New parsers/serialisers: - - Jingle Message Initialisation (XEP-0353) was added. - - The disco#items query (XEP-0030) is now supported, in - addition to the existing disco#info one. - * Breaking changes: - - Replaced many type aliases with proper wrapping structs. - - Split Disco into a query and a result part, since they have - very different constraints. - - Split IqPayload in three to avoid parsing queries as results - for example. - * Improvements: - - Use TryFrom from the try_from crate, thus removing the - dependency on nightly! - - Always implement From instead of Into, the latter is - generated anyway. - - Add helpers to construct your Presence stanza. - -Version 0.6.0: -2017-06-27 Emmanuel Gil Peyrot - * New parsers/serialisers: - - In-Band Registration (XEP-0077) was added. - - Multi-User Chat (XEP-0045) got expanded a lot, thanks pep.! - * Breaking changes: - - Added wrappers for Strings used as identifiers, to add type - safety. - - Use chrono’s DateTime for JingleFT’s date element. - - Use Jid for JingleS5B’s jid attribute. - * Improvements: - - Use more macros for common tasks. - - Add a constructor for Message and Presence. - - Implement std::fmt::Display and std::error::Error on our - error type. - - Fix DataForms serialisation. - - Fix roster group serialisation. - - Update libraries, notably chrono whose version 0.3.1 got - yanked. - -Version 0.5.0: -2017-06-11 Emmanuel Gil Peyrot - * New parsers/serialisers: - - Implementation of the roster management protocol defined in - RFC 6121 §2. - - Implementation of PubSub events (except collections). - - Early implementation of MUC. - * Breaking changes: - - Rename presence enums to make them easier to use. - * Improvements: - - Make hashes comparable and hashable. - - Make data forms embeddable easily into minidom - Element::builder. - -Version 0.4.0: -2017-05-28 Emmanuel Gil Peyrot - * Incompatible changes: - - Receipts now make the id optional, as per the specification. - - Hashes now expose their raw binary value, instead of staying - base64-encoded. - - Parse dates (XEP-0082) in delayed delivery (XEP-0203) and - last user interaction (XEP-0319), using the chrono crate. - * Improvements: - - Removal of most of the remaining clones, the only ones left - are due to minidom not exposing a draining iterator over the - children. - - Finish to parse all of the attributes using get_attr!(). - - More attribute checks. - - Split more parsers into one parser per element. - - Rely on minidom 0.4.3 to serialise more standard types - automatically. - - Implement forgotten serialisation for data forms (XEP-0004). - - Implement legacy capabilities (XEP-0115) for compatibility - with older software. - -Version 0.3.0: -2017-05-23 Emmanuel Gil Peyrot - * Big changes: - - All parsers and serialisers now consume their argument, this - makes the API way more efficient, but you will have to clone - before passing your structs in it if you want to keep them. - - Payloads of stanzas are not parsed automatically anymore, to - let applications which want to forward them as-is do so more - easily. Parsing now always succeeds on unknown payloads, it - just puts them into an Unknown value containing the existing - minidom Element. - * New parsers/serialisers: - - Last User Interaction in Presence, XEP-0319. - * Improved parsers/serialisers: - - Message now supports subject, bodies and threads as per - RFC 6121 §5.2. - - Replace most attribute reads with a nice macro. - - Use enums for more enum-like things, for example Algo in - Hash, or FieldType in DataForm. - - Wire up stanza-id and origin-id to MessagePayload. - - Wire up MAM elements to message and iq payloads. - - Changes in the RSM API. - - Add support for more data forms elements, but still not the - complete set. - - Thanks to minidom 0.3.1, check for explicitly disallowed - extra attributes in some elements. - * Crate updates: - - minidom 0.4.1 - -Version 0.2.0: -2017-05-06 Emmanuel Gil Peyrot - * New parsers/serialisers: - - Stanza error, as per RFC 6120 §8.3. - - Jingle SOCKS5 Transport, XEP-0260. - * Incompatible changes: - - Parsers and serialisers now all implement TryFrom - and Into, instead of the old parse_* and serialise_* - functions. - - Presence has got an overhaul, it now hosts show, statuses and - priority in its struct. The status module has also been - dropped. - - Message now supports multiple bodies, each in a different - language. The body module has also been dropped. - - Iq now gets a proper StanzaError when the type is error. - - Fix bogus Jingle payload, which was requiring both - description and transport. - * Crate updates: - - minidom 0.3.0 - -Version 0.1.0: -2017-04-29 Emmanuel Gil Peyrot - * Implement many extensions. diff --git a/xmpp-parsers/LICENSE b/xmpp-parsers/LICENSE deleted file mode 100644 index 14e2f77..0000000 --- a/xmpp-parsers/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/xmpp-parsers/doap.xml b/xmpp-parsers/doap.xml deleted file mode 100644 index 31d6e13..0000000 --- a/xmpp-parsers/doap.xml +++ /dev/null @@ -1,684 +0,0 @@ - - - - - xmpp-parsers - - 2017-04-18 - - Collection of parsers and serialisers for XMPP extensions - Collection de parseurs et de sérialiseurs pour extensions XMPP - - TODO - TODO - - - - - - - - - - - - - - - - Rust - - - - - - Link Mauve - - aaa4dac2b31c1be4ee8f8e2ab986d34fb261974f - - - - - pep. - - 99bcf9784288e323b0d2dea9c9ac7a2ede98395a - - - - - - - - - - - - - - - - - partial - 2.9 - 0.1.0 - - - - - - complete - 2.5rc3 - 0.1.0 - - - - - - complete - 1.32.0 - 0.5.0 - - - - - - complete - 2.0 - 0.1.0 - - - - - - complete - 1.1 - 0.10.0 - - - - - - complete - 1.0 - 0.1.0 - - - - - - partial - 1.15.8 - 0.5.0 - - - - - - complete - 1.2 - 0.1.0 - there is no specific module for this, the feature is all in the XEP-0004 module - - - - - - complete - 1.5.4 - 0.15.0 - - - - - - complete - 2.4 - 0.6.0 - - - - - - complete - 1.1 - 0.9.0 - - - - - - complete - 1.1.2 - 0.13.0 - - - - - - complete - 2.1 - 0.1.0 - - - - - - complete - 1.1 - 0.8.0 - - - - - - complete - 1.2.1 - 0.9.0 - - - - - - complete - 1.6 - 0.10.0 - - - - - - complete - 1.5.1 - 0.4.0 - - - - - - complete - 1.2 - 0.15.0 - - - - - - complete - 1.0.1 - 0.13.0 - - - - - - complete - 1.1.2 - 0.1.0 - - - - - - complete - 1.2.0 - 0.13.0 - - - - - - complete - 1.1 - 0.10.0 - - - - - - complete - 1.1 - 0.13.0 - - - - - - complete - 1.1 - NEXT - - - - - - complete - 1.4.0 - 0.1.0 - - - - - - complete - 1.3 - 0.9.0 - - - - - - complete - 1.6 - 0.10.0 - - - - - - complete - 2.0.1 - 0.1.0 - - - - - - complete - 2.0 - 0.14.0 - - - - - - complete - 2.0 - 0.1.0 - - - - - - complete - 1.0 - 0.1.0 - - - - - - complete - 1.0 - 0.1.0 - - - - - - complete - 1.0 - 0.15.0 - - - - - - complete - 0.19.1 - 0.1.0 - - - - - - complete - 0.3 - 0.16.0 - - - - - - complete - 1.0.3 - 0.2.0 - - - - - - complete - 1.0 - 0.1.0 - - - - - - partial - 0.6.3 - 0.14.0 - only the namespace is included for now - - - - - - complete - 0.13.0 - 0.15.0 - - - - - - partial - 1.0.1 - 0.16.0 - Only supported in payload-type, and only for rtcp-fb. - - - - - - partial - 1.0 - NEXT - Parameters aren’t yet implemented. - - - - - - complete - 1.0 - 0.1.0 - - - - - - complete - 0.6.0 - 0.1.0 - - - - - - complete - 1.1.0 - 0.1.0 - - - - - - complete - 0.7.5 - 0.1.0 - - - - - - complete - 1.0.2 - 0.3.0 - - - - - - complete - 0.3.1 - 0.13.0 - - - - - - complete - 0.1 - 0.16.0 - - - - - - complete - 1.0.0 - NEXT - - - - - - complete - 0.3 - 0.16.0 - - - - - - complete - 0.3.0 - 0.16.0 - - - - - - complete - 0.3 - 0.7.0 - - - - - - complete - 0.6.0 - 0.1.0 - - - - - - complete - 0.14.3 - NEXT - - - - - - partial - 0.4.0 - 0.16.0 - - - - - - complete - 0.2.0 - 0.1.0 - - - - - - complete - 0.3.0 - 0.1.0 - - - - - - complete - 1.1.1 - 0.16.0 - - - - - - complete - 0.1.0 - 0.16.0 - - - - - - complete - 0.2.0 - 0.1.0 - - - - - - 0.15.0 - 2019-09-06 - - - - - - 0.14.0 - 2019-07-13 - - - - - - 0.13.1 - 2019-04-12 - - - - - - 0.13.0 - 2019-03-20 - - - - - - 0.12.2 - 2019-01-16 - - - - - - 0.12.1 - 2019-01-16 - - - - - - 0.12.0 - 2019-01-16 - - - - - - 0.11.1 - 2018-09-20 - - - - - - 0.11.0 - 2018-08-02 - - - - - - 0.10.0 - 2018-07-31 - - - - - - 0.9.0 - 2017-12-27 - - - - - - 0.8.0 - 2017-11-30 - - - - - - 0.7.1 - 2017-11-30 - - - - - - 0.7.0 - 2017-11-30 - - - - - - 0.6.0 - 2017-11-30 - - - - - - 0.5.0 - 2017-11-30 - - - - - - 0.4.0 - 2017-11-30 - - - - - - 0.3.0 - 2017-11-30 - - - - - - 0.2.0 - 2017-11-30 - - - - - - 0.1.0 - 2017-11-30 - - - - - diff --git a/xmpp-parsers/examples/generate-caps.rs b/xmpp-parsers/examples/generate-caps.rs deleted file mode 100644 index a36963a..0000000 --- a/xmpp-parsers/examples/generate-caps.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::convert::TryFrom; -use std::env; -use std::io::{self, Read}; -use xmpp_parsers::{ - caps::{compute_disco as compute_disco_caps, hash_caps, Caps}, - disco::DiscoInfoResult, - ecaps2::{compute_disco as compute_disco_ecaps2, hash_ecaps2, ECaps2}, - hashes::Algo, - Element, Error, -}; - -fn get_caps(disco: &DiscoInfoResult, node: String) -> Result { - let data = compute_disco_caps(&disco); - let hash = hash_caps(&data, Algo::Sha_1)?; - Ok(Caps::new(node, hash)) -} - -fn get_ecaps2(disco: &DiscoInfoResult) -> Result { - let data = compute_disco_ecaps2(&disco)?; - let hash_sha256 = hash_ecaps2(&data, Algo::Sha_256)?; - let hash_sha3_256 = hash_ecaps2(&data, Algo::Sha3_256)?; - let hash_blake2b_256 = hash_ecaps2(&data, Algo::Blake2b_256)?; - Ok(ECaps2::new(vec![ - hash_sha256, - hash_sha3_256, - hash_blake2b_256, - ])) -} - -fn main() -> Result<(), Box> { - let args: Vec<_> = env::args().collect(); - if args.len() != 2 { - println!("Usage: {} ", args[0]); - std::process::exit(1); - } - let node = args[1].clone(); - - eprintln!("Reading a disco#info payload from stdin..."); - - // Read from stdin. - let stdin = io::stdin(); - let mut data = String::new(); - let mut handle = stdin.lock(); - handle.read_to_string(&mut data)?; - - // Parse the payload into a DiscoInfoResult. - let elem: Element = data.parse()?; - let disco = DiscoInfoResult::try_from(elem)?; - - // Compute both kinds of caps. - let caps = get_caps(&disco, node)?; - let ecaps2 = get_ecaps2(&disco)?; - - // Print them. - let caps_elem = Element::from(caps); - let ecaps2_elem = Element::from(ecaps2); - println!("{}", String::from(&caps_elem)); - println!("{}", String::from(&ecaps2_elem)); - - Ok(()) -} diff --git a/xmpp-parsers/src/attention.rs b/xmpp-parsers/src/attention.rs deleted file mode 100644 index 83a871c..0000000 --- a/xmpp-parsers/src/attention.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; - -generate_empty_element!( - /// Requests the attention of the recipient. - Attention, - "attention", - ATTENTION -); - -impl MessagePayload for Attention {} - -#[cfg(test)] -mod tests { - use super::*; - #[cfg(not(feature = "disable-validation"))] - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[test] - fn test_size() { - assert_size!(Attention, 0); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - Attention::try_from(elem).unwrap(); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Attention::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in attention element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Attention::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in attention element."); - } - - #[test] - fn test_serialise() { - let elem: Element = "".parse().unwrap(); - let attention = Attention; - let elem2: Element = attention.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/avatar.rs b/xmpp-parsers/src/avatar.rs deleted file mode 100644 index c475db8..0000000 --- a/xmpp-parsers/src/avatar.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::hashes::Sha1HexAttribute; -use crate::pubsub::PubSubPayload; -use crate::util::helpers::WhitespaceAwareBase64; - -generate_element!( - /// Communicates information about an avatar. - Metadata, "metadata", AVATAR_METADATA, - children: [ - /// List of information elements describing this avatar. - infos: Vec = ("info", AVATAR_METADATA) => Info - ] -); - -impl PubSubPayload for Metadata {} - -generate_element!( - /// Communicates avatar metadata. - Info, "info", AVATAR_METADATA, - attributes: [ - /// The size of the image data in bytes. - bytes: Required = "bytes", - - /// The width of the image in pixels. - width: Option = "width", - - /// The height of the image in pixels. - height: Option = "height", - - /// The SHA-1 hash of the image data for the specified content-type. - id: Required = "id", - - /// The IANA-registered content type of the image data. - type_: Required = "type", - - /// The http: or https: URL at which the image data file is hosted. - url: Option = "url", - ] -); - -generate_element!( - /// The actual avatar data. - Data, "data", AVATAR_DATA, - text: ( - /// Vector of bytes representing the avatar’s image. - data: WhitespaceAwareBase64> - ) -); - -impl PubSubPayload for Data {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::hashes::Algo; - #[cfg(not(feature = "disable-validation"))] - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Metadata, 12); - assert_size!(Info, 64); - assert_size!(Data, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Metadata, 24); - assert_size!(Info, 120); - assert_size!(Data, 24); - } - - #[test] - fn test_simple() { - let elem: Element = " - - " - .parse() - .unwrap(); - let metadata = Metadata::try_from(elem).unwrap(); - assert_eq!(metadata.infos.len(), 1); - let info = &metadata.infos[0]; - assert_eq!(info.bytes, 12345); - assert_eq!(info.width, Some(64)); - assert_eq!(info.height, Some(64)); - assert_eq!(info.id.algo, Algo::Sha_1); - assert_eq!(info.type_, "image/png"); - assert_eq!(info.url, None); - assert_eq!( - info.id.hash, - [ - 17, 31, 75, 60, 80, 215, 176, 223, 114, 157, 41, 155, 198, 248, 233, 239, 144, 102, - 151, 31 - ] - ); - - let elem: Element = "AAAA" - .parse() - .unwrap(); - let data = Data::try_from(elem).unwrap(); - assert_eq!(data.data, b"\0\0\0"); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Data::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in data element.") - } -} diff --git a/xmpp-parsers/src/bind.rs b/xmpp-parsers/src/bind.rs deleted file mode 100644 index e6d4cf9..0000000 --- a/xmpp-parsers/src/bind.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::{IqResultPayload, IqSetPayload}; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use jid::{FullJid, Jid}; -use std::convert::TryFrom; -use std::str::FromStr; - -/// The request for resource binding, which is the process by which a client -/// can obtain a full JID and start exchanging on the XMPP network. -/// -/// See https://xmpp.org/rfcs/rfc6120.html#bind -#[derive(Debug, Clone, PartialEq)] -pub struct BindQuery { - /// Requests this resource, the server may associate another one though. - /// - /// If this is None, we request no particular resource, and a random one - /// will be affected by the server. - resource: Option, -} - -impl BindQuery { - /// Creates a resource binding request. - pub fn new(resource: Option) -> BindQuery { - BindQuery { resource } - } -} - -impl IqSetPayload for BindQuery {} - -impl TryFrom for BindQuery { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "bind", BIND); - check_no_attributes!(elem, "bind"); - - let mut resource = None; - for child in elem.children() { - if resource.is_some() { - return Err(Error::ParseError("Bind can only have one child.")); - } - if child.is("resource", ns::BIND) { - check_no_attributes!(child, "resource"); - check_no_children!(child, "resource"); - resource = Some(child.text()); - } else { - return Err(Error::ParseError("Unknown element in bind request.")); - } - } - - Ok(BindQuery { resource }) - } -} - -impl From for Element { - fn from(bind: BindQuery) -> Element { - Element::builder("bind", ns::BIND) - .append_all( - bind.resource - .map(|resource| Element::builder("resource", ns::BIND).append(resource)), - ) - .build() - } -} - -/// The response for resource binding, containing the client’s full JID. -/// -/// See https://xmpp.org/rfcs/rfc6120.html#bind -#[derive(Debug, Clone, PartialEq)] -pub struct BindResponse { - /// The full JID returned by the server for this client. - jid: FullJid, -} - -impl IqResultPayload for BindResponse {} - -impl From for FullJid { - fn from(bind: BindResponse) -> FullJid { - bind.jid - } -} - -impl From for Jid { - fn from(bind: BindResponse) -> Jid { - Jid::Full(bind.jid) - } -} - -impl TryFrom for BindResponse { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "bind", BIND); - check_no_attributes!(elem, "bind"); - - let mut jid = None; - for child in elem.children() { - if jid.is_some() { - return Err(Error::ParseError("Bind can only have one child.")); - } - if child.is("jid", ns::BIND) { - check_no_attributes!(child, "jid"); - check_no_children!(child, "jid"); - jid = Some(FullJid::from_str(&child.text())?); - } else { - return Err(Error::ParseError("Unknown element in bind response.")); - } - } - - Ok(BindResponse { - jid: match jid { - None => { - return Err(Error::ParseError( - "Bind response must contain a jid element.", - )) - } - Some(jid) => jid, - }, - }) - } -} - -impl From for Element { - fn from(bind: BindResponse) -> Element { - Element::builder("bind", ns::BIND) - .append(Element::builder("jid", ns::BIND).append(bind.jid)) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(BindQuery, 12); - assert_size!(BindResponse, 36); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(BindQuery, 24); - assert_size!(BindResponse, 72); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let bind = BindQuery::try_from(elem).unwrap(); - assert_eq!(bind.resource, None); - - let elem: Element = - "Hello™" - .parse() - .unwrap(); - let bind = BindQuery::try_from(elem).unwrap(); - // FIXME: “™” should be resourceprep’d into “TM” here… - //assert_eq!(bind.resource.unwrap(), "HelloTM"); - assert_eq!(bind.resource.unwrap(), "Hello™"); - - let elem: Element = "coucou@linkmauve.fr/HelloTM" - .parse() - .unwrap(); - let bind = BindResponse::try_from(elem).unwrap(); - assert_eq!(bind.jid, FullJid::new("coucou", "linkmauve.fr", "HelloTM")); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_resource() { - let elem: Element = "resource" - .parse() - .unwrap(); - let error = BindQuery::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in resource element."); - - let elem: Element = "resource" - .parse() - .unwrap(); - let error = BindQuery::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in resource element."); - } -} diff --git a/xmpp-parsers/src/blocking.rs b/xmpp-parsers/src/blocking.rs deleted file mode 100644 index 246a715..0000000 --- a/xmpp-parsers/src/blocking.rs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::convert::TryFrom; - -generate_empty_element!( - /// The element requesting the blocklist, the result iq will contain a - /// [BlocklistResult]. - BlocklistRequest, - "blocklist", - BLOCKING -); - -impl IqGetPayload for BlocklistRequest {} - -macro_rules! generate_blocking_element { - ($(#[$meta:meta])* $elem:ident, $name:tt) => ( - $(#[$meta])* - #[derive(Debug, Clone)] - pub struct $elem { - /// List of JIDs affected by this command. - pub items: Vec, - } - - impl TryFrom for $elem { - type Error = Error; - - fn try_from(elem: Element) -> Result<$elem, Error> { - check_self!(elem, $name, BLOCKING); - check_no_attributes!(elem, $name); - let mut items = vec!(); - for child in elem.children() { - check_self!(child, "item", BLOCKING); - check_no_unknown_attributes!(child, "item", ["jid"]); - check_no_children!(child, "item"); - items.push(get_attr!(child, "jid", Required)); - } - Ok($elem { items }) - } - } - - impl From<$elem> for Element { - fn from(elem: $elem) -> Element { - Element::builder($name, ns::BLOCKING) - .append_all(elem.items.into_iter().map(|jid| { - Element::builder("item", ns::BLOCKING) - .attr("jid", jid) - })) - .build() - } - } - ); -} - -generate_blocking_element!( - /// The element containing the current blocklist, as a reply from - /// [BlocklistRequest]. - BlocklistResult, - "blocklist" -); - -impl IqResultPayload for BlocklistResult {} - -// TODO: Prevent zero elements from being allowed. -generate_blocking_element!( - /// A query to block one or more JIDs. - Block, - "block" -); - -impl IqSetPayload for Block {} - -generate_blocking_element!( - /// A query to unblock one or more JIDs, or all of them. - /// - /// Warning: not putting any JID there means clearing out the blocklist. - Unblock, - "unblock" -); - -impl IqSetPayload for Unblock {} - -generate_empty_element!( - /// The application-specific error condition when a message is blocked. - Blocked, - "blocked", - BLOCKING_ERRORS -); - -#[cfg(test)] -mod tests { - use super::*; - use jid::BareJid; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(BlocklistRequest, 0); - assert_size!(BlocklistResult, 12); - assert_size!(Block, 12); - assert_size!(Unblock, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(BlocklistRequest, 0); - assert_size!(BlocklistResult, 24); - assert_size!(Block, 24); - assert_size!(Unblock, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - let request_elem = elem.clone(); - BlocklistRequest::try_from(request_elem).unwrap(); - - let result_elem = elem.clone(); - let result = BlocklistResult::try_from(result_elem).unwrap(); - assert!(result.items.is_empty()); - - let elem: Element = "".parse().unwrap(); - let block = Block::try_from(elem).unwrap(); - assert!(block.items.is_empty()); - - let elem: Element = "".parse().unwrap(); - let unblock = Unblock::try_from(elem).unwrap(); - assert!(unblock.items.is_empty()); - } - - #[test] - fn test_items() { - let elem: Element = "".parse().unwrap(); - let two_items = vec![ - Jid::Bare(BareJid { - node: Some(String::from("coucou")), - domain: String::from("coucou"), - }), - Jid::Bare(BareJid { - node: None, - domain: String::from("domain"), - }), - ]; - - let result_elem = elem.clone(); - let result = BlocklistResult::try_from(result_elem).unwrap(); - assert_eq!(result.items, two_items); - - let elem: Element = "".parse().unwrap(); - let block = Block::try_from(elem).unwrap(); - assert_eq!(block.items, two_items); - - let elem: Element = "".parse().unwrap(); - let unblock = Unblock::try_from(elem).unwrap(); - assert_eq!(unblock.items, two_items); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let request_elem = elem.clone(); - let error = BlocklistRequest::try_from(request_elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in blocklist element."); - - let result_elem = elem.clone(); - let error = BlocklistResult::try_from(result_elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in blocklist element."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Block::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in block element."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Unblock::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in unblock element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_non_empty_blocklist_request() { - let elem: Element = "".parse().unwrap(); - let error = BlocklistRequest::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in blocklist element."); - } -} diff --git a/xmpp-parsers/src/bob.rs b/xmpp-parsers/src/bob.rs deleted file mode 100644 index 101a749..0000000 --- a/xmpp-parsers/src/bob.rs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::hashes::{Algo, Hash}; -use crate::util::error::Error; -use crate::util::helpers::Base64; -use minidom::IntoAttributeValue; -use std::str::FromStr; - -/// A Content-ID, as defined in RFC2111. -/// -/// The text value SHOULD be of the form algo+hash@bob.xmpp.org, this struct -/// enforces that format. -#[derive(Clone, Debug, PartialEq)] -pub struct ContentId { - hash: Hash, -} - -impl FromStr for ContentId { - type Err = Error; - - fn from_str(s: &str) -> Result { - let temp: Vec<_> = s.splitn(2, '@').collect(); - let temp: Vec<_> = match temp[..] { - [lhs, rhs] => { - if rhs != "bob.xmpp.org" { - return Err(Error::ParseError("Wrong domain for cid URI.")); - } - lhs.splitn(2, '+').collect() - } - _ => return Err(Error::ParseError("Missing @ in cid URI.")), - }; - let (algo, hex) = match temp[..] { - [lhs, rhs] => { - let algo = match lhs { - "sha1" => Algo::Sha_1, - "sha256" => Algo::Sha_256, - _ => unimplemented!(), - }; - (algo, rhs) - } - _ => return Err(Error::ParseError("Missing + in cid URI.")), - }; - let hash = Hash::from_hex(algo, hex)?; - Ok(ContentId { hash }) - } -} - -impl IntoAttributeValue for ContentId { - fn into_attribute_value(self) -> Option { - let algo = match self.hash.algo { - Algo::Sha_1 => "sha1", - Algo::Sha_256 => "sha256", - _ => unimplemented!(), - }; - Some(format!("{}+{}@bob.xmpp.org", algo, self.hash.to_hex())) - } -} - -generate_element!( - /// Request for an uncached cid file. - Data, "data", BOB, - attributes: [ - /// The cid in question. - cid: Required = "cid", - - /// How long to cache it (in seconds). - max_age: Option = "max-age", - - /// The MIME type of the data being transmitted. - /// - /// See the [IANA MIME Media Types Registry][1] for a list of - /// registered types, but unregistered or yet-to-be-registered are - /// accepted too. - /// - /// [1]: https://www.iana.org/assignments/media-types/media-types.xhtml - type_: Option = "type" - ], - text: ( - /// The actual data. - data: Base64> - ) -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(ContentId, 28); - assert_size!(Data, 60); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(ContentId, 56); - assert_size!(Data, 120); - } - - #[test] - fn test_simple() { - let cid: ContentId = "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org" - .parse() - .unwrap(); - assert_eq!(cid.hash.algo, Algo::Sha_1); - assert_eq!( - cid.hash.hash, - b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42" - ); - assert_eq!( - cid.into_attribute_value().unwrap(), - "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org" - ); - - let elem: Element = "".parse().unwrap(); - let data = Data::try_from(elem).unwrap(); - assert_eq!(data.cid.hash.algo, Algo::Sha_1); - assert_eq!( - data.cid.hash.hash, - b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42" - ); - assert!(data.max_age.is_none()); - assert!(data.type_.is_none()); - assert!(data.data.is_empty()); - } - - #[test] - fn invalid_cid() { - let error = "Hello world!".parse::().unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Missing @ in cid URI."); - - let error = "Hello world@bob.xmpp.org".parse::().unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Missing + in cid URI."); - - let error = "sha1+1234@coucou.linkmauve.fr" - .parse::() - .unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Wrong domain for cid URI."); - - let error = "sha1+invalid@bob.xmpp.org" - .parse::() - .unwrap_err(); - let message = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(message.to_string(), "invalid digit found in string"); - } - - #[test] - fn unknown_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Data::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in data element."); - } -} diff --git a/xmpp-parsers/src/bookmarks.rs b/xmpp-parsers/src/bookmarks.rs deleted file mode 100644 index 6276d2b..0000000 --- a/xmpp-parsers/src/bookmarks.rs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use jid::BareJid; - -generate_attribute!( - /// Whether a conference bookmark should be joined automatically. - Autojoin, - "autojoin", - bool -); - -generate_element!( - /// A conference bookmark. - Conference, "conference", BOOKMARKS, - attributes: [ - /// Whether a conference bookmark should be joined automatically. - autojoin: Default = "autojoin", - - /// The JID of the conference. - jid: Required = "jid", - - /// A user-defined name for this conference. - name: Option = "name", - ], - children: [ - /// The nick the user will use to join this conference. - nick: Option = ("nick", BOOKMARKS) => String, - - /// The password required to join this conference. - password: Option = ("password", BOOKMARKS) => String - ] -); - -generate_element!( - /// An URL bookmark. - Url, "url", BOOKMARKS, - attributes: [ - /// A user-defined name for this URL. - name: Option = "name", - - /// The URL of this bookmark. - url: Required = "url", - ] -); - -generate_element!( - /// Container element for multiple bookmarks. - #[derive(Default)] - Storage, "storage", BOOKMARKS, - children: [ - /// Conferences the user has expressed an interest in. - conferences: Vec = ("conference", BOOKMARKS) => Conference, - - /// URLs the user is interested in. - urls: Vec = ("url", BOOKMARKS) => Url - ] -); - -impl Storage { - /// Create an empty bookmarks storage. - pub fn new() -> Storage { - Storage::default() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Conference, 64); - assert_size!(Url, 24); - assert_size!(Storage, 24); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Conference, 128); - assert_size!(Url, 48); - assert_size!(Storage, 48); - } - - #[test] - fn empty() { - let elem: Element = "".parse().unwrap(); - let elem1 = elem.clone(); - let storage = Storage::try_from(elem).unwrap(); - assert_eq!(storage.conferences.len(), 0); - assert_eq!(storage.urls.len(), 0); - - let elem2 = Element::from(Storage::new()); - assert_eq!(elem1, elem2); - } - - #[test] - fn complete() { - let elem: Element = "Coucousecret".parse().unwrap(); - let storage = Storage::try_from(elem).unwrap(); - assert_eq!(storage.urls.len(), 1); - assert_eq!(storage.urls[0].clone().name.unwrap(), "Example"); - assert_eq!(storage.urls[0].url, "https://example.org/"); - assert_eq!(storage.conferences.len(), 1); - assert_eq!(storage.conferences[0].autojoin, Autojoin::True); - assert_eq!( - storage.conferences[0].jid, - BareJid::new("test-muc", "muc.localhost") - ); - assert_eq!(storage.conferences[0].clone().name.unwrap(), "Test MUC"); - assert_eq!(storage.conferences[0].clone().nick.unwrap(), "Coucou"); - assert_eq!(storage.conferences[0].clone().password.unwrap(), "secret"); - } -} diff --git a/xmpp-parsers/src/bookmarks2.rs b/xmpp-parsers/src/bookmarks2.rs deleted file mode 100644 index bbccd40..0000000 --- a/xmpp-parsers/src/bookmarks2.rs +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use std::convert::TryFrom; - -generate_attribute!( - /// Whether a conference bookmark should be joined automatically. - Autojoin, - "autojoin", - bool -); - -/// A conference bookmark. -#[derive(Debug, Clone)] -pub struct Conference { - /// Whether a conference bookmark should be joined automatically. - pub autojoin: Autojoin, - - /// A user-defined name for this conference. - pub name: Option, - - /// The nick the user will use to join this conference. - pub nick: Option, - - /// The password required to join this conference. - pub password: Option, - - /// Extensions elements. - pub extensions: Option>, -} - -impl Conference { - /// Create a new conference. - pub fn new() -> Conference { - Conference { - autojoin: Autojoin::False, - name: None, - nick: None, - password: None, - extensions: None, - } - } -} - -impl TryFrom for Conference { - type Error = Error; - - fn try_from(root: Element) -> Result { - check_self!(root, "conference", BOOKMARKS2, "Conference"); - check_no_unknown_attributes!(root, "Conference", ["autojoin", "name"]); - - let mut conference = Conference { - autojoin: get_attr!(root, "autojoin", Default), - name: get_attr!(root, "name", Option), - nick: None, - password: None, - extensions: None, - }; - - for child in root.children().cloned() { - if child.is("extensions", ns::BOOKMARKS2) { - if conference.extensions.is_some() { - return Err(Error::ParseError( - "Conference must not have more than one extensions element.", - )); - } - conference.extensions = Some(child.children().cloned().collect()); - } else if child.is("nick", ns::BOOKMARKS2) { - if conference.nick.is_some() { - return Err(Error::ParseError( - "Conference must not have more than one nick.", - )); - } - check_no_children!(child, "nick"); - check_no_attributes!(child, "nick"); - conference.nick = Some(child.text()); - } else if child.is("password", ns::BOOKMARKS2) { - if conference.password.is_some() { - return Err(Error::ParseError( - "Conference must not have more than one password.", - )); - } - check_no_children!(child, "password"); - check_no_attributes!(child, "password"); - conference.password = Some(child.text()); - } - } - - Ok(conference) - } -} - -impl From for Element { - fn from(conference: Conference) -> Element { - Element::builder("conference", ns::BOOKMARKS2) - .attr("autojoin", conference.autojoin) - .attr("name", conference.name) - .append_all( - conference - .nick - .map(|nick| Element::builder("nick", ns::BOOKMARKS2).append(nick)), - ) - .append_all( - conference - .password - .map(|password| Element::builder("password", ns::BOOKMARKS2).append(password)), - ) - .append_all(match conference.extensions { - Some(extensions) => extensions, - None => vec![], - }) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::pubsub::pubsub::Item as PubSubItem; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Conference, 52); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Conference, 104); - } - - #[test] - fn simple() { - let elem: Element = "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let conference = Conference::try_from(elem).unwrap(); - assert_eq!(conference.autojoin, Autojoin::False); - assert_eq!(conference.name, None); - assert_eq!(conference.nick, None); - assert_eq!(conference.password, None); - - let elem2 = Element::from(Conference::new()); - assert_eq!(elem1, elem2); - } - - #[test] - fn complete() { - let elem: Element = "Coucousecret".parse().unwrap(); - let conference = Conference::try_from(elem).unwrap(); - assert_eq!(conference.autojoin, Autojoin::True); - assert_eq!(conference.name, Some(String::from("Test MUC"))); - assert_eq!(conference.clone().nick.unwrap(), "Coucou"); - assert_eq!(conference.clone().password.unwrap(), "secret"); - assert_eq!(conference.clone().extensions.unwrap().len(), 1); - assert!(conference.clone().extensions.unwrap()[0].is("test", "urn:xmpp:unknown")); - } - - #[test] - fn wrapped() { - let elem: Element = "Coucousecret".parse().unwrap(); - let item = PubSubItem::try_from(elem).unwrap(); - let payload = item.payload.clone().unwrap(); - println!("FOO: payload: {:?}", payload); - // let conference = Conference::try_from(payload).unwrap(); - let conference = Conference::try_from(payload); - println!("FOO: conference: {:?}", conference); - /* - assert_eq!(conference.autojoin, Autojoin::True); - assert_eq!(conference.name, Some(String::from("Test MUC"))); - assert_eq!(conference.clone().nick.unwrap(), "Coucou"); - assert_eq!(conference.clone().password.unwrap(), "secret"); - - let elem: Element = "Coucousecret".parse().unwrap(); - let mut items = match PubSubEvent::try_from(elem) { - Ok(PubSubEvent::PublishedItems { node, items }) => { - assert_eq!(&node.0, ns::BOOKMARKS2); - items - } - _ => panic!(), - }; - assert_eq!(items.len(), 1); - let item = items.pop().unwrap(); - let payload = item.payload.clone().unwrap(); - let conference = Conference::try_from(payload).unwrap(); - assert_eq!(conference.autojoin, Autojoin::True); - assert_eq!(conference.name, Some(String::from("Test MUC"))); - assert_eq!(conference.clone().nick.unwrap(), "Coucou"); - assert_eq!(conference.clone().password.unwrap(), "secret"); - */ - } -} diff --git a/xmpp-parsers/src/caps.rs b/xmpp-parsers/src/caps.rs deleted file mode 100644 index 24f129a..0000000 --- a/xmpp-parsers/src/caps.rs +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::DataForm; -use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity}; -use crate::hashes::{Algo, Hash}; -use crate::ns; -use crate::presence::PresencePayload; -use crate::util::error::Error; -use crate::Element; -use blake2::VarBlake2b; -use digest::{Digest, Update, VariableOutput}; -use sha1::Sha1; -use sha2::{Sha256, Sha512}; -use sha3::{Sha3_256, Sha3_512}; -use std::convert::TryFrom; - -/// Represents a capability hash for a given client. -#[derive(Debug, Clone)] -pub struct Caps { - /// Deprecated list of additional feature bundles. - pub ext: Option, - - /// A URI identifying an XMPP application. - pub node: String, - - /// The hash of that application’s - /// [disco#info](../disco/struct.DiscoInfoResult.html). - /// - /// Warning: This protocol is insecure, you may want to switch to - /// [ecaps2](../ecaps2/index.html) instead, see [this - /// email](https://mail.jabber.org/pipermail/security/2009-July/000812.html). - pub hash: Hash, -} - -impl PresencePayload for Caps {} - -impl TryFrom for Caps { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "c", CAPS, "caps"); - check_no_children!(elem, "caps"); - check_no_unknown_attributes!(elem, "caps", ["hash", "ver", "ext", "node"]); - let ver: String = get_attr!(elem, "ver", Required); - let hash = Hash { - algo: get_attr!(elem, "hash", Required), - hash: base64::decode(&ver)?, - }; - Ok(Caps { - ext: get_attr!(elem, "ext", Option), - node: get_attr!(elem, "node", Required), - hash, - }) - } -} - -impl From for Element { - fn from(caps: Caps) -> Element { - Element::builder("c", ns::CAPS) - .attr("ext", caps.ext) - .attr("hash", caps.hash.algo) - .attr("node", caps.node) - .attr("ver", base64::encode(&caps.hash.hash)) - .build() - } -} - -impl Caps { - /// Create a Caps element from its node and hash. - pub fn new>(node: N, hash: Hash) -> Caps { - Caps { - ext: None, - node: node.into(), - hash, - } - } -} - -fn compute_item(field: &str) -> Vec { - let mut bytes = field.as_bytes().to_vec(); - bytes.push(b'<'); - bytes -} - -fn compute_items Vec>(things: &[T], encode: F) -> Vec { - let mut string: Vec = vec![]; - let mut accumulator: Vec> = vec![]; - for thing in things { - let bytes = encode(thing); - accumulator.push(bytes); - } - // This works using the expected i;octet collation. - accumulator.sort(); - for mut bytes in accumulator { - string.append(&mut bytes); - } - string -} - -fn compute_features(features: &[Feature]) -> Vec { - compute_items(features, |feature| compute_item(&feature.var)) -} - -fn compute_identities(identities: &[Identity]) -> Vec { - compute_items(identities, |identity| { - let lang = identity.lang.clone().unwrap_or_default(); - let name = identity.name.clone().unwrap_or_default(); - let string = format!("{}/{}/{}/{}", identity.category, identity.type_, lang, name); - let bytes = string.as_bytes(); - let mut vec = Vec::with_capacity(bytes.len()); - vec.extend_from_slice(bytes); - vec.push(b'<'); - vec - }) -} - -fn compute_extensions(extensions: &[DataForm]) -> Vec { - compute_items(extensions, |extension| { - let mut bytes = vec![]; - // TODO: maybe handle the error case? - if let Some(ref form_type) = extension.form_type { - bytes.extend_from_slice(form_type.as_bytes()); - } - bytes.push(b'<'); - for field in extension.fields.clone() { - if field.var == "FORM_TYPE" { - continue; - } - bytes.append(&mut compute_item(&field.var)); - bytes.append(&mut compute_items(&field.values, |value| { - compute_item(value) - })); - } - bytes - }) -} - -/// Applies the caps algorithm on the provided disco#info result, to generate -/// the hash input. -/// -/// Warning: This protocol is insecure, you may want to switch to -/// [ecaps2](../ecaps2/index.html) instead, see [this -/// email](https://mail.jabber.org/pipermail/security/2009-July/000812.html). -pub fn compute_disco(disco: &DiscoInfoResult) -> Vec { - let identities_string = compute_identities(&disco.identities); - let features_string = compute_features(&disco.features); - let extensions_string = compute_extensions(&disco.extensions); - - let mut final_string = vec![]; - final_string.extend(identities_string); - final_string.extend(features_string); - final_string.extend(extensions_string); - final_string -} - -fn get_hash_vec(hash: &[u8]) -> Vec { - hash.to_vec() -} - -/// Hashes the result of [compute_disco()] with one of the supported [hash -/// algorithms](../hashes/enum.Algo.html). -pub fn hash_caps(data: &[u8], algo: Algo) -> Result { - Ok(Hash { - hash: match algo { - Algo::Sha_1 => { - let hash = Sha1::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Sha_256 => { - let hash = Sha256::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Sha_512 => { - let hash = Sha512::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Sha3_256 => { - let hash = Sha3_256::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Sha3_512 => { - let hash = Sha3_512::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Blake2b_256 => { - let mut hasher = VarBlake2b::new(32).unwrap(); - hasher.update(data); - let mut vec = Vec::with_capacity(32); - hasher.finalize_variable(|slice| vec.extend_from_slice(slice)); - vec - } - Algo::Blake2b_512 => { - let mut hasher = VarBlake2b::new(64).unwrap(); - hasher.update(data); - let mut vec = Vec::with_capacity(64); - hasher.finalize_variable(|slice| vec.extend_from_slice(slice)); - vec - } - Algo::Unknown(algo) => return Err(format!("Unknown algorithm: {}.", algo)), - }, - algo, - }) -} - -/// Helper function to create the query for the disco#info corresponding to a -/// caps hash. -pub fn query_caps(caps: Caps) -> DiscoInfoQuery { - DiscoInfoQuery { - node: Some(format!("{}#{}", caps.node, base64::encode(&caps.hash.hash))), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::caps; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Caps, 52); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Caps, 104); - } - - #[test] - fn test_parse() { - let elem: Element = "".parse().unwrap(); - let caps = Caps::try_from(elem).unwrap(); - assert_eq!(caps.node, String::from("coucou")); - assert_eq!(caps.hash.algo, Algo::Sha_256); - assert_eq!( - caps.hash.hash, - base64::decode("K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=").unwrap() - ); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_child() { - let elem: Element = "K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=".parse().unwrap(); - let error = Caps::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in caps element."); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - let disco = DiscoInfoResult::try_from(elem).unwrap(); - let caps = caps::compute_disco(&disco); - assert_eq!(caps.len(), 50); - } - - #[test] - fn test_xep_5_2() { - let elem: Element = r#" - - - - - - - -"# - .parse() - .unwrap(); - - let data = b"client/pc//Exodus 0.9.1 - - - - - - - - - urn:xmpp:dataforms:softwareinfo - - - ipv4 - ipv6 - - - Mac - - - 10.5.1 - - - Psi - - - 0.11 - - - -"# - .parse() - .unwrap(); - let expected = b"client/pc/el/\xce\xa8 0.11 = ("forwarded", FORWARD) => Forwarded - ] -); - -impl MessagePayload for Received {} - -generate_element!( - /// Wrapper for a message sent from another resource. - Sent, "sent", CARBONS, - - children: [ - /// Wrapper for the enclosed message. - forwarded: Required = ("forwarded", FORWARD) => Forwarded - ] -); - -impl MessagePayload for Sent {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Enable, 0); - assert_size!(Disable, 0); - assert_size!(Private, 0); - assert_size!(Received, 212); - assert_size!(Sent, 212); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Enable, 0); - assert_size!(Disable, 0); - assert_size!(Private, 0); - assert_size!(Received, 408); - assert_size!(Sent, 408); - } - - #[test] - fn empty_elements() { - let elem: Element = "".parse().unwrap(); - Enable::try_from(elem).unwrap(); - - let elem: Element = "".parse().unwrap(); - Disable::try_from(elem).unwrap(); - - let elem: Element = "".parse().unwrap(); - Private::try_from(elem).unwrap(); - } - - #[test] - fn forwarded_elements() { - let elem: Element = " - - - -" - .parse() - .unwrap(); - let received = Received::try_from(elem).unwrap(); - assert!(received.forwarded.stanza.is_some()); - - let elem: Element = " - - - -" - .parse() - .unwrap(); - let sent = Sent::try_from(elem).unwrap(); - assert!(sent.forwarded.stanza.is_some()); - } - - #[test] - fn test_serialize_received() { - let reference: Element = "" - .parse() - .unwrap(); - - let elem: Element = "" - .parse() - .unwrap(); - let forwarded = Forwarded::try_from(elem).unwrap(); - - let received = Received { - forwarded: forwarded, - }; - - let serialized: Element = received.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn test_serialize_sent() { - let reference: Element = "" - .parse() - .unwrap(); - - let elem: Element = "" - .parse() - .unwrap(); - let forwarded = Forwarded::try_from(elem).unwrap(); - - let sent = Sent { - forwarded: forwarded, - }; - - let serialized: Element = sent.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/cert_management.rs b/xmpp-parsers/src/cert_management.rs deleted file mode 100644 index d93ebb3..0000000 --- a/xmpp-parsers/src/cert_management.rs +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use crate::util::helpers::Base64; - -generate_elem_id!( - /// The name of a certificate. - Name, - "name", - SASL_CERT -); - -generate_element!( - /// An X.509 certificate. - Cert, "x509cert", SASL_CERT, - text: ( - /// The BER X.509 data. - data: Base64> - ) -); - -generate_element!( - /// For the client to upload an X.509 certificate. - Append, "append", SASL_CERT, - children: [ - /// The name of this certificate. - name: Required = ("name", SASL_CERT) => Name, - - /// The X.509 certificate to set. - cert: Required = ("x509cert", SASL_CERT) => Cert, - - /// This client is forbidden from managing certificates. - no_cert_management: Present<_> = ("no-cert-management", SASL_CERT) => bool - ] -); - -impl IqSetPayload for Append {} - -generate_empty_element!( - /// Client requests the current list of X.509 certificates. - ListCertsQuery, - "items", - SASL_CERT -); - -impl IqGetPayload for ListCertsQuery {} - -generate_elem_id!( - /// One resource currently using a certificate. - Resource, - "resource", - SASL_CERT -); - -generate_element!( - /// A list of resources currently using this certificate. - Users, "users", SASL_CERT, - children: [ - /// Resources currently using this certificate. - resources: Vec = ("resource", SASL_CERT) => Resource - ] -); - -generate_element!( - /// An X.509 certificate being set for this user. - Item, "item", SASL_CERT, - children: [ - /// The name of this certificate. - name: Required = ("name", SASL_CERT) => Name, - - /// The X.509 certificate to set. - cert: Required = ("x509cert", SASL_CERT) => Cert, - - /// This client is forbidden from managing certificates. - no_cert_management: Present<_> = ("no-cert-management", SASL_CERT) => bool, - - /// List of resources currently using this certificate. - users: Option = ("users", SASL_CERT) => Users - ] -); - -generate_element!( - /// Server answers with the current list of X.509 certificates. - ListCertsResponse, "items", SASL_CERT, - children: [ - /// List of certificates. - items: Vec = ("item", SASL_CERT) => Item - ] -); - -impl IqResultPayload for ListCertsResponse {} - -generate_element!( - /// Client disables an X.509 certificate. - Disable, "disable", SASL_CERT, - children: [ - /// Name of the certificate to disable. - name: Required = ("name", SASL_CERT) => Name - ] -); - -impl IqSetPayload for Disable {} - -generate_element!( - /// Client revokes an X.509 certificate. - Revoke, "revoke", SASL_CERT, - children: [ - /// Name of the certificate to revoke. - name: Required = ("name", SASL_CERT) => Name - ] -); - -impl IqSetPayload for Revoke {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ns; - use crate::Element; - use std::convert::TryFrom; - use std::str::FromStr; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Append, 28); - assert_size!(Disable, 12); - assert_size!(Revoke, 12); - assert_size!(ListCertsQuery, 0); - assert_size!(ListCertsResponse, 12); - assert_size!(Item, 40); - assert_size!(Resource, 12); - assert_size!(Users, 12); - assert_size!(Cert, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Append, 56); - assert_size!(Disable, 24); - assert_size!(Revoke, 24); - assert_size!(ListCertsQuery, 0); - assert_size!(ListCertsResponse, 24); - assert_size!(Item, 80); - assert_size!(Resource, 24); - assert_size!(Users, 24); - assert_size!(Cert, 24); - } - - #[test] - fn simple() { - let elem: Element = "Mobile ClientAAAA".parse().unwrap(); - let append = Append::try_from(elem).unwrap(); - assert_eq!(append.name.0, "Mobile Client"); - assert_eq!(append.cert.data, b"\0\0\0"); - - let elem: Element = - "Mobile Client" - .parse() - .unwrap(); - let disable = Disable::try_from(elem).unwrap(); - assert_eq!(disable.name.0, "Mobile Client"); - - let elem: Element = - "Mobile Client" - .parse() - .unwrap(); - let revoke = Revoke::try_from(elem).unwrap(); - assert_eq!(revoke.name.0, "Mobile Client"); - } - - #[test] - fn list() { - let elem: Element = r#" - - - Mobile Client - AAAA - - Phone - - - - Laptop - BBBB - - "# - .parse() - .unwrap(); - let mut list = ListCertsResponse::try_from(elem).unwrap(); - assert_eq!(list.items.len(), 2); - - let item = list.items.pop().unwrap(); - assert_eq!(item.name.0, "Laptop"); - assert_eq!(item.cert.data, [4, 16, 65]); - assert!(item.users.is_none()); - - let item = list.items.pop().unwrap(); - assert_eq!(item.name.0, "Mobile Client"); - assert_eq!(item.cert.data, b"\0\0\0"); - assert_eq!(item.users.unwrap().resources.len(), 1); - } - - #[test] - fn test_serialise() { - let append = Append { - name: Name::from_str("Mobile Client").unwrap(), - cert: Cert { - data: b"\0\0\0".to_vec(), - }, - no_cert_management: false, - }; - let elem: Element = append.into(); - assert!(elem.is("append", ns::SASL_CERT)); - - let disable = Disable { - name: Name::from_str("Mobile Client").unwrap(), - }; - let elem: Element = disable.into(); - assert!(elem.is("disable", ns::SASL_CERT)); - let elem = elem.children().cloned().collect::>().pop().unwrap(); - assert!(elem.is("name", ns::SASL_CERT)); - assert_eq!(elem.text(), "Mobile Client"); - } - - #[test] - fn test_serialize_item() { - let reference: Element = "Mobile ClientAAAA" - .parse() - .unwrap(); - - let item = Item { - name: Name::from_str("Mobile Client").unwrap(), - cert: Cert { - data: b"\0\0\0".to_vec(), - }, - no_cert_management: false, - users: None, - }; - - let serialized: Element = item.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn test_serialize_append() { - let reference: Element = "Mobile ClientAAAA" - .parse() - .unwrap(); - - let append = Append { - name: Name::from_str("Mobile Client").unwrap(), - cert: Cert { - data: b"\0\0\0".to_vec(), - }, - no_cert_management: false, - }; - - let serialized: Element = append.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn test_serialize_disable() { - let reference: Element = - "Mobile Client" - .parse() - .unwrap(); - - let disable = Disable { - name: Name::from_str("Mobile Client").unwrap(), - }; - - let serialized: Element = disable.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn test_serialize_revoke() { - let reference: Element = - "Mobile Client" - .parse() - .unwrap(); - - let revoke = Revoke { - name: Name::from_str("Mobile Client").unwrap(), - }; - - let serialized: Element = revoke.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/chatstates.rs b/xmpp-parsers/src/chatstates.rs deleted file mode 100644 index 5e98112..0000000 --- a/xmpp-parsers/src/chatstates.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; - -generate_element_enum!( - /// Enum representing chatstate elements part of the - /// `http://jabber.org/protocol/chatstates` namespace. - ChatState, "chatstate", CHATSTATES, { - /// `` - Active => "active", - - /// `` - Composing => "composing", - - /// `` - Gone => "gone", - - /// `` - Inactive => "inactive", - - /// `` - Paused => "paused", - } -); - -impl MessagePayload for ChatState {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ns; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[test] - fn test_size() { - assert_size!(ChatState, 1); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - ChatState::try_from(elem).unwrap(); - } - - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let error = ChatState::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "This is not a chatstate element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = ChatState::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in chatstate element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - let elem: Element = "" - .parse() - .unwrap(); - let error = ChatState::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in chatstate element."); - } - - #[test] - fn test_serialise() { - let chatstate = ChatState::Active; - let elem: Element = chatstate.into(); - assert!(elem.is("active", ns::CHATSTATES)); - } -} diff --git a/xmpp-parsers/src/component.rs b/xmpp-parsers/src/component.rs deleted file mode 100644 index b8120ff..0000000 --- a/xmpp-parsers/src/component.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::util::helpers::PlainText; -use digest::Digest; -use sha1::Sha1; - -generate_element!( - /// The main authentication mechanism for components. - #[derive(Default)] - Handshake, "handshake", COMPONENT, - text: ( - /// If Some, contains the hex-encoded SHA-1 of the concatenation of the - /// stream id and the password, and is used to authenticate against the - /// server. - /// - /// If None, it is the successful reply from the server, the stream is now - /// fully established and both sides can now exchange stanzas. - data: PlainText> - ) -); - -impl Handshake { - /// Creates a successful reply from a server. - pub fn new() -> Handshake { - Handshake::default() - } - - /// Creates an authentication request from the component. - pub fn from_password_and_stream_id(password: &str, stream_id: &str) -> Handshake { - let input = String::from(stream_id) + password; - let hash = Sha1::digest(input.as_bytes()); - let content = format!("{:x}", hash); - Handshake { - data: Some(content), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Handshake, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Handshake, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let handshake = Handshake::try_from(elem).unwrap(); - assert_eq!(handshake.data, None); - - let elem: Element = "Coucou" - .parse() - .unwrap(); - let handshake = Handshake::try_from(elem).unwrap(); - assert_eq!(handshake.data, Some(String::from("Coucou"))); - } - - #[test] - fn test_constructors() { - let handshake = Handshake::new(); - assert_eq!(handshake.data, None); - - let handshake = Handshake::from_password_and_stream_id("123456", "sid"); - assert_eq!( - handshake.data, - Some(String::from("9accec263ab84a43c6037ccf7cd48cb1d3f6df8e")) - ); - } -} diff --git a/xmpp-parsers/src/csi.rs b/xmpp-parsers/src/csi.rs deleted file mode 100644 index bfd44f8..0000000 --- a/xmpp-parsers/src/csi.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -generate_empty_element!( - /// Stream:feature sent by the server to advertise it supports CSI. - Feature, - "csi", - CSI -); - -generate_empty_element!( - /// Client indicates it is inactive. - Inactive, - "inactive", - CSI -); - -generate_empty_element!( - /// Client indicates it is active again. - Active, - "active", - CSI -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::ns; - use crate::Element; - use std::convert::TryFrom; - - #[test] - fn test_size() { - assert_size!(Feature, 0); - assert_size!(Inactive, 0); - assert_size!(Active, 0); - } - - #[test] - fn parsing() { - let elem: Element = "".parse().unwrap(); - Feature::try_from(elem).unwrap(); - - let elem: Element = "".parse().unwrap(); - Inactive::try_from(elem).unwrap(); - - let elem: Element = "".parse().unwrap(); - Active::try_from(elem).unwrap(); - } - - #[test] - fn serialising() { - let elem: Element = Feature.into(); - assert!(elem.is("csi", ns::CSI)); - - let elem: Element = Inactive.into(); - assert!(elem.is("inactive", ns::CSI)); - - let elem: Element = Active.into(); - assert!(elem.is("active", ns::CSI)); - } -} diff --git a/xmpp-parsers/src/data_forms.rs b/xmpp-parsers/src/data_forms.rs deleted file mode 100644 index 596a317..0000000 --- a/xmpp-parsers/src/data_forms.rs +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::media_element::MediaElement; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use std::convert::TryFrom; - -generate_element!( - /// Represents one of the possible values for a list- field. - Option_, "option", DATA_FORMS, - attributes: [ - /// The optional label to be displayed to the user for this option. - label: Option = "label" - ], - children: [ - /// The value returned to the server when selecting this option. - value: Required = ("value", DATA_FORMS) => String - ] -); - -generate_attribute!( - /// The type of a [field](struct.Field.html) element. - FieldType, "type", { - /// This field can only take the values "0" or "false" for a false - /// value, and "1" or "true" for a true value. - Boolean => "boolean", - - /// This field describes data, it must not be sent back to the - /// requester. - Fixed => "fixed", - - /// This field is hidden, it should not be displayed to the user but - /// should be sent back to the requester. - Hidden => "hidden", - - /// This field accepts one or more [JIDs](../../jid/struct.Jid.html). - /// A client may want to let the user autocomplete them based on their - /// contacts list for instance. - JidMulti => "jid-multi", - - /// This field accepts one [JID](../../jid/struct.Jid.html). A client - /// may want to let the user autocomplete it based on their contacts - /// list for instance. - JidSingle => "jid-single", - - /// This field accepts one or more values from the list provided as - /// [options](struct.Option_.html). - ListMulti => "list-multi", - - /// This field accepts one value from the list provided as - /// [options](struct.Option_.html). - ListSingle => "list-single", - - /// This field accepts one or more free form text lines. - TextMulti => "text-multi", - - /// This field accepts one free form password, a client should hide it - /// in its user interface. - TextPrivate => "text-private", - - /// This field accepts one free form text line. - TextSingle => "text-single", - }, Default = TextSingle -); - -/// Represents a field in a [data form](struct.DataForm.html). -#[derive(Debug, Clone, PartialEq)] -pub struct Field { - /// The unique identifier for this field, in the form. - pub var: String, - - /// The type of this field. - pub type_: FieldType, - - /// The label to be possibly displayed to the user for this field. - pub label: Option, - - /// The form will be rejected if this field isn’t present. - pub required: bool, - - /// A list of allowed values. - pub options: Vec, - - /// The values provided for this field. - pub values: Vec, - - /// A list of media related to this field. - pub media: Vec, -} - -impl Field { - fn is_list(&self) -> bool { - self.type_ == FieldType::ListSingle || self.type_ == FieldType::ListMulti - } -} - -impl TryFrom for Field { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "field", DATA_FORMS); - check_no_unknown_attributes!(elem, "field", ["label", "type", "var"]); - let mut field = Field { - var: get_attr!(elem, "var", Required), - type_: get_attr!(elem, "type", Default), - label: get_attr!(elem, "label", Option), - required: false, - options: vec![], - values: vec![], - media: vec![], - }; - for element in elem.children() { - if element.is("value", ns::DATA_FORMS) { - check_no_children!(element, "value"); - check_no_attributes!(element, "value"); - field.values.push(element.text()); - } else if element.is("required", ns::DATA_FORMS) { - if field.required { - return Err(Error::ParseError("More than one required element.")); - } - check_no_children!(element, "required"); - check_no_attributes!(element, "required"); - field.required = true; - } else if element.is("option", ns::DATA_FORMS) { - if !field.is_list() { - return Err(Error::ParseError("Option element found in non-list field.")); - } - let option = Option_::try_from(element.clone())?; - field.options.push(option); - } else if element.is("media", ns::MEDIA_ELEMENT) { - let media_element = MediaElement::try_from(element.clone())?; - field.media.push(media_element); - } else { - return Err(Error::ParseError( - "Field child isn’t a value, option or media element.", - )); - } - } - Ok(field) - } -} - -impl From for Element { - fn from(field: Field) -> Element { - Element::builder("field", ns::DATA_FORMS) - .attr("var", field.var) - .attr("type", field.type_) - .attr("label", field.label) - .append_all(if field.required { - Some(Element::builder("required", ns::DATA_FORMS)) - } else { - None - }) - .append_all(field.options.iter().cloned().map(Element::from)) - .append_all( - field - .values - .into_iter() - .map(|value| Element::builder("value", ns::DATA_FORMS).append(value)), - ) - .append_all(field.media.iter().cloned().map(Element::from)) - .build() - } -} - -generate_attribute!( - /// Represents the type of a [data form](struct.DataForm.html). - DataFormType, "type", { - /// This is a cancel request for a prior type="form" data form. - Cancel => "cancel", - - /// This is a request for the recipient to fill this form and send it - /// back as type="submit". - Form => "form", - - /// This is a result form, which contains what the requester asked for. - Result_ => "result", - - /// This is a complete response to a form received before. - Submit => "submit", - } -); - -/// This is a form to be sent to another entity for filling. -#[derive(Debug, Clone, PartialEq)] -pub struct DataForm { - /// The type of this form, telling the other party which action to execute. - pub type_: DataFormType, - - /// An easy accessor for the FORM_TYPE of this form, see - /// [XEP-0068](https://xmpp.org/extensions/xep-0068.html) for more - /// information. - pub form_type: Option, - - /// The title of this form. - pub title: Option, - - /// The instructions given with this form. - pub instructions: Option, - - /// A list of fields comprising this form. - pub fields: Vec, -} - -impl TryFrom for DataForm { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "x", DATA_FORMS); - check_no_unknown_attributes!(elem, "x", ["type"]); - let type_ = get_attr!(elem, "type", Required); - let mut form = DataForm { - type_, - form_type: None, - title: None, - instructions: None, - fields: vec![], - }; - for child in elem.children() { - if child.is("title", ns::DATA_FORMS) { - if form.title.is_some() { - return Err(Error::ParseError("More than one title in form element.")); - } - check_no_children!(child, "title"); - check_no_attributes!(child, "title"); - form.title = Some(child.text()); - } else if child.is("instructions", ns::DATA_FORMS) { - if form.instructions.is_some() { - return Err(Error::ParseError( - "More than one instructions in form element.", - )); - } - check_no_children!(child, "instructions"); - check_no_attributes!(child, "instructions"); - form.instructions = Some(child.text()); - } else if child.is("field", ns::DATA_FORMS) { - let field = Field::try_from(child.clone())?; - if field.var == "FORM_TYPE" { - let mut field = field; - if form.form_type.is_some() { - return Err(Error::ParseError("More than one FORM_TYPE in a data form.")); - } - if field.type_ != FieldType::Hidden { - return Err(Error::ParseError("Invalid field type for FORM_TYPE.")); - } - if field.values.len() != 1 { - return Err(Error::ParseError("Wrong number of values in FORM_TYPE.")); - } - form.form_type = field.values.pop(); - } else { - form.fields.push(field); - } - } else { - return Err(Error::ParseError("Unknown child in data form element.")); - } - } - Ok(form) - } -} - -impl From for Element { - fn from(form: DataForm) -> Element { - Element::builder("x", ns::DATA_FORMS) - .attr("type", form.type_) - .append_all( - form.title - .map(|title| Element::builder("title", ns::DATA_FORMS).append(title)), - ) - .append_all( - form.instructions - .map(|text| Element::builder("instructions", ns::DATA_FORMS).append(text)), - ) - .append_all(form.form_type.map(|form_type| { - Element::builder("field", ns::DATA_FORMS) - .attr("var", "FORM_TYPE") - .attr("type", "hidden") - .append(Element::builder("value", ns::DATA_FORMS).append(form_type)) - })) - .append_all(form.fields.iter().cloned().map(Element::from)) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Option_, 24); - assert_size!(FieldType, 1); - assert_size!(Field, 64); - assert_size!(DataFormType, 1); - assert_size!(DataForm, 52); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Option_, 48); - assert_size!(FieldType, 1); - assert_size!(Field, 128); - assert_size!(DataFormType, 1); - assert_size!(DataForm, 104); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - let form = DataForm::try_from(elem).unwrap(); - assert_eq!(form.type_, DataFormType::Result_); - assert!(form.form_type.is_none()); - assert!(form.fields.is_empty()); - } - - #[test] - fn test_invalid() { - let elem: Element = "".parse().unwrap(); - let error = DataForm::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'type' missing."); - - let elem: Element = "".parse().unwrap(); - let error = DataForm::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'type' attribute."); - } - - #[test] - fn test_wrong_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = DataForm::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in data form element."); - } - - #[test] - fn option() { - let elem: Element = - "" - .parse() - .unwrap(); - let option = Option_::try_from(elem).unwrap(); - assert_eq!(&option.label.unwrap(), "Coucou !"); - assert_eq!(&option.value, "coucou"); - - let elem: Element = "".parse().unwrap(); - let error = Option_::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!( - message, - "Element option must not have more than one value child." - ); - } -} diff --git a/xmpp-parsers/src/date.rs b/xmpp-parsers/src/date.rs deleted file mode 100644 index ee3080b..0000000 --- a/xmpp-parsers/src/date.rs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::util::error::Error; -use chrono::{DateTime as ChronoDateTime, FixedOffset}; -use minidom::{IntoAttributeValue, Node}; -use std::str::FromStr; - -/// Implements the DateTime profile of XEP-0082, which represents a -/// non-recurring moment in time, with an accuracy of seconds or fraction of -/// seconds, and includes a timezone. -#[derive(Debug, Clone, PartialEq)] -pub struct DateTime(pub ChronoDateTime); - -impl DateTime { - /// Retrieves the associated timezone. - pub fn timezone(&self) -> FixedOffset { - self.0.timezone() - } - - /// Returns a new `DateTime` with a different timezone. - pub fn with_timezone(&self, tz: FixedOffset) -> DateTime { - DateTime(self.0.with_timezone(&tz)) - } - - /// Formats this `DateTime` with the specified format string. - pub fn format(&self, fmt: &str) -> String { - format!("{}", self.0.format(fmt)) - } -} - -impl FromStr for DateTime { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(DateTime(ChronoDateTime::parse_from_rfc3339(s)?)) - } -} - -impl IntoAttributeValue for DateTime { - fn into_attribute_value(self) -> Option { - Some(self.0.to_rfc3339()) - } -} - -impl Into for DateTime { - fn into(self) -> Node { - Node::Text(self.0.to_rfc3339()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use chrono::{Datelike, Timelike}; - - // DateTime’s size doesn’t depend on the architecture. - #[test] - fn test_size() { - assert_size!(DateTime, 16); - } - - #[test] - fn test_simple() { - let date: DateTime = "2002-09-10T23:08:25Z".parse().unwrap(); - assert_eq!(date.0.year(), 2002); - assert_eq!(date.0.month(), 9); - assert_eq!(date.0.day(), 10); - assert_eq!(date.0.hour(), 23); - assert_eq!(date.0.minute(), 08); - assert_eq!(date.0.second(), 25); - assert_eq!(date.0.nanosecond(), 0); - assert_eq!(date.0.timezone(), FixedOffset::east(0)); - } - - #[test] - fn test_invalid_date() { - // There is no thirteenth month. - let error = DateTime::from_str("2017-13-01T12:23:34Z").unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input is out of range"); - - // Timezone ≥24:00 aren’t allowed. - let error = DateTime::from_str("2017-05-27T12:11:02+25:00").unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input is out of range"); - - // Timezone without the : separator aren’t allowed. - let error = DateTime::from_str("2017-05-27T12:11:02+0100").unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input contains invalid characters"); - - // No seconds, error message could be improved. - let error = DateTime::from_str("2017-05-27T12:11+01:00").unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input contains invalid characters"); - - // TODO: maybe we’ll want to support this one, as per XEP-0082 §4. - let error = DateTime::from_str("20170527T12:11:02+01:00").unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input contains invalid characters"); - - // No timezone. - let error = DateTime::from_str("2017-05-27T12:11:02").unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "premature end of input"); - } - - #[test] - fn test_serialise() { - let date = - DateTime(ChronoDateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap()); - let attr = date.into_attribute_value(); - assert_eq!(attr, Some(String::from("2017-05-21T20:19:55+01:00"))); - } -} diff --git a/xmpp-parsers/src/delay.rs b/xmpp-parsers/src/delay.rs deleted file mode 100644 index 897b98c..0000000 --- a/xmpp-parsers/src/delay.rs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::date::DateTime; -use crate::message::MessagePayload; -use crate::presence::PresencePayload; -use crate::util::helpers::PlainText; -use jid::Jid; - -generate_element!( - /// Notes when and by whom a message got stored for later delivery. - Delay, "delay", DELAY, - attributes: [ - /// The entity which delayed this message. - from: Option = "from", - - /// The time at which this message got stored. - stamp: Required = "stamp" - ], - text: ( - /// The optional reason this message got delayed. - data: PlainText> - ) -); - -impl MessagePayload for Delay {} -impl PresencePayload for Delay {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use jid::BareJid; - use std::convert::TryFrom; - use std::str::FromStr; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Delay, 68); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Delay, 120); - } - - #[test] - fn test_simple() { - let elem: Element = - "" - .parse() - .unwrap(); - let delay = Delay::try_from(elem).unwrap(); - assert_eq!(delay.from.unwrap(), BareJid::domain("capulet.com")); - assert_eq!( - delay.stamp, - DateTime::from_str("2002-09-10T23:08:25Z").unwrap() - ); - assert_eq!(delay.data, None); - } - - #[test] - fn test_unknown() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Delay::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "This is not a delay element."); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Delay::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in delay element."); - } - - #[test] - fn test_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let delay = Delay { - from: None, - stamp: DateTime::from_str("2002-09-10T23:08:25Z").unwrap(), - data: None, - }; - let elem2 = delay.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_serialise_data() { - let elem: Element = "Reason".parse().unwrap(); - let delay = Delay { - from: Some(Jid::Bare(BareJid::new("juliet", "example.org"))), - stamp: DateTime::from_str("2002-09-10T23:08:25Z").unwrap(), - data: Some(String::from("Reason")), - }; - let elem2 = delay.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/disco.rs b/xmpp-parsers/src/disco.rs deleted file mode 100644 index 1bdb7a0..0000000 --- a/xmpp-parsers/src/disco.rs +++ /dev/null @@ -1,452 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::{DataForm, DataFormType}; -use crate::iq::{IqGetPayload, IqResultPayload}; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::convert::TryFrom; - -generate_element!( -/// Structure representing a `` element. -/// -/// It should only be used in an ``, as it can only represent -/// the request, and not a result. -DiscoInfoQuery, "query", DISCO_INFO, -attributes: [ - /// Node on which we are doing the discovery. - node: Option = "node", -]); - -impl IqGetPayload for DiscoInfoQuery {} - -generate_element!( -#[derive(Eq, Hash)] -/// Structure representing a `` element. -Feature, "feature", DISCO_INFO, -attributes: [ - /// Namespace of the feature we want to represent. - var: Required = "var", -]); - -impl Feature { - /// Create a new `` with the according `@var`. - pub fn new>(var: S) -> Feature { - Feature { var: var.into() } - } -} - -generate_element!( - /// Structure representing an `` element. - Identity, "identity", DISCO_INFO, - attributes: [ - /// Category of this identity. - // TODO: use an enum here. - category: RequiredNonEmpty = "category", - - /// Type of this identity. - // TODO: use an enum here. - type_: RequiredNonEmpty = "type", - - /// Lang of the name of this identity. - lang: Option = "xml:lang", - - /// Name of this identity. - name: Option = "name", - ] -); - -impl Identity { - /// Create a new ``. - pub fn new(category: C, type_: T, lang: L, name: N) -> Identity - where - C: Into, - T: Into, - L: Into, - N: Into, - { - Identity { - category: category.into(), - type_: type_.into(), - lang: Some(lang.into()), - name: Some(name.into()), - } - } - - /// Create a new `` without a name. - pub fn new_anonymous(category: C, type_: T) -> Identity - where - C: Into, - T: Into, - { - Identity { - category: category.into(), - type_: type_.into(), - lang: None, - name: None, - } - } -} - -/// Structure representing a `` element. -/// -/// It should only be used in an ``, as it can only -/// represent the result, and not a request. -#[derive(Debug, Clone)] -pub struct DiscoInfoResult { - /// Node on which we have done this discovery. - pub node: Option, - - /// List of identities exposed by this entity. - pub identities: Vec, - - /// List of features supported by this entity. - pub features: Vec, - - /// List of extensions reported by this entity. - pub extensions: Vec, -} - -impl IqResultPayload for DiscoInfoResult {} - -impl TryFrom for DiscoInfoResult { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "query", DISCO_INFO, "disco#info result"); - check_no_unknown_attributes!(elem, "disco#info result", ["node"]); - - let mut result = DiscoInfoResult { - node: get_attr!(elem, "node", Option), - identities: vec![], - features: vec![], - extensions: vec![], - }; - - for child in elem.children() { - if child.is("identity", ns::DISCO_INFO) { - let identity = Identity::try_from(child.clone())?; - result.identities.push(identity); - } else if child.is("feature", ns::DISCO_INFO) { - let feature = Feature::try_from(child.clone())?; - result.features.push(feature); - } else if child.is("x", ns::DATA_FORMS) { - let data_form = DataForm::try_from(child.clone())?; - if data_form.type_ != DataFormType::Result_ { - return Err(Error::ParseError( - "Data form must have a 'result' type in disco#info.", - )); - } - if data_form.form_type.is_none() { - return Err(Error::ParseError("Data form found without a FORM_TYPE.")); - } - result.extensions.push(data_form); - } else { - return Err(Error::ParseError("Unknown element in disco#info.")); - } - } - - if result.identities.is_empty() { - return Err(Error::ParseError( - "There must be at least one identity in disco#info.", - )); - } - if result.features.is_empty() { - return Err(Error::ParseError( - "There must be at least one feature in disco#info.", - )); - } - if !result.features.contains(&Feature { - var: ns::DISCO_INFO.to_owned(), - }) { - return Err(Error::ParseError( - "disco#info feature not present in disco#info.", - )); - } - - Ok(result) - } -} - -impl From for Element { - fn from(disco: DiscoInfoResult) -> Element { - Element::builder("query", ns::DISCO_INFO) - .attr("node", disco.node) - .append_all(disco.identities.into_iter()) - .append_all(disco.features.into_iter()) - .append_all(disco.extensions.iter().cloned().map(Element::from)) - .build() - } -} - -generate_element!( -/// Structure representing a `` element. -/// -/// It should only be used in an ``, as it can only represent -/// the request, and not a result. -DiscoItemsQuery, "query", DISCO_ITEMS, -attributes: [ - /// Node on which we are doing the discovery. - node: Option = "node", -]); - -impl IqGetPayload for DiscoItemsQuery {} - -generate_element!( -/// Structure representing an `` element. -Item, "item", DISCO_ITEMS, -attributes: [ - /// JID of the entity pointed by this item. - jid: Required = "jid", - /// Node of the entity pointed by this item. - node: Option = "node", - /// Name of the entity pointed by this item. - name: Option = "name", -]); - -generate_element!( - /// Structure representing a `` element. - /// - /// It should only be used in an ``, as it can only - /// represent the result, and not a request. - DiscoItemsResult, "query", DISCO_ITEMS, - attributes: [ - /// Node on which we have done this discovery. - node: Option = "node" - ], - children: [ - /// List of items pointed by this entity. - items: Vec = ("item", DISCO_ITEMS) => Item - ] -); - -impl IqResultPayload for DiscoItemsResult {} - -#[cfg(test)] -mod tests { - use super::*; - use jid::BareJid; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Identity, 48); - assert_size!(Feature, 12); - assert_size!(DiscoInfoQuery, 12); - assert_size!(DiscoInfoResult, 48); - - assert_size!(Item, 64); - assert_size!(DiscoItemsQuery, 12); - assert_size!(DiscoItemsResult, 24); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Identity, 96); - assert_size!(Feature, 24); - assert_size!(DiscoInfoQuery, 24); - assert_size!(DiscoInfoResult, 96); - - assert_size!(Item, 128); - assert_size!(DiscoItemsQuery, 24); - assert_size!(DiscoItemsResult, 48); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - let query = DiscoInfoResult::try_from(elem).unwrap(); - assert!(query.node.is_none()); - assert_eq!(query.identities.len(), 1); - assert_eq!(query.features.len(), 1); - assert!(query.extensions.is_empty()); - } - - #[test] - fn test_identity_after_feature() { - let elem: Element = "".parse().unwrap(); - let query = DiscoInfoResult::try_from(elem).unwrap(); - assert_eq!(query.identities.len(), 1); - assert_eq!(query.features.len(), 1); - assert!(query.extensions.is_empty()); - } - - #[test] - fn test_feature_after_dataform() { - let elem: Element = "coucou".parse().unwrap(); - let query = DiscoInfoResult::try_from(elem).unwrap(); - assert_eq!(query.identities.len(), 1); - assert_eq!(query.features.len(), 1); - assert_eq!(query.extensions.len(), 1); - } - - #[test] - fn test_extension() { - let elem: Element = "example".parse().unwrap(); - let elem1 = elem.clone(); - let query = DiscoInfoResult::try_from(elem).unwrap(); - assert!(query.node.is_none()); - assert_eq!(query.identities.len(), 1); - assert_eq!(query.features.len(), 1); - assert_eq!(query.extensions.len(), 1); - assert_eq!(query.extensions[0].form_type, Some(String::from("example"))); - - let elem2 = query.into(); - assert_eq!(elem1, elem2); - } - - #[test] - fn test_invalid() { - let elem: Element = - "" - .parse() - .unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown element in disco#info."); - } - - #[test] - fn test_invalid_identity() { - let elem: Element = - "" - .parse() - .unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'category' missing."); - - let elem: Element = - "" - .parse() - .unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'category' must not be empty."); - - let elem: Element = "".parse().unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'type' missing."); - - let elem: Element = "".parse().unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'type' must not be empty."); - } - - #[test] - fn test_invalid_feature() { - let elem: Element = - "" - .parse() - .unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'var' missing."); - } - - #[test] - fn test_invalid_result() { - let elem: Element = "" - .parse() - .unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!( - message, - "There must be at least one identity in disco#info." - ); - - let elem: Element = "".parse().unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "There must be at least one feature in disco#info."); - - let elem: Element = "".parse().unwrap(); - let error = DiscoInfoResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "disco#info feature not present in disco#info."); - } - - #[test] - fn test_simple_items() { - let elem: Element = "" - .parse() - .unwrap(); - let query = DiscoItemsQuery::try_from(elem).unwrap(); - assert!(query.node.is_none()); - - let elem: Element = "" - .parse() - .unwrap(); - let query = DiscoItemsQuery::try_from(elem).unwrap(); - assert_eq!(query.node, Some(String::from("coucou"))); - } - - #[test] - fn test_simple_items_result() { - let elem: Element = "" - .parse() - .unwrap(); - let query = DiscoItemsResult::try_from(elem).unwrap(); - assert!(query.node.is_none()); - assert!(query.items.is_empty()); - - let elem: Element = "" - .parse() - .unwrap(); - let query = DiscoItemsResult::try_from(elem).unwrap(); - assert_eq!(query.node, Some(String::from("coucou"))); - assert!(query.items.is_empty()); - } - - #[test] - fn test_answers_items_result() { - let elem: Element = "".parse().unwrap(); - let query = DiscoItemsResult::try_from(elem).unwrap(); - let elem2 = Element::from(query); - let query = DiscoItemsResult::try_from(elem2).unwrap(); - assert_eq!(query.items.len(), 2); - assert_eq!(query.items[0].jid, BareJid::domain("component")); - assert_eq!(query.items[0].node, None); - assert_eq!(query.items[0].name, None); - assert_eq!(query.items[1].jid, BareJid::domain("component2")); - assert_eq!(query.items[1].node, Some(String::from("test"))); - assert_eq!(query.items[1].name, Some(String::from("A component"))); - } -} diff --git a/xmpp-parsers/src/ecaps2.rs b/xmpp-parsers/src/ecaps2.rs deleted file mode 100644 index 4ca1f2a..0000000 --- a/xmpp-parsers/src/ecaps2.rs +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::DataForm; -use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity}; -use crate::hashes::{Algo, Hash}; -use crate::ns; -use crate::presence::PresencePayload; -use crate::util::error::Error; -use blake2::VarBlake2b; -use digest::{Digest, Update, VariableOutput}; -use sha2::{Sha256, Sha512}; -use sha3::{Sha3_256, Sha3_512}; - -generate_element!( - /// Represents a set of capability hashes, all of them must correspond to - /// the same input [disco#info](../disco/struct.DiscoInfoResult.html), - /// using different [algorithms](../hashes/enum.Algo.html). - ECaps2, "c", ECAPS2, - children: [ - /// Hashes of the [disco#info](../disco/struct.DiscoInfoResult.html). - hashes: Vec = ("hash", HASHES) => Hash - ] -); - -impl PresencePayload for ECaps2 {} - -impl ECaps2 { - /// Create an ECaps2 element from a list of hashes. - pub fn new(hashes: Vec) -> ECaps2 { - ECaps2 { hashes } - } -} - -fn compute_item(field: &str) -> Vec { - let mut bytes = field.as_bytes().to_vec(); - bytes.push(0x1f); - bytes -} - -fn compute_items Vec>(things: &[T], separator: u8, encode: F) -> Vec { - let mut string: Vec = vec![]; - let mut accumulator: Vec> = vec![]; - for thing in things { - let bytes = encode(thing); - accumulator.push(bytes); - } - // This works using the expected i;octet collation. - accumulator.sort(); - for mut bytes in accumulator { - string.append(&mut bytes); - } - string.push(separator); - string -} - -fn compute_features(features: &[Feature]) -> Vec { - compute_items(features, 0x1c, |feature| compute_item(&feature.var)) -} - -fn compute_identities(identities: &[Identity]) -> Vec { - compute_items(identities, 0x1c, |identity| { - let mut bytes = compute_item(&identity.category); - bytes.append(&mut compute_item(&identity.type_)); - bytes.append(&mut compute_item( - &identity.lang.clone().unwrap_or_default(), - )); - bytes.append(&mut compute_item( - &identity.name.clone().unwrap_or_default(), - )); - bytes.push(0x1e); - bytes - }) -} - -fn compute_extensions(extensions: &[DataForm]) -> Result, Error> { - for extension in extensions { - if extension.form_type.is_none() { - return Err(Error::ParseError("Missing FORM_TYPE in extension.")); - } - } - Ok(compute_items(extensions, 0x1c, |extension| { - let mut bytes = compute_item("FORM_TYPE"); - bytes.append(&mut compute_item( - if let Some(ref form_type) = extension.form_type { - form_type - } else { - unreachable!() - }, - )); - bytes.push(0x1e); - bytes.append(&mut compute_items(&extension.fields, 0x1d, |field| { - let mut bytes = compute_item(&field.var); - bytes.append(&mut compute_items(&field.values, 0x1e, |value| { - compute_item(value) - })); - bytes - })); - bytes - })) -} - -/// Applies the [algorithm from -/// XEP-0390](https://xmpp.org/extensions/xep-0390.html#algorithm-input) on a -/// [disco#info query element](../disco/struct.DiscoInfoResult.html). -pub fn compute_disco(disco: &DiscoInfoResult) -> Result, Error> { - let features_string = compute_features(&disco.features); - let identities_string = compute_identities(&disco.identities); - let extensions_string = compute_extensions(&disco.extensions)?; - - let mut final_string = vec![]; - final_string.extend(features_string); - final_string.extend(identities_string); - final_string.extend(extensions_string); - Ok(final_string) -} - -fn get_hash_vec(hash: &[u8]) -> Vec { - let mut vec = Vec::with_capacity(hash.len()); - vec.extend_from_slice(hash); - vec -} - -/// Hashes the result of [compute_disco()] with one of the supported [hash -/// algorithms](../hashes/enum.Algo.html). -pub fn hash_ecaps2(data: &[u8], algo: Algo) -> Result { - Ok(Hash { - hash: match algo { - Algo::Sha_256 => { - let hash = Sha256::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Sha_512 => { - let hash = Sha512::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Sha3_256 => { - let hash = Sha3_256::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Sha3_512 => { - let hash = Sha3_512::digest(data); - get_hash_vec(hash.as_slice()) - } - Algo::Blake2b_256 => { - let mut hasher = VarBlake2b::new(32).unwrap(); - hasher.update(data); - let mut vec = Vec::with_capacity(32); - hasher.finalize_variable(|slice| vec.extend_from_slice(slice)); - vec - } - Algo::Blake2b_512 => { - let mut hasher = VarBlake2b::new(64).unwrap(); - hasher.update(data); - let mut vec = Vec::with_capacity(64); - hasher.finalize_variable(|slice| vec.extend_from_slice(slice)); - vec - } - Algo::Sha_1 => return Err(Error::ParseError("Disabled algorithm sha-1: unsafe.")), - Algo::Unknown(_algo) => return Err(Error::ParseError("Unknown algorithm in ecaps2.")), - }, - algo, - }) -} - -/// Helper function to create the query for the disco#info corresponding to an -/// ecaps2 hash. -pub fn query_ecaps2(hash: Hash) -> DiscoInfoQuery { - DiscoInfoQuery { - node: Some(format!( - "{}#{}.{}", - ns::ECAPS2, - String::from(hash.algo), - base64::encode(&hash.hash) - )), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(ECaps2, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(ECaps2, 24); - } - - #[test] - fn test_parse() { - let elem: Element = "K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=+sDTQqBmX6iG/X3zjt06fjZMBBqL/723knFIyRf0sg8=".parse().unwrap(); - let ecaps2 = ECaps2::try_from(elem).unwrap(); - assert_eq!(ecaps2.hashes.len(), 2); - assert_eq!(ecaps2.hashes[0].algo, Algo::Sha_256); - assert_eq!( - ecaps2.hashes[0].hash, - base64::decode("K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=").unwrap() - ); - assert_eq!(ecaps2.hashes[1].algo, Algo::Sha3_256); - assert_eq!( - ecaps2.hashes[1].hash, - base64::decode("+sDTQqBmX6iG/X3zjt06fjZMBBqL/723knFIyRf0sg8=").unwrap() - ); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=+sDTQqBmX6iG/X3zjt06fjZMBBqL/723knFIyRf0sg8=".parse().unwrap(); - let error = ECaps2::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in c element."); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - let disco = DiscoInfoResult::try_from(elem).unwrap(); - let ecaps2 = compute_disco(&disco).unwrap(); - assert_eq!(ecaps2.len(), 54); - } - - #[test] - fn test_xep_ex1() { - let elem: Element = r#" - - - - - - - - - - - - - - - - - - - - -"# - .parse() - .unwrap(); - let expected = vec![ - 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, - 114, 111, 116, 111, 99, 111, 108, 47, 98, 121, 116, 101, 115, 116, 114, 101, 97, 109, - 115, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, - 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 99, 104, 97, 116, 115, 116, 97, 116, - 101, 115, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, - 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 100, 105, 115, 99, 111, 35, 105, - 110, 102, 111, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, - 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 100, 105, 115, 99, 111, 35, - 105, 116, 101, 109, 115, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, - 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 105, 98, 98, 31, 104, - 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, - 111, 116, 111, 99, 111, 108, 47, 114, 111, 115, 116, 101, 114, 120, 31, 104, 116, 116, - 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, - 111, 99, 111, 108, 47, 115, 105, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, - 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 115, 105, - 47, 112, 114, 111, 102, 105, 108, 101, 47, 102, 105, 108, 101, 45, 116, 114, 97, 110, - 115, 102, 101, 114, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 108, 97, 115, 116, - 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 112, 114, 105, 118, 97, 99, 121, 31, - 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 114, 111, 115, 116, 101, 114, 31, 106, 97, - 98, 98, 101, 114, 58, 105, 113, 58, 116, 105, 109, 101, 31, 106, 97, 98, 98, 101, 114, - 58, 105, 113, 58, 118, 101, 114, 115, 105, 111, 110, 31, 106, 97, 98, 98, 101, 114, 58, - 120, 58, 111, 111, 98, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, 112, 105, 110, - 103, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, 114, 101, 99, 101, 105, 112, 116, - 115, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, 116, 105, 109, 101, 31, 28, 99, - 108, 105, 101, 110, 116, 31, 109, 111, 98, 105, 108, 101, 31, 31, 66, 111, 109, 98, - 117, 115, 77, 111, 100, 31, 30, 28, 28, - ]; - let disco = DiscoInfoResult::try_from(elem).unwrap(); - let ecaps2 = compute_disco(&disco).unwrap(); - assert_eq!(ecaps2.len(), 0x1d9); - assert_eq!(ecaps2, expected); - - let sha_256 = hash_ecaps2(&ecaps2, Algo::Sha_256).unwrap(); - assert_eq!( - sha_256.hash, - base64::decode("kzBZbkqJ3ADrj7v08reD1qcWUwNGHaidNUgD7nHpiw8=").unwrap() - ); - let sha3_256 = hash_ecaps2(&ecaps2, Algo::Sha3_256).unwrap(); - assert_eq!( - sha3_256.hash, - base64::decode("79mdYAfU9rEdTOcWDO7UEAt6E56SUzk/g6TnqUeuD9Q=").unwrap() - ); - } - - #[test] - fn test_xep_ex2() { - let elem: Element = r#" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - urn:xmpp:dataforms:softwareinfo - - - Tkabber - - - 0.11.1-svn-20111216-mod (Tcl/Tk 8.6b2) - - - Windows - - - XP - - - -"# - .parse() - .unwrap(); - let expected = vec![ - 103, 97, 109, 101, 115, 58, 98, 111, 97, 114, 100, 31, 104, 116, 116, 112, 58, 47, 47, - 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, - 108, 47, 97, 99, 116, 105, 118, 105, 116, 121, 31, 104, 116, 116, 112, 58, 47, 47, 106, - 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, - 97, 99, 116, 105, 118, 105, 116, 121, 43, 110, 111, 116, 105, 102, 121, 31, 104, 116, - 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, - 116, 111, 99, 111, 108, 47, 98, 121, 116, 101, 115, 116, 114, 101, 97, 109, 115, 31, - 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, - 114, 111, 116, 111, 99, 111, 108, 47, 99, 104, 97, 116, 115, 116, 97, 116, 101, 115, - 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, - 112, 114, 111, 116, 111, 99, 111, 108, 47, 99, 111, 109, 109, 97, 110, 100, 115, 31, - 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, - 114, 111, 116, 111, 99, 111, 108, 47, 100, 105, 115, 99, 111, 35, 105, 110, 102, 111, - 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, - 112, 114, 111, 116, 111, 99, 111, 108, 47, 100, 105, 115, 99, 111, 35, 105, 116, 101, - 109, 115, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, - 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 101, 118, 105, 108, 31, 104, 116, - 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, - 116, 111, 99, 111, 108, 47, 102, 101, 97, 116, 117, 114, 101, 45, 110, 101, 103, 31, - 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, - 114, 111, 116, 111, 99, 111, 108, 47, 103, 101, 111, 108, 111, 99, 31, 104, 116, 116, - 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, - 111, 99, 111, 108, 47, 103, 101, 111, 108, 111, 99, 43, 110, 111, 116, 105, 102, 121, - 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, - 112, 114, 111, 116, 111, 99, 111, 108, 47, 105, 98, 98, 31, 104, 116, 116, 112, 58, 47, - 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, - 108, 47, 105, 113, 105, 98, 98, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, - 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 109, 111, - 111, 100, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, - 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 109, 111, 111, 100, 43, 110, 111, - 116, 105, 102, 121, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, - 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 114, 111, 115, 116, 101, - 114, 120, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, - 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 115, 105, 31, 104, 116, 116, 112, - 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, - 99, 111, 108, 47, 115, 105, 47, 112, 114, 111, 102, 105, 108, 101, 47, 102, 105, 108, - 101, 45, 116, 114, 97, 110, 115, 102, 101, 114, 31, 104, 116, 116, 112, 58, 47, 47, - 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, - 108, 47, 116, 117, 110, 101, 31, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, - 102, 97, 99, 101, 98, 111, 111, 107, 46, 99, 111, 109, 47, 120, 109, 112, 112, 47, 109, - 101, 115, 115, 97, 103, 101, 115, 31, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, - 46, 120, 109, 112, 112, 46, 111, 114, 103, 47, 101, 120, 116, 101, 110, 115, 105, 111, - 110, 115, 47, 120, 101, 112, 45, 48, 48, 56, 52, 46, 104, 116, 109, 108, 35, 110, 115, - 45, 109, 101, 116, 97, 100, 97, 116, 97, 43, 110, 111, 116, 105, 102, 121, 31, 106, 97, - 98, 98, 101, 114, 58, 105, 113, 58, 97, 118, 97, 116, 97, 114, 31, 106, 97, 98, 98, - 101, 114, 58, 105, 113, 58, 98, 114, 111, 119, 115, 101, 31, 106, 97, 98, 98, 101, 114, - 58, 105, 113, 58, 100, 116, 99, 112, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, - 102, 105, 108, 101, 120, 102, 101, 114, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, - 58, 105, 98, 98, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 105, 110, 98, 97, - 110, 100, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 106, 105, 100, 108, 105, - 110, 107, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 108, 97, 115, 116, 31, 106, - 97, 98, 98, 101, 114, 58, 105, 113, 58, 111, 111, 98, 31, 106, 97, 98, 98, 101, 114, - 58, 105, 113, 58, 112, 114, 105, 118, 97, 99, 121, 31, 106, 97, 98, 98, 101, 114, 58, - 105, 113, 58, 114, 111, 115, 116, 101, 114, 31, 106, 97, 98, 98, 101, 114, 58, 105, - 113, 58, 116, 105, 109, 101, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 118, 101, - 114, 115, 105, 111, 110, 31, 106, 97, 98, 98, 101, 114, 58, 120, 58, 100, 97, 116, 97, - 31, 106, 97, 98, 98, 101, 114, 58, 120, 58, 101, 118, 101, 110, 116, 31, 106, 97, 98, - 98, 101, 114, 58, 120, 58, 111, 111, 98, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, - 97, 118, 97, 116, 97, 114, 58, 109, 101, 116, 97, 100, 97, 116, 97, 43, 110, 111, 116, - 105, 102, 121, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, 112, 105, 110, 103, 31, - 117, 114, 110, 58, 120, 109, 112, 112, 58, 114, 101, 99, 101, 105, 112, 116, 115, 31, - 117, 114, 110, 58, 120, 109, 112, 112, 58, 116, 105, 109, 101, 31, 28, 99, 108, 105, - 101, 110, 116, 31, 112, 99, 31, 101, 110, 31, 84, 107, 97, 98, 98, 101, 114, 31, 30, - 99, 108, 105, 101, 110, 116, 31, 112, 99, 31, 114, 117, 31, 208, 162, 208, 186, 208, - 176, 208, 177, 208, 177, 208, 181, 209, 128, 31, 30, 28, 70, 79, 82, 77, 95, 84, 89, - 80, 69, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, 100, 97, 116, 97, 102, 111, 114, - 109, 115, 58, 115, 111, 102, 116, 119, 97, 114, 101, 105, 110, 102, 111, 31, 30, 111, - 115, 31, 87, 105, 110, 100, 111, 119, 115, 31, 30, 111, 115, 95, 118, 101, 114, 115, - 105, 111, 110, 31, 88, 80, 31, 30, 115, 111, 102, 116, 119, 97, 114, 101, 31, 84, 107, - 97, 98, 98, 101, 114, 31, 30, 115, 111, 102, 116, 119, 97, 114, 101, 95, 118, 101, 114, - 115, 105, 111, 110, 31, 48, 46, 49, 49, 46, 49, 45, 115, 118, 110, 45, 50, 48, 49, 49, - 49, 50, 49, 54, 45, 109, 111, 100, 32, 40, 84, 99, 108, 47, 84, 107, 32, 56, 46, 54, - 98, 50, 41, 31, 30, 29, 28, - ]; - let disco = DiscoInfoResult::try_from(elem).unwrap(); - let ecaps2 = compute_disco(&disco).unwrap(); - assert_eq!(ecaps2.len(), 0x543); - assert_eq!(ecaps2, expected); - - let sha_256 = hash_ecaps2(&ecaps2, Algo::Sha_256).unwrap(); - assert_eq!( - sha_256.hash, - base64::decode("u79ZroNJbdSWhdSp311mddz44oHHPsEBntQ5b1jqBSY=").unwrap() - ); - let sha3_256 = hash_ecaps2(&ecaps2, Algo::Sha3_256).unwrap(); - assert_eq!( - sha3_256.hash, - base64::decode("XpUJzLAc93258sMECZ3FJpebkzuyNXDzRNwQog8eycg=").unwrap() - ); - } - - #[test] - fn test_blake2b_512() { - let hash = hash_ecaps2("abc".as_bytes(), Algo::Blake2b_512).unwrap(); - let known_hash: Vec = vec![ - 0xBA, 0x80, 0xA5, 0x3F, 0x98, 0x1C, 0x4D, 0x0D, 0x6A, 0x27, 0x97, 0xB6, 0x9F, 0x12, - 0xF6, 0xE9, 0x4C, 0x21, 0x2F, 0x14, 0x68, 0x5A, 0xC4, 0xB7, 0x4B, 0x12, 0xBB, 0x6F, - 0xDB, 0xFF, 0xA2, 0xD1, 0x7D, 0x87, 0xC5, 0x39, 0x2A, 0xAB, 0x79, 0x2D, 0xC2, 0x52, - 0xD5, 0xDE, 0x45, 0x33, 0xCC, 0x95, 0x18, 0xD3, 0x8A, 0xA8, 0xDB, 0xF1, 0x92, 0x5A, - 0xB9, 0x23, 0x86, 0xED, 0xD4, 0x00, 0x99, 0x23, - ]; - assert_eq!(hash.hash, known_hash); - } -} diff --git a/xmpp-parsers/src/eme.rs b/xmpp-parsers/src/eme.rs deleted file mode 100644 index 1014318..0000000 --- a/xmpp-parsers/src/eme.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; - -generate_element!( - /// Structure representing an `` element. - ExplicitMessageEncryption, "encryption", EME, - attributes: [ - /// Namespace of the encryption scheme used. - namespace: Required = "namespace", - - /// User-friendly name for the encryption scheme, should be `None` for OTR, - /// legacy OpenPGP and OX. - name: Option = "name", - ] -); - -impl MessagePayload for ExplicitMessageEncryption {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(ExplicitMessageEncryption, 24); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(ExplicitMessageEncryption, 48); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let encryption = ExplicitMessageEncryption::try_from(elem).unwrap(); - assert_eq!(encryption.namespace, "urn:xmpp:otr:0"); - assert_eq!(encryption.name, None); - - let elem: Element = "".parse().unwrap(); - let encryption = ExplicitMessageEncryption::try_from(elem).unwrap(); - assert_eq!(encryption.namespace, "some.unknown.mechanism"); - assert_eq!(encryption.name, Some(String::from("SuperMechanism"))); - } - - #[test] - fn test_unknown() { - let elem: Element = "" - .parse() - .unwrap(); - let error = ExplicitMessageEncryption::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "This is not a encryption element."); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = ExplicitMessageEncryption::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in encryption element."); - } - - #[test] - fn test_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let eme = ExplicitMessageEncryption { - namespace: String::from("coucou"), - name: None, - }; - let elem2 = eme.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/forwarding.rs b/xmpp-parsers/src/forwarding.rs deleted file mode 100644 index c59e9d1..0000000 --- a/xmpp-parsers/src/forwarding.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::delay::Delay; -use crate::message::Message; - -generate_element!( - /// Contains a forwarded stanza, either standalone or part of another - /// extension (such as carbons). - Forwarded, "forwarded", FORWARD, - children: [ - /// When the stanza originally got sent. - delay: Option = ("delay", DELAY) => Delay, - - // XXX: really? Option? - /// The stanza being forwarded. - stanza: Option = ("message", DEFAULT_NS) => Message - - // TODO: also handle the two other stanza possibilities. - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Forwarded, 212); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Forwarded, 408); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - Forwarded::try_from(elem).unwrap(); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Forwarded::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in forwarded element."); - } - - #[test] - fn test_serialise() { - let elem: Element = "".parse().unwrap(); - let forwarded = Forwarded { - delay: None, - stanza: None, - }; - let elem2 = forwarded.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_serialize_with_delay_and_stanza() { - let reference: Element = "" - .parse() - .unwrap(); - - let elem: Element = "" - .parse() - .unwrap(); - let message = Message::try_from(elem).unwrap(); - - let elem: Element = - "" - .parse() - .unwrap(); - let delay = Delay::try_from(elem).unwrap(); - - let forwarded = Forwarded { - delay: Some(delay), - stanza: Some(message), - }; - - let serialized: Element = forwarded.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/hashes.rs b/xmpp-parsers/src/hashes.rs deleted file mode 100644 index 704af52..0000000 --- a/xmpp-parsers/src/hashes.rs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::util::error::Error; -use crate::util::helpers::Base64; -use minidom::IntoAttributeValue; -use std::num::ParseIntError; -use std::ops::{Deref, DerefMut}; -use std::str::FromStr; - -/// List of the algorithms we support, or Unknown. -#[allow(non_camel_case_types)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Algo { - /// The Secure Hash Algorithm 1, with known vulnerabilities, do not use it. - /// - /// See https://tools.ietf.org/html/rfc3174 - Sha_1, - - /// The Secure Hash Algorithm 2, in its 256-bit version. - /// - /// See https://tools.ietf.org/html/rfc6234 - Sha_256, - - /// The Secure Hash Algorithm 2, in its 512-bit version. - /// - /// See https://tools.ietf.org/html/rfc6234 - Sha_512, - - /// The Secure Hash Algorithm 3, based on Keccak, in its 256-bit version. - /// - /// See https://keccak.team/files/Keccak-submission-3.pdf - Sha3_256, - - /// The Secure Hash Algorithm 3, based on Keccak, in its 512-bit version. - /// - /// See https://keccak.team/files/Keccak-submission-3.pdf - Sha3_512, - - /// The BLAKE2 hash algorithm, for a 256-bit output. - /// - /// See https://tools.ietf.org/html/rfc7693 - Blake2b_256, - - /// The BLAKE2 hash algorithm, for a 512-bit output. - /// - /// See https://tools.ietf.org/html/rfc7693 - Blake2b_512, - - /// An unknown hash not in this list, you can probably reject it. - Unknown(String), -} - -impl FromStr for Algo { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "" => return Err(Error::ParseError("'algo' argument can’t be empty.")), - - "sha-1" => Algo::Sha_1, - "sha-256" => Algo::Sha_256, - "sha-512" => Algo::Sha_512, - "sha3-256" => Algo::Sha3_256, - "sha3-512" => Algo::Sha3_512, - "blake2b-256" => Algo::Blake2b_256, - "blake2b-512" => Algo::Blake2b_512, - value => Algo::Unknown(value.to_owned()), - }) - } -} - -impl From for String { - fn from(algo: Algo) -> String { - String::from(match algo { - Algo::Sha_1 => "sha-1", - Algo::Sha_256 => "sha-256", - Algo::Sha_512 => "sha-512", - Algo::Sha3_256 => "sha3-256", - Algo::Sha3_512 => "sha3-512", - Algo::Blake2b_256 => "blake2b-256", - Algo::Blake2b_512 => "blake2b-512", - Algo::Unknown(text) => return text, - }) - } -} - -impl IntoAttributeValue for Algo { - fn into_attribute_value(self) -> Option { - Some(String::from(self)) - } -} - -generate_element!( - /// This element represents a hash of some data, defined by the hash - /// algorithm used and the computed value. - Hash, "hash", HASHES, - attributes: [ - /// The algorithm used to create this hash. - algo: Required = "algo" - ], - text: ( - /// The hash value, as a vector of bytes. - hash: Base64> - ) -); - -impl Hash { - /// Creates a [Hash] element with the given algo and data. - pub fn new(algo: Algo, hash: Vec) -> Hash { - Hash { algo, hash } - } - - /// Like [new](#method.new) but takes base64-encoded data before decoding - /// it. - pub fn from_base64(algo: Algo, hash: &str) -> Result { - Ok(Hash::new(algo, base64::decode(hash)?)) - } - - /// Like [new](#method.new) but takes hex-encoded data before decoding it. - pub fn from_hex(algo: Algo, hex: &str) -> Result { - let mut bytes = vec![]; - for i in 0..hex.len() / 2 { - let byte = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16)?; - bytes.push(byte); - } - Ok(Hash::new(algo, bytes)) - } - - /// Like [new](#method.new) but takes hex-encoded data before decoding it. - pub fn from_colon_separated_hex(algo: Algo, hex: &str) -> Result { - let mut bytes = vec![]; - for i in 0..(1 + hex.len()) / 3 { - let byte = u8::from_str_radix(&hex[3 * i..3 * i + 2], 16)?; - if 3 * i + 2 < hex.len() { - assert_eq!(&hex[3 * i + 2..3 * i + 3], ":"); - } - bytes.push(byte); - } - Ok(Hash::new(algo, bytes)) - } - - /// Formats this hash into base64. - pub fn to_base64(&self) -> String { - base64::encode(&self.hash[..]) - } - - /// Formats this hash into hexadecimal. - pub fn to_hex(&self) -> String { - self.hash - .iter() - .map(|byte| format!("{:02x}", byte)) - .collect::>() - .join("") - } - - /// Formats this hash into colon-separated hexadecimal. - pub fn to_colon_separated_hex(&self) -> String { - self.hash - .iter() - .map(|byte| format!("{:02x}", byte)) - .collect::>() - .join(":") - } -} - -/// Helper for parsing and serialising a SHA-1 attribute. -#[derive(Debug, Clone, PartialEq)] -pub struct Sha1HexAttribute(Hash); - -impl FromStr for Sha1HexAttribute { - type Err = ParseIntError; - - fn from_str(hex: &str) -> Result { - let hash = Hash::from_hex(Algo::Sha_1, hex)?; - Ok(Sha1HexAttribute(hash)) - } -} - -impl IntoAttributeValue for Sha1HexAttribute { - fn into_attribute_value(self) -> Option { - Some(self.to_hex()) - } -} - -impl DerefMut for Sha1HexAttribute { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Deref for Sha1HexAttribute { - type Target = Hash; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Algo, 16); - assert_size!(Hash, 28); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Algo, 32); - assert_size!(Hash, 56); - } - - #[test] - fn test_simple() { - let elem: Element = "2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=".parse().unwrap(); - let hash = Hash::try_from(elem).unwrap(); - assert_eq!(hash.algo, Algo::Sha_256); - assert_eq!( - hash.hash, - base64::decode("2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=").unwrap() - ); - } - - #[test] - fn value_serialisation() { - let elem: Element = "2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=".parse().unwrap(); - let hash = Hash::try_from(elem).unwrap(); - assert_eq!( - hash.to_base64(), - "2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=" - ); - assert_eq!( - hash.to_hex(), - "d976ab9b04e53710c0324bf29a5a17dd2e7e55bca536b26dfe5e50c8f6be6285" - ); - assert_eq!(hash.to_colon_separated_hex(), "d9:76:ab:9b:04:e5:37:10:c0:32:4b:f2:9a:5a:17:dd:2e:7e:55:bc:a5:36:b2:6d:fe:5e:50:c8:f6:be:62:85"); - } - - #[test] - fn test_unknown() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Hash::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "This is not a hash element."); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Hash::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in hash element."); - } -} diff --git a/xmpp-parsers/src/ibb.rs b/xmpp-parsers/src/ibb.rs deleted file mode 100644 index 3e4ab57..0000000 --- a/xmpp-parsers/src/ibb.rs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::IqSetPayload; -use crate::util::helpers::Base64; - -generate_id!( - /// An identifier matching a stream. - StreamId -); - -generate_attribute!( -/// Which stanza type to use to exchange data. -Stanza, "stanza", { - /// `` gives a feedback on whether the chunk has been received or not, - /// which is useful in the case the recipient might not receive them in a - /// timely manner, or to do your own throttling based on the results. - Iq => "iq", - - /// `` can be faster, since it doesn’t require any feedback, but in - /// practice it will be throttled by the servers on the way. - Message => "message", -}, Default = Iq); - -generate_element!( -/// Starts an In-Band Bytestream session with the given parameters. -Open, "open", IBB, -attributes: [ - /// Maximum size in bytes for each chunk. - block_size: Required = "block-size", - - /// The identifier to be used to create a stream. - sid: Required = "sid", - - /// Which stanza type to use to exchange data. - stanza: Default = "stanza", -]); - -impl IqSetPayload for Open {} - -generate_element!( -/// Exchange a chunk of data in an open stream. -Data, "data", IBB, - attributes: [ - /// Sequence number of this chunk, must wraparound after 65535. - seq: Required = "seq", - - /// The identifier of the stream on which data is being exchanged. - sid: Required = "sid" - ], - text: ( - /// Vector of bytes to be exchanged. - data: Base64> - ) -); - -impl IqSetPayload for Data {} - -generate_element!( -/// Close an open stream. -Close, "close", IBB, -attributes: [ - /// The identifier of the stream to be closed. - sid: Required = "sid", -]); - -impl IqSetPayload for Close {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(StreamId, 12); - assert_size!(Stanza, 1); - assert_size!(Open, 16); - assert_size!(Data, 28); - assert_size!(Close, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(StreamId, 24); - assert_size!(Stanza, 1); - assert_size!(Open, 32); - assert_size!(Data, 56); - assert_size!(Close, 24); - } - - #[test] - fn test_simple() { - let sid = StreamId(String::from("coucou")); - - let elem: Element = - "" - .parse() - .unwrap(); - let open = Open::try_from(elem).unwrap(); - assert_eq!(open.block_size, 3); - assert_eq!(open.sid, sid); - assert_eq!(open.stanza, Stanza::Iq); - - let elem: Element = - "AAAA" - .parse() - .unwrap(); - let data = Data::try_from(elem).unwrap(); - assert_eq!(data.seq, 0); - assert_eq!(data.sid, sid); - assert_eq!(data.data, vec!(0, 0, 0)); - - let elem: Element = "" - .parse() - .unwrap(); - let close = Close::try_from(elem).unwrap(); - assert_eq!(close.sid, sid); - } - - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Open::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'block-size' missing."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Open::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(message.to_string(), "invalid digit found in string"); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Open::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(error) => error, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'sid' missing."); - } - - #[test] - fn test_invalid_stanza() { - let elem: Element = "".parse().unwrap(); - let error = Open::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'stanza' attribute."); - } -} diff --git a/xmpp-parsers/src/ibr.rs b/xmpp-parsers/src/ibr.rs deleted file mode 100644 index b0a2f85..0000000 --- a/xmpp-parsers/src/ibr.rs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::DataForm; -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use std::collections::HashMap; -use std::convert::TryFrom; - -/// Query for registering against a service. -#[derive(Debug, Clone)] -pub struct Query { - /// Deprecated fixed list of possible fields to fill before the user can - /// register. - pub fields: HashMap, - - /// Whether this account is already registered. - pub registered: bool, - - /// Whether to remove this account. - pub remove: bool, - - /// A data form the user must fill before being allowed to register. - pub form: Option, - // Not yet implemented. - //pub oob: Option, -} - -impl IqGetPayload for Query {} -impl IqSetPayload for Query {} -impl IqResultPayload for Query {} - -impl TryFrom for Query { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "query", REGISTER, "IBR query"); - let mut query = Query { - registered: false, - fields: HashMap::new(), - remove: false, - form: None, - }; - for child in elem.children() { - let namespace = child.ns(); - if namespace == ns::REGISTER { - let name = child.name(); - let fields = vec![ - "address", - "city", - "date", - "email", - "first", - "instructions", - "key", - "last", - "misc", - "name", - "nick", - "password", - "phone", - "state", - "text", - "url", - "username", - "zip", - ]; - if fields.binary_search(&name).is_ok() { - query.fields.insert(name.to_owned(), child.text()); - } else if name == "registered" { - query.registered = true; - } else if name == "remove" { - query.remove = true; - } else { - return Err(Error::ParseError("Wrong field in ibr element.")); - } - } else if child.is("x", ns::DATA_FORMS) { - query.form = Some(DataForm::try_from(child.clone())?); - } else { - return Err(Error::ParseError("Unknown child in ibr element.")); - } - } - Ok(query) - } -} - -impl From for Element { - fn from(query: Query) -> Element { - Element::builder("query", ns::REGISTER) - .append_all(if query.registered { - Some(Element::builder("registered", ns::REGISTER)) - } else { - None - }) - .append_all( - query - .fields - .into_iter() - .map(|(name, value)| Element::builder(name, ns::REGISTER).append(value)), - ) - .append_all(if query.remove { - Some(Element::builder("remove", ns::REGISTER)) - } else { - None - }) - .append_all(query.form.map(Element::from)) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Query, 88); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Query, 160); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - Query::try_from(elem).unwrap(); - } - - #[test] - fn test_ex2() { - let elem: Element = r#" - - - Choose a username and password for use with this service. - Please also provide your email address. - - - - - -"# - .parse() - .unwrap(); - let query = Query::try_from(elem).unwrap(); - assert_eq!(query.registered, false); - assert_eq!(query.fields["instructions"], "\n Choose a username and password for use with this service.\n Please also provide your email address.\n "); - assert_eq!(query.fields["username"], ""); - assert_eq!(query.fields["password"], ""); - assert_eq!(query.fields["email"], ""); - assert_eq!(query.fields.contains_key("name"), false); - - // FIXME: HashMap doesn’t keep the order right. - //let elem2 = query.into(); - //assert_eq!(elem, elem2); - } - - #[test] - fn test_ex9() { - let elem: Element = "Use the enclosed form to register. If your Jabber client does not support Data Forms, visit http://www.shakespeare.lit/contests.phpContest RegistrationPlease provide the following information to sign up for our special contests!jabber:iq:register" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let query = Query::try_from(elem).unwrap(); - assert_eq!(query.registered, false); - assert!(!query.fields["instructions"].is_empty()); - let form = query.form.clone().unwrap(); - assert!(!form.instructions.unwrap().is_empty()); - let elem2 = query.into(); - assert_eq!(elem1, elem2); - } - - #[test] - fn test_ex10() { - let elem: Element = "jabber:iq:registerJulietCapuletjuliet@capulet.comF" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let query = Query::try_from(elem).unwrap(); - assert_eq!(query.registered, false); - for _ in &query.fields { - panic!(); - } - let elem2 = query.into(); - assert_eq!(elem1, elem2); - } -} diff --git a/xmpp-parsers/src/idle.rs b/xmpp-parsers/src/idle.rs deleted file mode 100644 index a575b32..0000000 --- a/xmpp-parsers/src/idle.rs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::date::DateTime; -use crate::presence::PresencePayload; - -generate_element!( - /// Represents the last time the user interacted with their system. - Idle, "idle", IDLE, - attributes: [ - /// The time at which the user stopped interacting. - since: Required = "since", - ] -); - -impl PresencePayload for Idle {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - use std::str::FromStr; - - #[test] - fn test_size() { - assert_size!(Idle, 16); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - Idle::try_from(elem).unwrap(); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in idle element."); - } - - #[test] - fn test_invalid_id() { - let elem: Element = "".parse().unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'since' missing."); - } - - #[test] - fn test_invalid_date() { - // There is no thirteenth month. - let elem: Element = "" - .parse() - .unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input is out of range"); - - // Timezone ≥24:00 aren’t allowed. - let elem: Element = "" - .parse() - .unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input is out of range"); - - // Timezone without the : separator aren’t allowed. - let elem: Element = "" - .parse() - .unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input contains invalid characters"); - - // No seconds, error message could be improved. - let elem: Element = "" - .parse() - .unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input contains invalid characters"); - - // TODO: maybe we’ll want to support this one, as per XEP-0082 §4. - let elem: Element = "" - .parse() - .unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "input contains invalid characters"); - - // No timezone. - let elem: Element = "" - .parse() - .unwrap(); - let error = Idle::try_from(elem).unwrap_err(); - let message = match error { - Error::ChronoParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message.to_string(), "premature end of input"); - } - - #[test] - fn test_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let idle = Idle { - since: DateTime::from_str("2017-05-21T20:19:55+01:00").unwrap(), - }; - let elem2 = idle.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/iq.rs b/xmpp-parsers/src/iq.rs deleted file mode 100644 index b9c022f..0000000 --- a/xmpp-parsers/src/iq.rs +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// Copyright (c) 2017 Maxime “pep” Buquet -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::stanza_error::StanzaError; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use minidom::IntoAttributeValue; -use std::convert::TryFrom; - -/// Should be implemented on every known payload of an ``. -pub trait IqGetPayload: TryFrom + Into {} - -/// Should be implemented on every known payload of an ``. -pub trait IqSetPayload: TryFrom + Into {} - -/// Should be implemented on every known payload of an ``. -pub trait IqResultPayload: TryFrom + Into {} - -/// Represents one of the four possible iq types. -#[derive(Debug, Clone)] -pub enum IqType { - /// This is a request for accessing some data. - Get(Element), - - /// This is a request for modifying some data. - Set(Element), - - /// This is a result containing some data. - Result(Option), - - /// A get or set request failed. - Error(StanzaError), -} - -impl<'a> IntoAttributeValue for &'a IqType { - fn into_attribute_value(self) -> Option { - Some( - match *self { - IqType::Get(_) => "get", - IqType::Set(_) => "set", - IqType::Result(_) => "result", - IqType::Error(_) => "error", - } - .to_owned(), - ) - } -} - -/// The main structure representing the `` stanza. -#[derive(Debug, Clone)] -pub struct Iq { - /// The JID emitting this stanza. - pub from: Option, - - /// The recipient of this stanza. - pub to: Option, - - /// The @id attribute of this stanza, which is required in order to match a - /// request with its result/error. - pub id: String, - - /// The payload content of this stanza. - pub payload: IqType, -} - -impl Iq { - /// Creates an `` stanza containing a get request. - pub fn from_get>(id: S, payload: impl IqGetPayload) -> Iq { - Iq { - from: None, - to: None, - id: id.into(), - payload: IqType::Get(payload.into()), - } - } - - /// Creates an `` stanza containing a set request. - pub fn from_set>(id: S, payload: impl IqSetPayload) -> Iq { - Iq { - from: None, - to: None, - id: id.into(), - payload: IqType::Set(payload.into()), - } - } - - /// Creates an empty `` stanza. - pub fn empty_result>(to: Jid, id: S) -> Iq { - Iq { - from: None, - to: Some(to), - id: id.into(), - payload: IqType::Result(None), - } - } - - /// Creates an `` stanza containing a result. - pub fn from_result>(id: S, payload: Option) -> Iq { - Iq { - from: None, - to: None, - id: id.into(), - payload: IqType::Result(payload.map(Into::into)), - } - } - - /// Creates an `` stanza containing an error. - pub fn from_error>(id: S, payload: StanzaError) -> Iq { - Iq { - from: None, - to: None, - id: id.into(), - payload: IqType::Error(payload), - } - } - - /// Sets the recipient of this stanza. - pub fn with_to(mut self, to: Jid) -> Iq { - self.to = Some(to); - self - } - - /// Sets the emitter of this stanza. - pub fn with_from(mut self, from: Jid) -> Iq { - self.from = Some(from); - self - } - - /// Sets the id of this stanza, in order to later match its response. - pub fn with_id(mut self, id: String) -> Iq { - self.id = id; - self - } -} - -impl TryFrom for Iq { - type Error = Error; - - fn try_from(root: Element) -> Result { - check_self!(root, "iq", DEFAULT_NS); - let from = get_attr!(root, "from", Option); - let to = get_attr!(root, "to", Option); - let id = get_attr!(root, "id", Required); - let type_: String = get_attr!(root, "type", Required); - - let mut payload = None; - let mut error_payload = None; - for elem in root.children() { - if payload.is_some() { - return Err(Error::ParseError("Wrong number of children in iq element.")); - } - if type_ == "error" { - if elem.is("error", ns::DEFAULT_NS) { - if error_payload.is_some() { - return Err(Error::ParseError("Wrong number of children in iq element.")); - } - error_payload = Some(StanzaError::try_from(elem.clone())?); - } else if root.children().count() != 2 { - return Err(Error::ParseError("Wrong number of children in iq element.")); - } - } else { - payload = Some(elem.clone()); - } - } - - let type_ = if type_ == "get" { - if let Some(payload) = payload { - IqType::Get(payload) - } else { - return Err(Error::ParseError("Wrong number of children in iq element.")); - } - } else if type_ == "set" { - if let Some(payload) = payload { - IqType::Set(payload) - } else { - return Err(Error::ParseError("Wrong number of children in iq element.")); - } - } else if type_ == "result" { - if let Some(payload) = payload { - IqType::Result(Some(payload)) - } else { - IqType::Result(None) - } - } else if type_ == "error" { - if let Some(payload) = error_payload { - IqType::Error(payload) - } else { - return Err(Error::ParseError("Wrong number of children in iq element.")); - } - } else { - return Err(Error::ParseError("Unknown iq type.")); - }; - - Ok(Iq { - from, - to, - id, - payload: type_, - }) - } -} - -impl From for Element { - fn from(iq: Iq) -> Element { - let mut stanza = Element::builder("iq", ns::DEFAULT_NS) - .attr("from", iq.from) - .attr("to", iq.to) - .attr("id", iq.id) - .attr("type", &iq.payload) - .build(); - let elem = match iq.payload { - IqType::Get(elem) | IqType::Set(elem) | IqType::Result(Some(elem)) => elem, - IqType::Error(error) => error.into(), - IqType::Result(None) => return stanza, - }; - stanza.append_child(elem); - stanza - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::disco::DiscoInfoQuery; - use crate::stanza_error::{DefinedCondition, ErrorType}; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(IqType, 136); - assert_size!(Iq, 228); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(IqType, 272); - assert_size!(Iq, 456); - } - - #[test] - fn test_require_type() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "".parse().unwrap(); - let error = Iq::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'id' missing."); - - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let error = Iq::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'type' missing."); - } - - #[test] - fn test_get() { - #[cfg(not(feature = "component"))] - let elem: Element = " - - " - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = " - - " - .parse() - .unwrap(); - let iq = Iq::try_from(elem).unwrap(); - let query: Element = "".parse().unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "foo"); - assert!(match iq.payload { - IqType::Get(element) => element == query, - _ => false, - }); - } - - #[test] - fn test_set() { - #[cfg(not(feature = "component"))] - let elem: Element = " - - " - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = " - - " - .parse() - .unwrap(); - let iq = Iq::try_from(elem).unwrap(); - let vcard: Element = "".parse().unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "vcard"); - assert!(match iq.payload { - IqType::Set(element) => element == vcard, - _ => false, - }); - } - - #[test] - fn test_result_empty() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let iq = Iq::try_from(elem).unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "res"); - assert!(match iq.payload { - IqType::Result(None) => true, - _ => false, - }); - } - - #[test] - fn test_result() { - #[cfg(not(feature = "component"))] - let elem: Element = " - - " - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = " - - " - .parse() - .unwrap(); - let iq = Iq::try_from(elem).unwrap(); - let query: Element = "" - .parse() - .unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(&iq.id, "res"); - assert!(match iq.payload { - IqType::Result(Some(element)) => element == query, - _ => false, - }); - } - - #[test] - fn test_error() { - #[cfg(not(feature = "component"))] - let elem: Element = " - - - - - " - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = " - - - - - " - .parse() - .unwrap(); - let iq = Iq::try_from(elem).unwrap(); - assert_eq!(iq.from, None); - assert_eq!(iq.to, None); - assert_eq!(iq.id, "err1"); - match iq.payload { - IqType::Error(error) => { - assert_eq!(error.type_, ErrorType::Cancel); - assert_eq!(error.by, None); - assert_eq!( - error.defined_condition, - DefinedCondition::ServiceUnavailable - ); - assert_eq!(error.texts.len(), 0); - assert_eq!(error.other, None); - } - _ => panic!(), - } - } - - #[test] - fn test_children_invalid() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let error = Iq::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Wrong number of children in iq element."); - } - - #[test] - fn test_serialise() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let iq2 = Iq { - from: None, - to: None, - id: String::from("res"), - payload: IqType::Result(None), - }; - let elem2 = iq2.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_disco() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "".parse().unwrap(); - let iq = Iq::try_from(elem).unwrap(); - let disco_info = match iq.payload { - IqType::Get(payload) => DiscoInfoQuery::try_from(payload).unwrap(), - _ => panic!(), - }; - assert!(disco_info.node.is_none()); - } -} diff --git a/xmpp-parsers/src/jid_prep.rs b/xmpp-parsers/src/jid_prep.rs deleted file mode 100644 index a78cc0b..0000000 --- a/xmpp-parsers/src/jid_prep.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::{IqGetPayload, IqResultPayload}; -use crate::util::helpers::{JidCodec, Text}; -use jid::Jid; - -generate_element!( - /// Request from a client to stringprep/PRECIS a string into a JID. - JidPrepQuery, "jid", JID_PREP, - text: ( - /// The potential JID. - data: Text - ) -); - -impl IqGetPayload for JidPrepQuery {} - -impl JidPrepQuery { - /// Create a new JID Prep query. - pub fn new>(jid: J) -> JidPrepQuery { - JidPrepQuery { data: jid.into() } - } -} - -generate_element!( - /// Response from the server with the stringprep’d/PRECIS’d JID. - JidPrepResponse, "jid", JID_PREP, - text: ( - /// The JID. - jid: JidCodec - ) -); - -impl IqResultPayload for JidPrepResponse {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use jid::FullJid; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(JidPrepQuery, 12); - assert_size!(JidPrepResponse, 40); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(JidPrepQuery, 24); - assert_size!(JidPrepResponse, 80); - } - - #[test] - fn simple() { - let elem: Element = "ROMeo@montague.lit/orchard" - .parse() - .unwrap(); - let query = JidPrepQuery::try_from(elem).unwrap(); - assert_eq!(query.data, "ROMeo@montague.lit/orchard"); - - let elem: Element = "romeo@montague.lit/orchard" - .parse() - .unwrap(); - let response = JidPrepResponse::try_from(elem).unwrap(); - assert_eq!( - response.jid, - FullJid::new("romeo", "montague.lit", "orchard") - ); - } -} diff --git a/xmpp-parsers/src/jingle.rs b/xmpp-parsers/src/jingle.rs deleted file mode 100644 index d15f6e8..0000000 --- a/xmpp-parsers/src/jingle.rs +++ /dev/null @@ -1,911 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::IqSetPayload; -use crate::jingle_grouping::Group; -use crate::jingle_ibb::Transport as IbbTransport; -use crate::jingle_ice_udp::Transport as IceUdpTransport; -use crate::jingle_rtp::Description as RtpDescription; -use crate::jingle_s5b::Transport as Socks5Transport; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::collections::BTreeMap; -use std::convert::TryFrom; -use std::fmt; -use std::str::FromStr; - -generate_attribute!( - /// The action attribute. - Action, "action", { - /// Accept a content-add action received from another party. - ContentAccept => "content-accept", - - /// Add one or more new content definitions to the session. - ContentAdd => "content-add", - - /// Change the directionality of media sending. - ContentModify => "content-modify", - - /// Reject a content-add action received from another party. - ContentReject => "content-reject", - - /// Remove one or more content definitions from the session. - ContentRemove => "content-remove", - - /// Exchange information about parameters for an application type. - DescriptionInfo => "description-info", - - /// Exchange information about security preconditions. - SecurityInfo => "security-info", - - /// Definitively accept a session negotiation. - SessionAccept => "session-accept", - - /// Send session-level information, such as a ping or a ringing message. - SessionInfo => "session-info", - - /// Request negotiation of a new Jingle session. - SessionInitiate => "session-initiate", - - /// End an existing session. - SessionTerminate => "session-terminate", - - /// Accept a transport-replace action received from another party. - TransportAccept => "transport-accept", - - /// Exchange transport candidates. - TransportInfo => "transport-info", - - /// Reject a transport-replace action received from another party. - TransportReject => "transport-reject", - - /// Redefine a transport method or replace it with a different method. - TransportReplace => "transport-replace", - - /// --- Non-standard messages used by Jitsi Meet: - - /// Add a source to existing content. - SourceAdd => "source-add", - } -); - -generate_attribute!( - /// Which party originally generated the content type. - Creator, "creator", { - /// This content was created by the initiator of this session. - Initiator => "initiator", - - /// This content was created by the responder of this session. - Responder => "responder", - } -); - -generate_attribute!( - /// Which parties in the session will be generating content. - Senders, "senders", { - /// Both parties can send for this content. - Both => "both", - - /// Only the initiator can send for this content. - Initiator => "initiator", - - /// No one can send for this content. - None => "none", - - /// Only the responder can send for this content. - Responder => "responder", - } -); - -generate_attribute!( - /// How the content definition is to be interpreted by the recipient. The - /// meaning of this attribute matches the "Content-Disposition" header as - /// defined in RFC 2183 and applied to SIP by RFC 3261. - /// - /// Possible values are defined here: - /// https://www.iana.org/assignments/cont-disp/cont-disp.xhtml - Disposition, "disposition", { - /// Displayed automatically. - Inline => "inline", - - /// User controlled display. - Attachment => "attachment", - - /// Process as form response. - FormData => "form-data", - - /// Tunneled content to be processed silently. - Signal => "signal", - - /// The body is a custom ring tone to alert the user. - Alert => "alert", - - /// The body is displayed as an icon to the user. - Icon => "icon", - - /// The body should be displayed to the user. - Render => "render", - - /// The body contains a list of URIs that indicates the recipients of - /// the request. - RecipientListHistory => "recipient-list-history", - - /// The body describes a communications session, for example, an - /// RFC2327 SDP body. - Session => "session", - - /// Authenticated Identity Body. - Aib => "aib", - - /// The body describes an early communications session, for example, - /// and [RFC2327] SDP body. - EarlySession => "early-session", - - /// The body includes a list of URIs to which URI-list services are to - /// be applied. - RecipientList => "recipient-list", - - /// The payload of the message carrying this Content-Disposition header - /// field value is an Instant Message Disposition Notification as - /// requested in the corresponding Instant Message. - Notification => "notification", - - /// The body needs to be handled according to a reference to the body - /// that is located in the same SIP message as the body. - ByReference => "by-reference", - - /// The body contains information associated with an Info Package. - InfoPackage => "info-package", - - /// The body describes either metadata about the RS or the reason for - /// the metadata snapshot request as determined by the MIME value - /// indicated in the Content-Type. - RecordingSession => "recording-session", - }, Default = Session -); - -generate_id!( - /// An unique identifier in a session, referencing a - /// [struct.Content.html](Content element). - ContentId -); - -/// Enum wrapping all of the various supported descriptions of a Content. -#[derive(Debug, Clone, PartialEq)] -pub enum Description { - /// Jingle RTP Sessions (XEP-0167) description. - Rtp(RtpDescription), - - /// To be used for any description that isn’t known at compile-time. - Unknown(Element), -} - -impl TryFrom for Description { - type Error = Error; - - fn try_from(elem: Element) -> Result { - Ok(if elem.is("description", ns::JINGLE_RTP) { - Description::Rtp(RtpDescription::try_from(elem)?) - } else { - Description::Unknown(elem) - }) - } -} - -impl From for Description { - fn from(desc: RtpDescription) -> Description { - Description::Rtp(desc) - } -} - -impl From for Element { - fn from(desc: Description) -> Element { - match desc { - Description::Rtp(desc) => desc.into(), - Description::Unknown(elem) => elem, - } - } -} - -/// Enum wrapping all of the various supported transports of a Content. -#[derive(Debug, Clone, PartialEq)] -pub enum Transport { - /// Jingle ICE-UDP Bytestreams (XEP-0176) transport. - IceUdp(IceUdpTransport), - - /// Jingle In-Band Bytestreams (XEP-0261) transport. - Ibb(IbbTransport), - - /// Jingle SOCKS5 Bytestreams (XEP-0260) transport. - Socks5(Socks5Transport), - - /// To be used for any transport that isn’t known at compile-time. - Unknown(Element), -} - -impl TryFrom for Transport { - type Error = Error; - - fn try_from(elem: Element) -> Result { - Ok(if elem.is("transport", ns::JINGLE_ICE_UDP) { - Transport::IceUdp(IceUdpTransport::try_from(elem)?) - } else if elem.is("transport", ns::JINGLE_IBB) { - Transport::Ibb(IbbTransport::try_from(elem)?) - } else if elem.is("transport", ns::JINGLE_S5B) { - Transport::Socks5(Socks5Transport::try_from(elem)?) - } else { - Transport::Unknown(elem) - }) - } -} - -impl From for Transport { - fn from(transport: IceUdpTransport) -> Transport { - Transport::IceUdp(transport) - } -} - -impl From for Transport { - fn from(transport: IbbTransport) -> Transport { - Transport::Ibb(transport) - } -} - -impl From for Transport { - fn from(transport: Socks5Transport) -> Transport { - Transport::Socks5(transport) - } -} - -impl From for Element { - fn from(transport: Transport) -> Element { - match transport { - Transport::IceUdp(transport) => transport.into(), - Transport::Ibb(transport) => transport.into(), - Transport::Socks5(transport) => transport.into(), - Transport::Unknown(elem) => elem, - } - } -} - -generate_element!( - /// Describes a session’s content, there can be multiple content in one - /// session. - Content, "content", JINGLE, - attributes: [ - /// Who created this content. - creator: Option = "creator", - - /// How the content definition is to be interpreted by the recipient. - disposition: Default = "disposition", - - /// A per-session unique identifier for this content. - name: Required = "name", - - /// Who can send data for this content. - senders: Option = "senders", - ], - children: [ - /// What to send. - description: Option = ("description", *) => Description, - - /// How to send it. - transport: Option = ("transport", *) => Transport, - - /// With which security. - security: Option = ("security", JINGLE) => Element - ] -); - -impl Content { - /// Create a new content. - pub fn new(creator: Creator, name: ContentId) -> Content { - Content { - creator: Some(creator), - name, - disposition: Disposition::Session, - senders: Some(Senders::Both), - description: None, - transport: None, - security: None, - } - } - - /// Set how the content is to be interpreted by the recipient. - pub fn with_disposition(mut self, disposition: Disposition) -> Content { - self.disposition = disposition; - self - } - - /// Specify who can send data for this content. - pub fn with_senders(mut self, senders: Senders) -> Content { - self.senders = Some(senders); - self - } - - /// Set the description of this content. - pub fn with_description>(mut self, description: D) -> Content { - self.description = Some(description.into()); - self - } - - /// Set the transport of this content. - pub fn with_transport>(mut self, transport: T) -> Content { - self.transport = Some(transport.into()); - self - } - - /// Set the security of this content. - pub fn with_security(mut self, security: Element) -> Content { - self.security = Some(security); - self - } -} - -/// Lists the possible reasons to be included in a Jingle iq. -#[derive(Debug, Clone, PartialEq)] -pub enum Reason { - /// The party prefers to use an existing session with the peer rather than - /// initiate a new session; the Jingle session ID of the alternative - /// session SHOULD be provided as the XML character data of the - /// child. - AlternativeSession, //(String), - - /// The party is busy and cannot accept a session. - Busy, - - /// The initiator wishes to formally cancel the session initiation request. - Cancel, - - /// The action is related to connectivity problems. - ConnectivityError, - - /// The party wishes to formally decline the session. - Decline, - - /// The session length has exceeded a pre-defined time limit (e.g., a - /// meeting hosted at a conference service). - Expired, - - /// The party has been unable to initialize processing related to the - /// application type. - FailedApplication, - - /// The party has been unable to establish connectivity for the transport - /// method. - FailedTransport, - - /// The action is related to a non-specific application error. - GeneralError, - - /// The entity is going offline or is no longer available. - Gone, - - /// The party supports the offered application type but does not support - /// the offered or negotiated parameters. - IncompatibleParameters, - - /// The action is related to media processing problems. - MediaError, - - /// The action is related to a violation of local security policies. - SecurityError, - - /// The action is generated during the normal course of state management - /// and does not reflect any error. - Success, - - /// A request has not been answered so the sender is timing out the - /// request. - Timeout, - - /// The party supports none of the offered application types. - UnsupportedApplications, - - /// The party supports none of the offered transport methods. - UnsupportedTransports, -} - -impl FromStr for Reason { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "alternative-session" => Reason::AlternativeSession, - "busy" => Reason::Busy, - "cancel" => Reason::Cancel, - "connectivity-error" => Reason::ConnectivityError, - "decline" => Reason::Decline, - "expired" => Reason::Expired, - "failed-application" => Reason::FailedApplication, - "failed-transport" => Reason::FailedTransport, - "general-error" => Reason::GeneralError, - "gone" => Reason::Gone, - "incompatible-parameters" => Reason::IncompatibleParameters, - "media-error" => Reason::MediaError, - "security-error" => Reason::SecurityError, - "success" => Reason::Success, - "timeout" => Reason::Timeout, - "unsupported-applications" => Reason::UnsupportedApplications, - "unsupported-transports" => Reason::UnsupportedTransports, - - _ => return Err(Error::ParseError("Unknown reason.")), - }) - } -} - -impl From for Element { - fn from(reason: Reason) -> Element { - Element::builder( - match reason { - Reason::AlternativeSession => "alternative-session", - Reason::Busy => "busy", - Reason::Cancel => "cancel", - Reason::ConnectivityError => "connectivity-error", - Reason::Decline => "decline", - Reason::Expired => "expired", - Reason::FailedApplication => "failed-application", - Reason::FailedTransport => "failed-transport", - Reason::GeneralError => "general-error", - Reason::Gone => "gone", - Reason::IncompatibleParameters => "incompatible-parameters", - Reason::MediaError => "media-error", - Reason::SecurityError => "security-error", - Reason::Success => "success", - Reason::Timeout => "timeout", - Reason::UnsupportedApplications => "unsupported-applications", - Reason::UnsupportedTransports => "unsupported-transports", - }, - ns::JINGLE, - ) - .build() - } -} - -type Lang = String; - -/// Informs the recipient of something. -#[derive(Debug, Clone, PartialEq)] -pub struct ReasonElement { - /// The list of possible reasons to be included in a Jingle iq. - pub reason: Reason, - - /// A human-readable description of this reason. - pub texts: BTreeMap, -} - -impl fmt::Display for ReasonElement { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{}", Element::from(self.reason.clone()).name())?; - if let Some(text) = self.texts.get("en") { - write!(fmt, ": {}", text)?; - } else if let Some(text) = self.texts.get("") { - write!(fmt, ": {}", text)?; - } - Ok(()) - } -} - -impl TryFrom for ReasonElement { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "reason", JINGLE); - check_no_attributes!(elem, "reason"); - let mut reason = None; - let mut texts = BTreeMap::new(); - for child in elem.children() { - if child.is("text", ns::JINGLE) { - check_no_children!(child, "text"); - check_no_unknown_attributes!(child, "text", ["xml:lang"]); - let lang = get_attr!(elem, "xml:lang", Default); - if texts.insert(lang, child.text()).is_some() { - return Err(Error::ParseError( - "Text element present twice for the same xml:lang.", - )); - } - } else if child.has_ns(ns::JINGLE) { - if reason.is_some() { - return Err(Error::ParseError( - "Reason must not have more than one reason.", - )); - } - check_no_children!(child, "reason"); - check_no_attributes!(child, "reason"); - reason = Some(child.name().parse()?); - } else { - return Err(Error::ParseError("Reason contains a foreign element.")); - } - } - let reason = reason.ok_or(Error::ParseError("Reason doesn’t contain a valid reason."))?; - Ok(ReasonElement { reason, texts }) - } -} - -impl From for Element { - fn from(reason: ReasonElement) -> Element { - Element::builder("reason", ns::JINGLE) - .append(Element::from(reason.reason)) - .append_all(reason.texts.into_iter().map(|(lang, text)| { - Element::builder("text", ns::JINGLE) - .attr("xml:lang", lang) - .append(text) - })) - .build() - } -} - -generate_id!( - /// Unique identifier for a session between two JIDs. - SessionId -); - -/// The main Jingle container, to be included in an iq stanza. -#[derive(Debug, Clone, PartialEq)] -pub struct Jingle { - /// The action to execute on both ends. - pub action: Action, - - /// Who the initiator is. - pub initiator: Option, - - /// Who the responder is. - pub responder: Option, - - /// Unique session identifier between two entities. - pub sid: SessionId, - - /// A list of contents to be negotiated in this session. - pub contents: Vec, - - /// An optional reason. - pub reason: Option, - - /// An optional grouping. - pub group: Option, - - /// Payloads to be included. - pub other: Vec, -} - -impl IqSetPayload for Jingle {} - -impl Jingle { - /// Create a new Jingle element. - pub fn new(action: Action, sid: SessionId) -> Jingle { - Jingle { - action, - sid, - initiator: None, - responder: None, - contents: Vec::new(), - reason: None, - group: None, - other: Vec::new(), - } - } - - /// Set the initiator’s JID. - pub fn with_initiator(mut self, initiator: Jid) -> Jingle { - self.initiator = Some(initiator); - self - } - - /// Set the responder’s JID. - pub fn with_responder(mut self, responder: Jid) -> Jingle { - self.responder = Some(responder); - self - } - - /// Add a content to this Jingle container. - pub fn add_content(mut self, content: Content) -> Jingle { - self.contents.push(content); - self - } - - /// Set the reason in this Jingle container. - pub fn set_reason(mut self, reason: ReasonElement) -> Jingle { - self.reason = Some(reason); - self - } - - /// Set the grouping in this Jingle container. - pub fn set_group(mut self, group: Group) -> Jingle { - self.group = Some(group); - self - } -} - -impl TryFrom for Jingle { - type Error = Error; - - fn try_from(root: Element) -> Result { - check_self!(root, "jingle", JINGLE, "Jingle"); - check_no_unknown_attributes!(root, "Jingle", ["action", "initiator", "responder", "sid"]); - - let mut jingle = Jingle { - action: get_attr!(root, "action", Required), - initiator: get_attr!(root, "initiator", Option), - responder: get_attr!(root, "responder", Option), - sid: get_attr!(root, "sid", Required), - contents: vec![], - reason: None, - group: None, - other: vec![], - }; - - for child in root.children().cloned() { - if child.is("content", ns::JINGLE) { - let content = Content::try_from(child)?; - jingle.contents.push(content); - } else if child.is("reason", ns::JINGLE) { - if jingle.reason.is_some() { - return Err(Error::ParseError( - "Jingle must not have more than one reason.", - )); - } - let reason = ReasonElement::try_from(child)?; - jingle.reason = Some(reason); - } else if child.is("group", ns::JINGLE_GROUPING) { - if jingle.group.is_some() { - return Err(Error::ParseError( - "Jingle must not have more than one grouping.", - )); - } - let group = Group::try_from(child)?; - jingle.group = Some(group); - } else { - jingle.other.push(child); - } - } - - Ok(jingle) - } -} - -impl From for Element { - fn from(jingle: Jingle) -> Element { - Element::builder("jingle", ns::JINGLE) - .attr("action", jingle.action) - .attr("initiator", jingle.initiator) - .attr("responder", jingle.responder) - .attr("sid", jingle.sid) - .append_all(jingle.contents) - .append_all(jingle.reason.map(Element::from)) - .append_all(jingle.group.map(Element::from)) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Action, 1); - assert_size!(Creator, 1); - assert_size!(Senders, 1); - assert_size!(Disposition, 1); - assert_size!(ContentId, 12); - assert_size!(Content, 252); - assert_size!(Reason, 1); - assert_size!(ReasonElement, 16); - assert_size!(SessionId, 12); - assert_size!(Jingle, 152); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Action, 1); - assert_size!(Creator, 1); - assert_size!(Senders, 1); - assert_size!(Disposition, 1); - assert_size!(ContentId, 24); - assert_size!(Content, 504); - assert_size!(Reason, 1); - assert_size!(ReasonElement, 32); - assert_size!(SessionId, 24); - assert_size!(Jingle, 304); - } - - #[test] - fn test_simple() { - let elem: Element = - "" - .parse() - .unwrap(); - let jingle = Jingle::try_from(elem).unwrap(); - assert_eq!(jingle.action, Action::SessionInitiate); - assert_eq!(jingle.sid, SessionId(String::from("coucou"))); - } - - #[test] - fn test_invalid_jingle() { - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'action' missing."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'sid' missing."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'action' attribute."); - } - - #[test] - fn test_content() { - let elem: Element = "".parse().unwrap(); - let jingle = Jingle::try_from(elem).unwrap(); - assert_eq!(jingle.contents[0].creator, Creator::Initiator); - assert_eq!(jingle.contents[0].name, ContentId(String::from("coucou"))); - assert_eq!(jingle.contents[0].senders, Senders::Both); - assert_eq!(jingle.contents[0].disposition, Disposition::Session); - - let elem: Element = "".parse().unwrap(); - let jingle = Jingle::try_from(elem).unwrap(); - assert_eq!(jingle.contents[0].senders, Senders::Both); - - let elem: Element = "".parse().unwrap(); - let jingle = Jingle::try_from(elem).unwrap(); - assert_eq!(jingle.contents[0].disposition, Disposition::EarlySession); - } - - #[test] - fn test_invalid_content() { - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'creator' missing."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'name' missing."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'creator' attribute."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'senders' attribute."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'senders' attribute."); - } - - #[test] - fn test_reason() { - let elem: Element = "".parse().unwrap(); - let jingle = Jingle::try_from(elem).unwrap(); - let reason = jingle.reason.unwrap(); - assert_eq!(reason.reason, Reason::Success); - assert_eq!(reason.texts, BTreeMap::new()); - - let elem: Element = "coucou".parse().unwrap(); - let jingle = Jingle::try_from(elem).unwrap(); - let reason = jingle.reason.unwrap(); - assert_eq!(reason.reason, Reason::Success); - assert_eq!(reason.texts.get(""), Some(&String::from("coucou"))); - } - - #[test] - fn test_invalid_reason() { - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Reason doesn’t contain a valid reason."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown reason."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Reason contains a foreign element."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Jingle must not have more than one reason."); - - let elem: Element = "".parse().unwrap(); - let error = Jingle::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Text element present twice for the same xml:lang."); - } - - #[test] - fn test_serialize_jingle() { - let reference: Element = "" - .parse() - .unwrap(); - - let jingle = Jingle { - action: Action::SessionInitiate, - initiator: None, - responder: None, - sid: SessionId(String::from("a73sjjvkla37jfea")), - contents: vec![Content { - creator: Creator::Initiator, - disposition: Disposition::default(), - name: ContentId(String::from("this-is-a-stub")), - senders: Senders::default(), - description: Some(Description::Unknown( - Element::builder("description", "urn:xmpp:jingle:apps:stub:0").build(), - )), - transport: Some(Transport::Unknown( - Element::builder("transport", "urn:xmpp:jingle:transports:stub:0").build(), - )), - security: None, - }], - reason: None, - group: None, - other: vec![], - }; - let serialized: Element = jingle.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/jingle_dtls_srtp.rs b/xmpp-parsers/src/jingle_dtls_srtp.rs deleted file mode 100644 index 76dd92e..0000000 --- a/xmpp-parsers/src/jingle_dtls_srtp.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::hashes::{Algo, Hash}; -use crate::util::error::Error; -use crate::util::helpers::ColonSeparatedHex; - -generate_attribute!( - /// Indicates which of the end points should initiate the TCP connection establishment. - Setup, "setup", { - /// The endpoint will initiate an outgoing connection. - Active => "active", - - /// The endpoint will accept an incoming connection. - Passive => "passive", - - /// The endpoint is willing to accept an incoming connection or to initiate an outgoing - /// connection. - Actpass => "actpass", - - /* - /// The endpoint does not want the connection to be established for the time being. - /// - /// Note that this value isn’t used, as per the XEP. - Holdconn => "holdconn", - */ - } -); - -// TODO: use a hashes::Hash instead of two different fields here. -generate_element!( - /// Fingerprint of the key used for a DTLS handshake. - Fingerprint, "fingerprint", JINGLE_DTLS, - attributes: [ - /// The hash algorithm used for this fingerprint. - hash: Required = "hash", - - /// Indicates which of the end points should initiate the TCP connection establishment. - setup: Option = "setup", - - /// Indicates whether DTLS is mandatory - required: Option = "required" - ], - text: ( - /// Hash value of this fingerprint. - value: ColonSeparatedHex> - ) -); - -impl Fingerprint { - /// Create a new Fingerprint from a Setup and a Hash. - pub fn from_hash(setup: Setup, hash: Hash) -> Fingerprint { - Fingerprint { - hash: hash.algo, - setup: Some(setup), - value: hash.hash, - required: None, - } - } - - /// Create a new Fingerprint from a Setup and parsing the hash. - pub fn from_colon_separated_hex( - setup: Setup, - algo: &str, - hash: &str, - ) -> Result { - let algo = algo.parse()?; - let hash = Hash::from_colon_separated_hex(algo, hash)?; - Ok(Fingerprint::from_hash(setup, hash)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Setup, 1); - assert_size!(Fingerprint, 32); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Setup, 1); - assert_size!(Fingerprint, 64); - } - - #[test] - fn test_ex1() { - let elem: Element = "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2" - .parse() - .unwrap(); - let fingerprint = Fingerprint::try_from(elem).unwrap(); - assert_eq!(fingerprint.setup, Some(Setup::Actpass)); - assert_eq!(fingerprint.hash, Algo::Sha_256); - assert_eq!( - fingerprint.value, - [ - 2, 26, 204, 84, 39, 171, 235, 156, 83, 63, 62, 75, 101, 46, 125, 70, 63, 84, 66, - 205, 84, 241, 122, 3, 162, 125, 249, 176, 127, 70, 25, 178 - ] - ); - } -} diff --git a/xmpp-parsers/src/jingle_ft.rs b/xmpp-parsers/src/jingle_ft.rs deleted file mode 100644 index d44b7e1..0000000 --- a/xmpp-parsers/src/jingle_ft.rs +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::date::DateTime; -use crate::hashes::Hash; -use crate::jingle::{ContentId, Creator}; -use crate::ns; -use crate::util::error::Error; -use minidom::{Element, Node}; -use std::collections::BTreeMap; -use std::convert::TryFrom; -use std::str::FromStr; - -generate_element!( - /// Represents a range in a file. - #[derive(Default)] - Range, "range", JINGLE_FT, - attributes: [ - /// The offset in bytes from the beginning of the file. - offset: Default = "offset", - - /// The length in bytes of the range, or None to be the entire - /// remaining of the file. - length: Option = "length" - ], - children: [ - /// List of hashes for this range. - hashes: Vec = ("hash", HASHES) => Hash - ] -); - -impl Range { - /// Creates a new range. - pub fn new() -> Range { - Default::default() - } -} - -type Lang = String; - -generate_id!( - /// Wrapper for a file description. - Desc -); - -/// Represents a file to be transferred. -#[derive(Debug, Clone, Default)] -pub struct File { - /// The date of last modification of this file. - pub date: Option, - - /// The MIME type of this file. - pub media_type: Option, - - /// The name of this file. - pub name: Option, - - /// The description of this file, possibly localised. - pub descs: BTreeMap, - - /// The size of this file, in bytes. - pub size: Option, - - /// Used to request only a part of this file. - pub range: Option, - - /// A list of hashes matching this entire file. - pub hashes: Vec, -} - -impl File { - /// Creates a new file descriptor. - pub fn new() -> File { - File::default() - } - - /// Sets the date of last modification on this file. - pub fn with_date(mut self, date: DateTime) -> File { - self.date = Some(date); - self - } - - /// Sets the date of last modification on this file from an ISO-8601 - /// string. - pub fn with_date_str(mut self, date: &str) -> Result { - self.date = Some(DateTime::from_str(date)?); - Ok(self) - } - - /// Sets the MIME type of this file. - pub fn with_media_type(mut self, media_type: String) -> File { - self.media_type = Some(media_type); - self - } - - /// Sets the name of this file. - pub fn with_name(mut self, name: String) -> File { - self.name = Some(name); - self - } - - /// Sets a description for this file. - pub fn add_desc(mut self, lang: &str, desc: Desc) -> File { - self.descs.insert(Lang::from(lang), desc); - self - } - - /// Sets the file size of this file, in bytes. - pub fn with_size(mut self, size: u64) -> File { - self.size = Some(size); - self - } - - /// Request only a range of this file. - pub fn with_range(mut self, range: Range) -> File { - self.range = Some(range); - self - } - - /// Add a hash on this file. - pub fn add_hash(mut self, hash: Hash) -> File { - self.hashes.push(hash); - self - } -} - -impl TryFrom for File { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "file", JINGLE_FT); - check_no_attributes!(elem, "file"); - - let mut file = File { - date: None, - media_type: None, - name: None, - descs: BTreeMap::new(), - size: None, - range: None, - hashes: vec![], - }; - - for child in elem.children() { - if child.is("date", ns::JINGLE_FT) { - if file.date.is_some() { - return Err(Error::ParseError("File must not have more than one date.")); - } - file.date = Some(child.text().parse()?); - } else if child.is("media-type", ns::JINGLE_FT) { - if file.media_type.is_some() { - return Err(Error::ParseError( - "File must not have more than one media-type.", - )); - } - file.media_type = Some(child.text()); - } else if child.is("name", ns::JINGLE_FT) { - if file.name.is_some() { - return Err(Error::ParseError("File must not have more than one name.")); - } - file.name = Some(child.text()); - } else if child.is("desc", ns::JINGLE_FT) { - let lang = get_attr!(child, "xml:lang", Default); - let desc = Desc(child.text()); - if file.descs.insert(lang, desc).is_some() { - return Err(Error::ParseError( - "Desc element present twice for the same xml:lang.", - )); - } - } else if child.is("size", ns::JINGLE_FT) { - if file.size.is_some() { - return Err(Error::ParseError("File must not have more than one size.")); - } - file.size = Some(child.text().parse()?); - } else if child.is("range", ns::JINGLE_FT) { - if file.range.is_some() { - return Err(Error::ParseError("File must not have more than one range.")); - } - file.range = Some(Range::try_from(child.clone())?); - } else if child.is("hash", ns::HASHES) { - file.hashes.push(Hash::try_from(child.clone())?); - } else { - return Err(Error::ParseError("Unknown element in JingleFT file.")); - } - } - - Ok(file) - } -} - -impl From for Element { - fn from(file: File) -> Element { - Element::builder("file", ns::JINGLE_FT) - .append_all( - file.date - .map(|date| Element::builder("date", ns::JINGLE_FT).append(date)), - ) - .append_all( - file.media_type.map(|media_type| { - Element::builder("media-type", ns::JINGLE_FT).append(media_type) - }), - ) - .append_all( - file.name - .map(|name| Element::builder("name", ns::JINGLE_FT).append(name)), - ) - .append_all(file.descs.into_iter().map(|(lang, desc)| { - Element::builder("desc", ns::JINGLE_FT) - .attr("xml:lang", lang) - .append(desc.0) - })) - .append_all( - file.size.map(|size| { - Element::builder("size", ns::JINGLE_FT).append(format!("{}", size)) - }), - ) - .append_all(file.range) - .append_all(file.hashes) - .build() - } -} - -/// A wrapper element for a file. -#[derive(Debug, Clone)] -pub struct Description { - /// The actual file descriptor. - pub file: File, -} - -impl TryFrom for Description { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "description", JINGLE_FT, "JingleFT description"); - check_no_attributes!(elem, "JingleFT description"); - let mut file = None; - for child in elem.children() { - if file.is_some() { - return Err(Error::ParseError( - "JingleFT description element must have exactly one child.", - )); - } - file = Some(File::try_from(child.clone())?); - } - if file.is_none() { - return Err(Error::ParseError( - "JingleFT description element must have exactly one child.", - )); - } - Ok(Description { - file: file.unwrap(), - }) - } -} - -impl From for Element { - fn from(description: Description) -> Element { - Element::builder("description", ns::JINGLE_FT) - .append(Node::Element(description.file.into())) - .build() - } -} - -/// A checksum for checking that the file has been transferred correctly. -#[derive(Debug, Clone)] -pub struct Checksum { - /// The identifier of the file transfer content. - pub name: ContentId, - - /// The creator of this file transfer. - pub creator: Creator, - - /// The file being checksummed. - pub file: File, -} - -impl TryFrom for Checksum { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "checksum", JINGLE_FT); - check_no_unknown_attributes!(elem, "checksum", ["name", "creator"]); - let mut file = None; - for child in elem.children() { - if file.is_some() { - return Err(Error::ParseError( - "JingleFT checksum element must have exactly one child.", - )); - } - file = Some(File::try_from(child.clone())?); - } - if file.is_none() { - return Err(Error::ParseError( - "JingleFT checksum element must have exactly one child.", - )); - } - Ok(Checksum { - name: get_attr!(elem, "name", Required), - creator: get_attr!(elem, "creator", Required), - file: file.unwrap(), - }) - } -} - -impl From for Element { - fn from(checksum: Checksum) -> Element { - Element::builder("checksum", ns::JINGLE_FT) - .attr("name", checksum.name) - .attr("creator", checksum.creator) - .append(Node::Element(checksum.file.into())) - .build() - } -} - -generate_element!( - /// A notice that the file transfer has been completed. - Received, "received", JINGLE_FT, - attributes: [ - /// The content identifier of this Jingle session. - name: Required = "name", - - /// The creator of this file transfer. - creator: Required = "creator", - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::hashes::Algo; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Range, 40); - assert_size!(File, 128); - assert_size!(Description, 128); - assert_size!(Checksum, 144); - assert_size!(Received, 16); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Range, 48); - assert_size!(File, 184); - assert_size!(Description, 184); - assert_size!(Checksum, 216); - assert_size!(Received, 32); - } - - #[test] - fn test_description() { - let elem: Element = r#" - - - text/plain - test.txt - 2015-07-26T21:46:00+01:00 - 6144 - w0mcJylzCn+AfvuGdqkty2+KP48= - - -"# - .parse() - .unwrap(); - let desc = Description::try_from(elem).unwrap(); - assert_eq!(desc.file.media_type, Some(String::from("text/plain"))); - assert_eq!(desc.file.name, Some(String::from("test.txt"))); - assert_eq!(desc.file.descs, BTreeMap::new()); - assert_eq!( - desc.file.date, - DateTime::from_str("2015-07-26T21:46:00+01:00").ok() - ); - assert_eq!(desc.file.size, Some(6144u64)); - assert_eq!(desc.file.range, None); - assert_eq!(desc.file.hashes[0].algo, Algo::Sha_1); - assert_eq!( - desc.file.hashes[0].hash, - base64::decode("w0mcJylzCn+AfvuGdqkty2+KP48=").unwrap() - ); - } - - #[test] - fn test_request() { - let elem: Element = r#" - - - w0mcJylzCn+AfvuGdqkty2+KP48= - - -"# - .parse() - .unwrap(); - let desc = Description::try_from(elem).unwrap(); - assert_eq!(desc.file.media_type, None); - assert_eq!(desc.file.name, None); - assert_eq!(desc.file.descs, BTreeMap::new()); - assert_eq!(desc.file.date, None); - assert_eq!(desc.file.size, None); - assert_eq!(desc.file.range, None); - assert_eq!(desc.file.hashes[0].algo, Algo::Sha_1); - assert_eq!( - desc.file.hashes[0].hash, - base64::decode("w0mcJylzCn+AfvuGdqkty2+KP48=").unwrap() - ); - } - - #[test] - fn test_descs() { - let elem: Element = r#" - - - text/plain - Fichier secret ! - Secret file! - w0mcJylzCn+AfvuGdqkty2+KP48= - - -"# - .parse() - .unwrap(); - let desc = Description::try_from(elem).unwrap(); - assert_eq!( - desc.file.descs.keys().cloned().collect::>(), - ["en", "fr"] - ); - assert_eq!(desc.file.descs["en"], Desc(String::from("Secret file!"))); - assert_eq!( - desc.file.descs["fr"], - Desc(String::from("Fichier secret !")) - ); - - let elem: Element = r#" - - - text/plain - Fichier secret ! - Secret file! - w0mcJylzCn+AfvuGdqkty2+KP48= - - -"# - .parse() - .unwrap(); - let error = Description::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Desc element present twice for the same xml:lang."); - } - - #[test] - fn test_received() { - let elem: Element = "".parse().unwrap(); - let received = Received::try_from(elem).unwrap(); - assert_eq!(received.name, ContentId(String::from("coucou"))); - assert_eq!(received.creator, Creator::Initiator); - let elem2 = Element::from(received.clone()); - let received2 = Received::try_from(elem2).unwrap(); - assert_eq!(received2.name, ContentId(String::from("coucou"))); - assert_eq!(received2.creator, Creator::Initiator); - - let elem: Element = "".parse().unwrap(); - let error = Received::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in received element."); - - let elem: Element = - "" - .parse() - .unwrap(); - let error = Received::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'name' missing."); - - let elem: Element = "".parse().unwrap(); - let error = Received::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'creator' attribute."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_received() { - let elem: Element = "".parse().unwrap(); - let error = Received::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in received element."); - } - - #[test] - fn test_checksum() { - let elem: Element = "w0mcJylzCn+AfvuGdqkty2+KP48=".parse().unwrap(); - let hash = vec![ - 195, 73, 156, 39, 41, 115, 10, 127, 128, 126, 251, 134, 118, 169, 45, 203, 111, 138, - 63, 143, - ]; - let checksum = Checksum::try_from(elem).unwrap(); - assert_eq!(checksum.name, ContentId(String::from("coucou"))); - assert_eq!(checksum.creator, Creator::Initiator); - assert_eq!( - checksum.file.hashes, - vec!(Hash { - algo: Algo::Sha_1, - hash: hash.clone() - }) - ); - let elem2 = Element::from(checksum); - let checksum2 = Checksum::try_from(elem2).unwrap(); - assert_eq!(checksum2.name, ContentId(String::from("coucou"))); - assert_eq!(checksum2.creator, Creator::Initiator); - assert_eq!( - checksum2.file.hashes, - vec!(Hash { - algo: Algo::Sha_1, - hash: hash.clone() - }) - ); - - let elem: Element = "".parse().unwrap(); - let error = Checksum::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "This is not a file element."); - - let elem: Element = "w0mcJylzCn+AfvuGdqkty2+KP48=".parse().unwrap(); - let error = Checksum::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'name' missing."); - - let elem: Element = "w0mcJylzCn+AfvuGdqkty2+KP48=".parse().unwrap(); - let error = Checksum::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'creator' attribute."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_checksum() { - let elem: Element = "w0mcJylzCn+AfvuGdqkty2+KP48=".parse().unwrap(); - let error = Checksum::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in checksum element."); - } - - #[test] - fn test_range() { - let elem: Element = "" - .parse() - .unwrap(); - let range = Range::try_from(elem).unwrap(); - assert_eq!(range.offset, 0); - assert_eq!(range.length, None); - assert_eq!(range.hashes, vec!()); - - let elem: Element = "kHp5RSzW/h7Gm1etSf90Mr5PC/k=".parse().unwrap(); - let hashes = vec![Hash { - algo: Algo::Sha_1, - hash: vec![ - 144, 122, 121, 69, 44, 214, 254, 30, 198, 155, 87, 173, 73, 255, 116, 50, 190, 79, - 11, 249, - ], - }]; - let range = Range::try_from(elem).unwrap(); - assert_eq!(range.offset, 2048); - assert_eq!(range.length, Some(1024)); - assert_eq!(range.hashes, hashes); - let elem2 = Element::from(range); - let range2 = Range::try_from(elem2).unwrap(); - assert_eq!(range2.offset, 2048); - assert_eq!(range2.length, Some(1024)); - assert_eq!(range2.hashes, hashes); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_range() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Range::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in range element."); - } -} diff --git a/xmpp-parsers/src/jingle_grouping.rs b/xmpp-parsers/src/jingle_grouping.rs deleted file mode 100644 index 01c882d..0000000 --- a/xmpp-parsers/src/jingle_grouping.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2020 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::jingle::ContentId; - -generate_attribute!( - /// The semantics of the grouping. - Semantics, "semantics", { - /// Lip synchronsation. - Ls => "LS", - - /// Bundle. - Bundle => "BUNDLE", - } -); - -generate_element!( - /// Describes a content that should be grouped with other ones. - Content, "content", JINGLE_GROUPING, - attributes: [ - /// The name of the matching [`Content`](crate::jingle::Content). - name: Required = "name", - ] -); - -impl Content { - /// Creates a new element. - pub fn new(name: &str) -> Content { - Content { - name: ContentId(name.to_string()), - } - } -} - -generate_element!( - /// A semantic group of contents. - Group, "group", JINGLE_GROUPING, - attributes: [ - /// Semantics of the grouping. - semantics: Required = "semantics", - ], - children: [ - /// List of contents that should be grouped with each other. - contents: Vec = ("content", JINGLE_GROUPING) => Content - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Semantics, 1); - assert_size!(Content, 12); - assert_size!(Group, 16); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Semantics, 1); - assert_size!(Content, 24); - assert_size!(Group, 32); - } - - #[test] - fn parse_group() { - let elem: Element = " - - - - " - .parse() - .unwrap(); - let group = Group::try_from(elem).unwrap(); - assert_eq!(group.semantics, Semantics::Bundle); - assert_eq!(group.contents.len(), 2); - assert_eq!( - group.contents, - &[Content::new("voice"), Content::new("webcam")] - ); - } -} diff --git a/xmpp-parsers/src/jingle_ibb.rs b/xmpp-parsers/src/jingle_ibb.rs deleted file mode 100644 index 3018842..0000000 --- a/xmpp-parsers/src/jingle_ibb.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ibb::{Stanza, StreamId}; - -generate_element!( -/// Describes an [In-Band Bytestream](https://xmpp.org/extensions/xep-0047.html) -/// Jingle transport, see also the [IBB module](../ibb.rs). -Transport, "transport", JINGLE_IBB, -attributes: [ - /// Maximum size in bytes for each chunk. - block_size: Required = "block-size", - - /// The identifier to be used to create a stream. - sid: Required = "sid", - - /// Which stanza type to use to exchange data. - stanza: Default = "stanza", -]); - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Transport, 16); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Transport, 32); - } - - #[test] - fn test_simple() { - let elem: Element = - "" - .parse() - .unwrap(); - let transport = Transport::try_from(elem).unwrap(); - assert_eq!(transport.block_size, 3); - assert_eq!(transport.sid, StreamId(String::from("coucou"))); - assert_eq!(transport.stanza, Stanza::Iq); - } - - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Transport::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'block-size' missing."); - - let elem: Element = - "" - .parse() - .unwrap(); - let error = Transport::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!( - message.to_string(), - "number too large to fit in target type" - ); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Transport::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(message.to_string(), "invalid digit found in string"); - - let elem: Element = - "" - .parse() - .unwrap(); - let error = Transport::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'sid' missing."); - } - - #[test] - fn test_invalid_stanza() { - let elem: Element = "".parse().unwrap(); - let error = Transport::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'stanza' attribute."); - } -} diff --git a/xmpp-parsers/src/jingle_ice_udp.rs b/xmpp-parsers/src/jingle_ice_udp.rs deleted file mode 100644 index 2f89d97..0000000 --- a/xmpp-parsers/src/jingle_ice_udp.rs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::jingle_dtls_srtp::Fingerprint; -use std::net::IpAddr; - -generate_empty_element!( - /// Specifies the ability to multiplex RTP Data and Control Packets on a single port as - /// described in RFC 5761. - RtcpMux, - "rtcp-mux", - JINGLE_ICE_UDP -); - -generate_element!( - /// Wrapper element for an ICE-UDP transport. - Transport, "transport", JINGLE_ICE_UDP, - attributes: [ - /// A Password as defined in ICE-CORE. - pwd: Option = "pwd", - - /// A User Fragment as defined in ICE-CORE. - ufrag: Option = "ufrag", - ], - children: [ - /// List of candidates for this ICE-UDP session. - candidates: Vec = ("candidate", JINGLE_ICE_UDP) => Candidate, - - /// Fingerprint of the key used for the DTLS handshake. - fingerprint: Option = ("fingerprint", JINGLE_DTLS) => Fingerprint, - - /// Indicates that RTCP can be muxed - rtcp_mux: Option = ("rtcp-mux", JINGLE_ICE_UDP) => RtcpMux, - - /// Details of the Colibri WebSocket - web_socket: Option = ("web-socket", JITSI_COLIBRI) => WebSocket - ] -); - -impl Transport { - /// Create a new ICE-UDP transport. - pub fn new() -> Transport { - Transport { - pwd: None, - ufrag: None, - candidates: Vec::new(), - fingerprint: None, - rtcp_mux: None, - web_socket: None, - } - } - - /// Add a candidate to this transport. - pub fn add_candidate(mut self, candidate: Candidate) -> Self { - self.candidates.push(candidate); - self - } - - /// Set the DTLS-SRTP fingerprint of this transport. - pub fn with_fingerprint(mut self, fingerprint: Fingerprint) -> Self { - self.fingerprint = Some(fingerprint); - self - } -} - -generate_element!( - /// Colibri WebSocket details - WebSocket, "web-socket", JITSI_COLIBRI, - attributes: [ - /// The WebSocket URL - url: Required = "url", - ] -); - -generate_attribute!( - /// A Candidate Type as defined in ICE-CORE. - Type, "type", { - /// Host candidate. - Host => "host", - - /// Peer reflexive candidate. - Prflx => "prflx", - - /// Relayed candidate. - Relay => "relay", - - /// Server reflexive candidate. - Srflx => "srflx", - } -); - -generate_element!( - /// A candidate for an ICE-UDP session. - Candidate, "candidate", JINGLE_ICE_UDP, - attributes: [ - /// A Component ID as defined in ICE-CORE. - component: Required = "component", - - /// A Foundation as defined in ICE-CORE. - foundation: Required = "foundation", - - /// An index, starting at 0, that enables the parties to keep track of updates to the - /// candidate throughout the life of the session. - generation: Required = "generation", - - /// A unique identifier for the candidate. - id: Required = "id", - - /// The Internet Protocol (IP) address for the candidate transport mechanism; this can be - /// either an IPv4 address or an IPv6 address. - ip: Required = "ip", - - /// The port at the candidate IP address. - port: Required = "port", - - /// A Priority as defined in ICE-CORE. - priority: Required = "priority", - - /// The protocol to be used. The only value defined by this specification is "udp". - protocol: Required = "protocol", - - /// A related address as defined in ICE-CORE. - rel_addr: Option = "rel-addr", - - /// A related port as defined in ICE-CORE. - rel_port: Option = "rel-port", - - /// An index, starting at 0, referencing which network this candidate is on for a given - /// peer. - network: Option = "network", - - /// A Candidate Type as defined in ICE-CORE. - type_: Required = "type", - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::hashes::Algo; - use crate::jingle_dtls_srtp::Setup; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Transport, 68); - assert_size!(Type, 1); - assert_size!(Candidate, 92); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Transport, 136); - assert_size!(Type, 1); - assert_size!(Candidate, 128); - } - - #[test] - fn test_gajim() { - let elem: Element = " - - - - - - - - - - - - - - - - - - - -" - .parse() - .unwrap(); - let transport = Transport::try_from(elem).unwrap(); - assert_eq!(transport.pwd.unwrap(), "wakMJ8Ydd5rqnPaFerws5o"); - assert_eq!(transport.ufrag.unwrap(), "aeXX"); - } - - #[test] - fn test_jitsi_meet() { - let elem: Element = " - - 97:F2:B5:BE:DB:A6:00:B1:3E:40:B2:41:3C:0D:FC:E0:BD:B2:A0:E8 - - - -" - .parse() - .unwrap(); - let transport = Transport::try_from(elem).unwrap(); - assert_eq!(transport.pwd.unwrap(), "7lk9uul39gckit6t02oavv2r9j"); - assert_eq!(transport.ufrag.unwrap(), "2acq51d4p07v2m"); - - let fingerprint = transport.fingerprint.unwrap(); - assert_eq!(fingerprint.hash, Algo::Sha_1); - assert_eq!(fingerprint.setup, Setup::Actpass); - assert_eq!( - fingerprint.value, - [ - 151, 242, 181, 190, 219, 166, 0, 177, 62, 64, 178, 65, 60, 13, 252, 224, 189, 178, - 160, 232 - ] - ); - } - - #[test] - fn test_serialize_transport() { - let reference: Element = - "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2" - .parse() - .unwrap(); - - let elem: Element = "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2" - .parse() - .unwrap(); - let fingerprint = Fingerprint::try_from(elem).unwrap(); - - let transport = Transport { - pwd: None, - ufrag: None, - candidates: vec![], - fingerprint: Some(fingerprint), - }; - - let serialized: Element = transport.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/jingle_message.rs b/xmpp-parsers/src/jingle_message.rs deleted file mode 100644 index bfc8abb..0000000 --- a/xmpp-parsers/src/jingle_message.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::jingle::SessionId; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use std::convert::TryFrom; - -/// Defines a protocol for broadcasting Jingle requests to all of the clients -/// of a user. -#[derive(Debug, Clone)] -pub enum JingleMI { - /// Indicates we want to start a Jingle session. - Propose { - /// The generated session identifier, must be unique between two users. - sid: SessionId, - - /// The application description of the proposed session. - // TODO: Use a more specialised type here. - description: Element, - }, - - /// Cancels a previously proposed session. - Retract(SessionId), - - /// Accepts a session proposed by the other party. - Accept(SessionId), - - /// Proceed with a previously proposed session. - Proceed(SessionId), - - /// Rejects a session proposed by the other party. - Reject(SessionId), -} - -fn get_sid(elem: Element) -> Result { - check_no_unknown_attributes!(elem, "Jingle message", ["id"]); - Ok(SessionId(get_attr!(elem, "id", Required))) -} - -fn check_empty_and_get_sid(elem: Element) -> Result { - check_no_children!(elem, "Jingle message"); - get_sid(elem) -} - -impl TryFrom for JingleMI { - type Error = Error; - - fn try_from(elem: Element) -> Result { - if !elem.has_ns(ns::JINGLE_MESSAGE) { - return Err(Error::ParseError("This is not a Jingle message element.")); - } - Ok(match elem.name() { - "propose" => { - let mut description = None; - for child in elem.children() { - if child.name() != "description" { - return Err(Error::ParseError("Unknown child in propose element.")); - } - if description.is_some() { - return Err(Error::ParseError("Too many children in propose element.")); - } - description = Some(child.clone()); - } - JingleMI::Propose { - sid: get_sid(elem)?, - description: description.ok_or(Error::ParseError( - "Propose element doesn’t contain a description.", - ))?, - } - } - "retract" => JingleMI::Retract(check_empty_and_get_sid(elem)?), - "accept" => JingleMI::Accept(check_empty_and_get_sid(elem)?), - "proceed" => JingleMI::Proceed(check_empty_and_get_sid(elem)?), - "reject" => JingleMI::Reject(check_empty_and_get_sid(elem)?), - _ => return Err(Error::ParseError("This is not a Jingle message element.")), - }) - } -} - -impl From for Element { - fn from(jingle_mi: JingleMI) -> Element { - match jingle_mi { - JingleMI::Propose { sid, description } => { - Element::builder("propose", ns::JINGLE_MESSAGE) - .attr("id", sid) - .append(description) - } - JingleMI::Retract(sid) => { - Element::builder("retract", ns::JINGLE_MESSAGE).attr("id", sid) - } - JingleMI::Accept(sid) => Element::builder("accept", ns::JINGLE_MESSAGE).attr("id", sid), - JingleMI::Proceed(sid) => { - Element::builder("proceed", ns::JINGLE_MESSAGE).attr("id", sid) - } - JingleMI::Reject(sid) => Element::builder("reject", ns::JINGLE_MESSAGE).attr("id", sid), - } - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(JingleMI, 92); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(JingleMI, 184); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - JingleMI::try_from(elem).unwrap(); - } - - #[test] - fn test_invalid_child() { - let elem: Element = - "" - .parse() - .unwrap(); - let error = JingleMI::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in propose element."); - } -} diff --git a/xmpp-parsers/src/jingle_raw_udp.rs b/xmpp-parsers/src/jingle_raw_udp.rs deleted file mode 100644 index 9197980..0000000 --- a/xmpp-parsers/src/jingle_raw_udp.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2020 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::jingle_ice_udp::Type; -use std::net::IpAddr; - -generate_element!( - /// Wrapper element for an raw UDP transport. - Transport, "transport", JINGLE_RAW_UDP, - children: [ - /// List of candidates for this raw UDP session. - candidates: Vec = ("candidate", JINGLE_RAW_UDP) => Candidate - ] -); - -impl Transport { - /// Create a new ICE-UDP transport. - pub fn new() -> Transport { - Transport { - candidates: Vec::new(), - } - } - - /// Add a candidate to this transport. - pub fn add_candidate(mut self, candidate: Candidate) -> Self { - self.candidates.push(candidate); - self - } -} - -generate_element!( - /// A candidate for an ICE-UDP session. - Candidate, "candidate", JINGLE_RAW_UDP, - attributes: [ - /// A Component ID as defined in ICE-CORE. - component: Required = "component", - - /// An index, starting at 0, that enables the parties to keep track of updates to the - /// candidate throughout the life of the session. - generation: Required = "generation", - - /// A unique identifier for the candidate. - id: Required = "id", - - /// The Internet Protocol (IP) address for the candidate transport mechanism; this can be - /// either an IPv4 address or an IPv6 address. - ip: Required = "ip", - - /// The port at the candidate IP address. - port: Required = "port", - - /// A Candidate Type as defined in ICE-CORE. - type_: Option = "type", - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Transport, 12); - assert_size!(Candidate, 40); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Transport, 24); - assert_size!(Candidate, 56); - } - - #[test] - fn example_1() { - let elem: Element = " - - -" - .parse() - .unwrap(); - let mut transport = Transport::try_from(elem).unwrap(); - assert_eq!(transport.candidates.len(), 1); - let candidate = transport.candidates.pop().unwrap(); - assert_eq!(candidate.component, 1); - assert_eq!(candidate.generation, 0); - assert_eq!(candidate.id, "a9j3mnbtu1"); - assert_eq!(candidate.ip, "10.1.1.104".parse::().unwrap()); - assert_eq!(candidate.port, 13540u16); - assert!(candidate.type_.is_none()); - } -} diff --git a/xmpp-parsers/src/jingle_rtcp_fb.rs b/xmpp-parsers/src/jingle_rtcp_fb.rs deleted file mode 100644 index d02a589..0000000 --- a/xmpp-parsers/src/jingle_rtcp_fb.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -generate_element!( - /// Wrapper element for a rtcp-fb. - RtcpFb, "rtcp-fb", JINGLE_RTCP_FB, - attributes: [ - /// Type of this rtcp-fb. - type_: Required = "type", - - /// Subtype of this rtcp-fb, if relevant. - subtype: Option = "subtype", - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(RtcpFb, 24); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(RtcpFb, 48); - } - - #[test] - fn parse_simple() { - let elem: Element = - "" - .parse() - .unwrap(); - let rtcp_fb = RtcpFb::try_from(elem).unwrap(); - assert_eq!(rtcp_fb.type_, "nack"); - assert_eq!(rtcp_fb.subtype.unwrap(), "sli"); - } -} diff --git a/xmpp-parsers/src/jingle_rtp.rs b/xmpp-parsers/src/jingle_rtp.rs deleted file mode 100644 index aacdf5f..0000000 --- a/xmpp-parsers/src/jingle_rtp.rs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2019-2020 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::jingle_rtcp_fb::RtcpFb; -use crate::jingle_rtp_hdrext::RtpHdrext; -use crate::jingle_ssma::{Group, Source}; - -generate_empty_element!( - /// Specifies the ability to multiplex RTP Data and Control Packets on a single port as - /// described in RFC 5761. - RtcpMux, - "rtcp-mux", - JINGLE_RTP -); - -generate_element!( - /// Wrapper element describing an RTP session. - Description, "description", JINGLE_RTP, - attributes: [ - /// Namespace of the encryption scheme used. - media: Required = "media", - - /// ssrc? - ssrc: Option = "ssrc", - - /// maximum packet time - maxptime: Option = "maxptime", - ], - children: [ - /// List of encodings that can be used for this RTP stream. - payload_types: Vec = ("payload-type", JINGLE_RTP) => PayloadType, - - /// Specifies the ability to multiplex RTP Data and Control Packets on a single port as - /// described in RFC 5761. - rtcp_mux: Option = ("rtcp-mux", JINGLE_RTP) => RtcpMux, - - /// List of ssrc-group. - ssrc_groups: Vec = ("ssrc-group", JINGLE_SSMA) => Group, - - /// List of ssrc. - ssrcs: Vec = ("source", JINGLE_SSMA) => Source, - - /// List of header extensions. - hdrexts: Vec = ("rtp-hdrext", JINGLE_RTP_HDREXT) => RtpHdrext - - // TODO: Add support for and . - ] -); - -impl Description { - /// Create a new RTP description. - pub fn new(media: String) -> Description { - Description { - media, - ssrc: None, - maxptime: None, - payload_types: Vec::new(), - rtcp_mux: None, - ssrc_groups: Vec::new(), - ssrcs: Vec::new(), - hdrexts: Vec::new(), - } - } -} - -generate_attribute!( - /// The number of channels. - Channels, - "channels", - u8, - Default = 1 -); - -generate_element!( - /// An encoding that can be used for an RTP stream. - PayloadType, "payload-type", JINGLE_RTP, - attributes: [ - /// The number of channels. - channels: Default = "channels", - - /// The sampling frequency in Hertz. - clockrate: Option = "clockrate", - - /// The payload identifier. - id: Required = "id", - - /// Maximum packet time as specified in RFC 4566. - maxptime: Option = "maxptime", - - /// The appropriate subtype of the MIME type. - name: Option = "name", - - /// Packet time as specified in RFC 4566. - ptime: Option = "ptime", - ], - children: [ - /// List of parameters specifying this payload-type. - /// - /// Their order MUST be ignored. - parameters: Vec = ("parameter", JINGLE_RTP) => Parameter, - - /// List of rtcp-fb parameters from XEP-0293. - rtcp_fbs: Vec = ("rtcp-fb", JINGLE_RTCP_FB) => RtcpFb - ] -); - -impl PayloadType { - /// Create a new RTP payload-type. - pub fn new(id: u8, name: String, clockrate: u32, channels: u8) -> PayloadType { - PayloadType { - channels: Channels(channels), - clockrate: Some(clockrate), - id, - maxptime: None, - name: Some(name), - ptime: None, - parameters: Vec::new(), - rtcp_fbs: Vec::new(), - } - } - - /// Create a new RTP payload-type without a clockrate. Warning: this is invalid as per - /// RFC 4566! - pub fn without_clockrate(id: u8, name: String) -> PayloadType { - PayloadType { - channels: Default::default(), - clockrate: None, - id, - maxptime: None, - name: Some(name), - ptime: None, - parameters: Vec::new(), - rtcp_fbs: Vec::new(), - } - } -} - -generate_element!( - /// Parameter related to a payload. - Parameter, "parameter", JINGLE_RTP, - attributes: [ - /// The name of the parameter, from the list at - /// https://www.iana.org/assignments/sdp-parameters/sdp-parameters.xhtml - name: Required = "name", - - /// The value of this parameter. - value: Required = "value", - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Description, 76); - assert_size!(Channels, 1); - assert_size!(PayloadType, 64); - assert_size!(Parameter, 24); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Description, 152); - assert_size!(Channels, 1); - assert_size!(PayloadType, 104); - assert_size!(Parameter, 48); - } - - #[test] - fn test_simple() { - let elem: Element = " - - - - - - - - - - - - - - - - - - - - - - - -" - .parse() - .unwrap(); - let desc = Description::try_from(elem).unwrap(); - assert_eq!(desc.media, "audio"); - assert_eq!(desc.ssrc, None); - } -} diff --git a/xmpp-parsers/src/jingle_rtp_hdrext.rs b/xmpp-parsers/src/jingle_rtp_hdrext.rs deleted file mode 100644 index 199b439..0000000 --- a/xmpp-parsers/src/jingle_rtp_hdrext.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2020 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -generate_attribute!( - /// Which party is allowed to send the negotiated RTP Header Extensions. - Senders, "senders", { - /// Both parties can send them. - Both => "both", - - /// Only the initiator can send them. - Initiator => "initiator", - - /// Only the responder can send them. - Responder => "responder", - }, Default = Both -); - -generate_element!( - /// Header extensions to be used in a RTP description. - RtpHdrext, "rtp-hdrext", JINGLE_RTP_HDREXT, - attributes: [ - /// The ID of the extensions. - id: Required = "id", - - /// The URI that defines the extension. - uri: Required = "uri", - - /// Which party is allowed to send the negotiated RTP Header Extensions. - senders: Default = "senders", - ] -); - -impl RtpHdrext { - /// Create a new RTP header extension element. - pub fn new(id: String, uri: String) -> RtpHdrext { - RtpHdrext { - id, - uri, - senders: Default::default(), - } - } - - /// Set the senders. - pub fn with_senders(mut self, senders: Senders) -> RtpHdrext { - self.senders = senders; - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Senders, 1); - assert_size!(RtpHdrext, 28); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Senders, 1); - assert_size!(RtpHdrext, 56); - } - - #[test] - fn parse_exthdr() { - let elem: Element = " - " - .parse() - .unwrap(); - let rtp_hdrext = RtpHdrext::try_from(elem).unwrap(); - assert_eq!(rtp_hdrext.id, "1"); - assert_eq!(rtp_hdrext.uri, "urn:ietf:params:rtp-hdrext:toffset"); - assert_eq!(rtp_hdrext.senders, Senders::Both); - } -} diff --git a/xmpp-parsers/src/jingle_s5b.rs b/xmpp-parsers/src/jingle_s5b.rs deleted file mode 100644 index e481f56..0000000 --- a/xmpp-parsers/src/jingle_s5b.rs +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::convert::TryFrom; -use std::net::IpAddr; - -generate_attribute!( - /// The type of the connection being proposed by this candidate. - Type, "type", { - /// Direct connection using NAT assisting technologies like NAT-PMP or - /// UPnP-IGD. - Assisted => "assisted", - - /// Direct connection using the given interface. - Direct => "direct", - - /// SOCKS5 relay. - Proxy => "proxy", - - /// Tunnel protocol such as Teredo. - Tunnel => "tunnel", - }, Default = Direct -); - -generate_attribute!( - /// Which mode to use for the connection. - Mode, "mode", { - /// Use TCP, which is the default. - Tcp => "tcp", - - /// Use UDP. - Udp => "udp", - }, Default = Tcp -); - -generate_id!( - /// An identifier for a candidate. - CandidateId -); - -generate_id!( - /// An identifier for a stream. - StreamId -); - -generate_element!( - /// A candidate for a connection. - Candidate, "candidate", JINGLE_S5B, - attributes: [ - /// The identifier for this candidate. - cid: Required = "cid", - - /// The host to connect to. - host: Required = "host", - - /// The JID to request at the given end. - jid: Required = "jid", - - /// The port to connect to. - port: Option = "port", - - /// The priority of this candidate, computed using this formula: - /// priority = (2^16)*(type preference) + (local preference) - priority: Required = "priority", - - /// The type of the connection being proposed by this candidate. - type_: Default = "type", - ] -); - -impl Candidate { - /// Creates a new candidate with the given parameters. - pub fn new(cid: CandidateId, host: IpAddr, jid: Jid, priority: u32) -> Candidate { - Candidate { - cid, - host, - jid, - priority, - port: Default::default(), - type_: Default::default(), - } - } - - /// Sets the port of this candidate. - pub fn with_port(mut self, port: u16) -> Candidate { - self.port = Some(port); - self - } - - /// Sets the type of this candidate. - pub fn with_type(mut self, type_: Type) -> Candidate { - self.type_ = type_; - self - } -} - -/// The payload of a transport. -#[derive(Debug, Clone, PartialEq)] -pub enum TransportPayload { - /// The responder informs the initiator that the bytestream pointed by this - /// candidate has been activated. - Activated(CandidateId), - - /// A list of suggested candidates. - Candidates(Vec), - - /// Both parties failed to use a candidate, they should fallback to another - /// transport. - CandidateError, - - /// The candidate pointed here should be used by both parties. - CandidateUsed(CandidateId), - - /// This entity can’t connect to the SOCKS5 proxy. - ProxyError, - - /// XXX: Invalid, should not be found in the wild. - None, -} - -/// Describes a Jingle transport using a direct or proxied connection. -#[derive(Debug, Clone, PartialEq)] -pub struct Transport { - /// The stream identifier for this transport. - pub sid: StreamId, - - /// The destination address. - pub dstaddr: Option, - - /// The mode to be used for the transfer. - pub mode: Mode, - - /// The payload of this transport. - pub payload: TransportPayload, -} - -impl Transport { - /// Creates a new transport element. - pub fn new(sid: StreamId) -> Transport { - Transport { - sid, - dstaddr: None, - mode: Default::default(), - payload: TransportPayload::None, - } - } - - /// Sets the destination address of this transport. - pub fn with_dstaddr(mut self, dstaddr: String) -> Transport { - self.dstaddr = Some(dstaddr); - self - } - - /// Sets the mode of this transport. - pub fn with_mode(mut self, mode: Mode) -> Transport { - self.mode = mode; - self - } - - /// Sets the payload of this transport. - pub fn with_payload(mut self, payload: TransportPayload) -> Transport { - self.payload = payload; - self - } -} - -impl TryFrom for Transport { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "transport", JINGLE_S5B); - check_no_unknown_attributes!(elem, "transport", ["sid", "dstaddr", "mode"]); - let sid = get_attr!(elem, "sid", Required); - let dstaddr = get_attr!(elem, "dstaddr", Option); - let mode = get_attr!(elem, "mode", Default); - - let mut payload = None; - for child in elem.children() { - payload = Some(if child.is("candidate", ns::JINGLE_S5B) { - let mut candidates = - match payload { - Some(TransportPayload::Candidates(candidates)) => candidates, - Some(_) => return Err(Error::ParseError( - "Non-candidate child already present in JingleS5B transport element.", - )), - None => vec![], - }; - candidates.push(Candidate::try_from(child.clone())?); - TransportPayload::Candidates(candidates) - } else if child.is("activated", ns::JINGLE_S5B) { - if payload.is_some() { - return Err(Error::ParseError( - "Non-activated child already present in JingleS5B transport element.", - )); - } - let cid = get_attr!(child, "cid", Required); - TransportPayload::Activated(cid) - } else if child.is("candidate-error", ns::JINGLE_S5B) { - if payload.is_some() { - return Err(Error::ParseError( - "Non-candidate-error child already present in JingleS5B transport element.", - )); - } - TransportPayload::CandidateError - } else if child.is("candidate-used", ns::JINGLE_S5B) { - if payload.is_some() { - return Err(Error::ParseError( - "Non-candidate-used child already present in JingleS5B transport element.", - )); - } - let cid = get_attr!(child, "cid", Required); - TransportPayload::CandidateUsed(cid) - } else if child.is("proxy-error", ns::JINGLE_S5B) { - if payload.is_some() { - return Err(Error::ParseError( - "Non-proxy-error child already present in JingleS5B transport element.", - )); - } - TransportPayload::ProxyError - } else { - return Err(Error::ParseError( - "Unknown child in JingleS5B transport element.", - )); - }); - } - let payload = payload.unwrap_or(TransportPayload::None); - Ok(Transport { - sid, - dstaddr, - mode, - payload, - }) - } -} - -impl From for Element { - fn from(transport: Transport) -> Element { - Element::builder("transport", ns::JINGLE_S5B) - .attr("sid", transport.sid) - .attr("dstaddr", transport.dstaddr) - .attr("mode", transport.mode) - .append_all(match transport.payload { - TransportPayload::Candidates(candidates) => candidates - .into_iter() - .map(Element::from) - .collect::>(), - TransportPayload::Activated(cid) => { - vec![Element::builder("activated", ns::JINGLE_S5B) - .attr("cid", cid) - .build()] - } - TransportPayload::CandidateError => { - vec![Element::builder("candidate-error", ns::JINGLE_S5B).build()] - } - TransportPayload::CandidateUsed(cid) => { - vec![Element::builder("candidate-used", ns::JINGLE_S5B) - .attr("cid", cid) - .build()] - } - TransportPayload::ProxyError => { - vec![Element::builder("proxy-error", ns::JINGLE_S5B).build()] - } - TransportPayload::None => vec![], - }) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use jid::BareJid; - use std::str::FromStr; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Type, 1); - assert_size!(Mode, 1); - assert_size!(CandidateId, 12); - assert_size!(StreamId, 12); - assert_size!(Candidate, 84); - assert_size!(TransportPayload, 16); - assert_size!(Transport, 44); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Type, 1); - assert_size!(Mode, 1); - assert_size!(CandidateId, 24); - assert_size!(StreamId, 24); - assert_size!(Candidate, 136); - assert_size!(TransportPayload, 32); - assert_size!(Transport, 88); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let transport = Transport::try_from(elem).unwrap(); - assert_eq!(transport.sid, StreamId(String::from("coucou"))); - assert_eq!(transport.dstaddr, None); - assert_eq!(transport.mode, Mode::Tcp); - match transport.payload { - TransportPayload::None => (), - _ => panic!("Wrong element inside transport!"), - } - } - - #[test] - fn test_serialise_activated() { - let elem: Element = "".parse().unwrap(); - let transport = Transport { - sid: StreamId(String::from("coucou")), - dstaddr: None, - mode: Mode::Tcp, - payload: TransportPayload::Activated(CandidateId(String::from("coucou"))), - }; - let elem2: Element = transport.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_serialise_candidate() { - let elem: Element = "".parse().unwrap(); - let transport = Transport { - sid: StreamId(String::from("coucou")), - dstaddr: None, - mode: Mode::Tcp, - payload: TransportPayload::Candidates(vec![Candidate { - cid: CandidateId(String::from("coucou")), - host: IpAddr::from_str("127.0.0.1").unwrap(), - jid: Jid::Bare(BareJid::new("coucou", "coucou")), - port: None, - priority: 0u32, - type_: Type::Direct, - }]), - }; - let elem2: Element = transport.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/jingle_ssma.rs b/xmpp-parsers/src/jingle_ssma.rs deleted file mode 100644 index 7f554b1..0000000 --- a/xmpp-parsers/src/jingle_ssma.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -generate_element!( - /// Source element for the ssrc SDP attribute. - Source, "source", JINGLE_SSMA, - attributes: [ - /// Maps to the ssrc-id parameter. - id: Required = "ssrc", - - /// XXX: wtf is that? It can be either name='jvb-a0' or name='jvb-v0' at avstack’s jicofo. - name: Option = "name", - ], - children: [ - /// List of attributes for this source. - parameters: Vec = ("parameter", JINGLE_SSMA) => Parameter, - - /// ssrc-info for this source. - info: Option = ("ssrc-info", JITSI_MEET) => SsrcInfo - ] -); - -impl Source { - /// Create a new SSMA Source element. - pub fn new(id: String) -> Source { - Source { - id, - parameters: Vec::new(), - info: None, - name: None, - } - } -} - -generate_element!( - /// Parameter associated with a ssrc. - Parameter, "parameter", JINGLE_SSMA, - attributes: [ - /// The name of the parameter. - name: Required = "name", - - /// The optional value of the parameter. - value: Option = "value", - ] -); - -generate_element!( - /// ssrc-info associated with a ssrc. - SsrcInfo, "ssrc-info", JITSI_MEET, - attributes: [ - /// The owner of the ssrc. - owner: Required = "owner" - ] -); - -generate_element!( - /// Element grouping multiple ssrc. - Group, "ssrc-group", JINGLE_SSMA, - attributes: [ - /// The semantics of this group. - semantics: Required = "semantics", - ], - children: [ - /// The various ssrc concerned by this group. - sources: Vec = ("source", JINGLE_SSMA) => Source - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Source, 24); - assert_size!(Parameter, 24); - assert_size!(Group, 24); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Source, 48); - assert_size!(Parameter, 48); - assert_size!(Group, 48); - } - - #[test] - fn parse_source() { - let elem: Element = " - - - -" - .parse() - .unwrap(); - let mut ssrc = Source::try_from(elem).unwrap(); - assert_eq!(ssrc.id, "1656081975"); - assert_eq!(ssrc.parameters.len(), 2); - let parameter = ssrc.parameters.pop().unwrap(); - assert_eq!(parameter.name, "msid"); - assert_eq!( - parameter.value.unwrap(), - "MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIv MLTJKIHilGn71fNQoszkQ4jlPTuS5vJyKVIva0" - ); - let parameter = ssrc.parameters.pop().unwrap(); - assert_eq!(parameter.name, "cname"); - assert_eq!(parameter.value.unwrap(), "Yv/wvbCdsDW2Prgd"); - } - - #[test] - fn parse_source_group() { - let elem: Element = " - - - -" - .parse() - .unwrap(); - let mut group = Group::try_from(elem).unwrap(); - assert_eq!(group.semantics, "FID"); - assert_eq!(group.sources.len(), 2); - let source = group.sources.pop().unwrap(); - assert_eq!(source.id, "386328120"); - let source = group.sources.pop().unwrap(); - assert_eq!(source.id, "2301230316"); - } -} diff --git a/xmpp-parsers/src/lib.rs b/xmpp-parsers/src/lib.rs deleted file mode 100644 index d18f399..0000000 --- a/xmpp-parsers/src/lib.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! A crate parsing common XMPP elements into Rust structures. -//! -//! Each module implements the `TryFrom` trait, which takes a -//! minidom [`Element`] and returns a `Result` whose value is `Ok` if the -//! element parsed correctly, `Err(error::Error)` otherwise. -//! -//! The returned structure can be manipuled as any Rust structure, with each -//! field being public. You can also create the same structure manually, with -//! some having `new()` and `with_*()` helper methods to create them. -//! -//! Once you are happy with your structure, you can serialise it back to an -//! [`Element`], using either `From` or `Into`, which give you what -//! you want to be sending on the wire. -//! -//! [`Element`]: ../minidom/element/struct.Element.html - -// Copyright (c) 2017-2019 Emmanuel Gil Peyrot -// Copyright (c) 2017-2019 Maxime “pep” Buquet -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#![deny(missing_docs)] - -pub use crate::util::error::Error; -pub use jid::{BareJid, FullJid, Jid, JidParseError}; -pub use minidom::Element; - -/// XML namespace definitions used through XMPP. -pub mod ns; - -#[macro_use] -mod util; - -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub mod bind; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub mod iq; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub mod message; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub mod presence; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub mod sasl; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub mod stanza_error; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub mod stream; - -/// RFC 6121: Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence -pub mod roster; - -/// RFC 7395: An Extensible Messaging and Presence Protocol (XMPP) Subprotocol for WebSocket -pub mod websocket; - -/// XEP-0004: Data Forms -pub mod data_forms; - -/// XEP-0030: Service Discovery -pub mod disco; - -/// XEP-0045: Multi-User Chat -pub mod muc; - -/// XEP-0047: In-Band Bytestreams -pub mod ibb; - -/// XEP-0048: Bookmarks -pub mod bookmarks; - -/// XEP-0059: Result Set Management -pub mod rsm; - -/// XEP-0060: Publish-Subscribe -pub mod pubsub; - -/// XEP-0071: XHTML-IM -pub mod xhtml; - -/// XEP-0077: In-Band Registration -pub mod ibr; - -/// XEP-0082: XMPP Date and Time Profiles -pub mod date; - -/// XEP-0084: User Avatar -pub mod avatar; - -/// XEP-0085: Chat State Notifications -pub mod chatstates; - -/// XEP-0092: Software Version -pub mod version; - -/// XEP-0107: User Mood -pub mod mood; - -/// XEP-0114: Jabber Component Protocol -pub mod component; - -/// XEP-0115: Entity Capabilities -pub mod caps; - -/// XEP-0118: User Tune -pub mod tune; - -/// XEP-0157: Contact Addresses for XMPP Services -pub mod server_info; - -/// XEP-0166: Jingle -pub mod jingle; - -/// XEP-0167: Jingle RTP Sessions -pub mod jingle_rtp; - -/// XEP-0172: User Nickname -pub mod nick; - -/// XEP-0176: Jingle ICE-UDP Transport Method -pub mod jingle_ice_udp; - -/// XEP-0177: Jingle Raw UDP Transport Method -pub mod jingle_raw_udp; - -/// XEP-0184: Message Delivery Receipts -pub mod receipts; - -/// XEP-0191: Blocking Command -pub mod blocking; - -/// XEP-0198: Stream Management -pub mod sm; - -/// XEP-0199: XMPP Ping -pub mod ping; - -/// XEP-0202: Entity Time -pub mod time; - -/// XEP-0203: Delayed Delivery -pub mod delay; - -/// XEP-0221: Data Forms Media Element -pub mod media_element; - -/// XEP-0224: Attention -pub mod attention; - -/// XEP-0231: Bits of Binary -pub mod bob; - -/// XEP-0234: Jingle File Transfer -pub mod jingle_ft; - -/// XEP-0257: Client Certificate Management for SASL EXTERNAL -pub mod cert_management; - -/// XEP-0260: Jingle SOCKS5 Bytestreams Transport Method -pub mod jingle_s5b; - -/// XEP-0261: Jingle In-Band Bytestreams Transport Method -pub mod jingle_ibb; - -/// XEP-0280: Message Carbons -pub mod carbons; - -/// XEP-0293: Jingle RTP Feedback Negotiation -pub mod jingle_rtcp_fb; - -/// XEP-0294: Jingle RTP Header Extensions Negociation -pub mod jingle_rtp_hdrext; - -/// XEP-0297: Stanza Forwarding -pub mod forwarding; - -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub mod hashes; - -/// XEP-0308: Last Message Correction -pub mod message_correct; - -/// XEP-0313: Message Archive Management -pub mod mam; - -/// XEP-0319: Last User Interaction in Presence -pub mod idle; - -/// XEP-0320: Use of DTLS-SRTP in Jingle Sessions -pub mod jingle_dtls_srtp; - -/// XEP-0328: JID Prep -pub mod jid_prep; - -/// XEP-0338: Jingle Grouping Framework -pub mod jingle_grouping; - -/// XEP-0339: Source-Specific Media Attributes in Jingle -pub mod jingle_ssma; - -/// XEP-0352: Client State Indication -pub mod csi; - -/// XEP-0353: Jingle Message Initiation -pub mod jingle_message; - -/// XEP-0359: Unique and Stable Stanza IDs -pub mod stanza_id; - -/// XEP-0369: Mediated Information eXchange (MIX) -pub mod mix; - -/// XEP-0373: OpenPGP for XMPP -pub mod openpgp; - -/// XEP-0380: Explicit Message Encryption -pub mod eme; - -/// XEP-0390: Entity Capabilities 2.0 -pub mod ecaps2; - -/// XEP-0402: Bookmarks 2 (This Time it's Serious) -pub mod bookmarks2; - -/// XEP-0421: Anonymous unique occupant identifiers for MUCs -pub mod occupant_id; - -/// XEP-0441: Message Archive Management Preferences -pub mod mam_prefs; diff --git a/xmpp-parsers/src/mam.rs b/xmpp-parsers/src/mam.rs deleted file mode 100644 index eff0065..0000000 --- a/xmpp-parsers/src/mam.rs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) 2017-2021 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::DataForm; -use crate::forwarding::Forwarded; -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use crate::message::MessagePayload; -use crate::pubsub::NodeName; -use crate::rsm::{SetQuery, SetResult}; - -generate_id!( - /// An identifier matching a result message to the query requesting it. - QueryId -); - -generate_element!( - /// Starts a query to the archive. - Query, "query", MAM, - attributes: [ - /// An optional identifier for matching forwarded messages to this - /// query. - queryid: Option = "queryid", - - /// Must be set to Some when querying a PubSub node’s archive. - node: Option = "node" - ], - children: [ - /// Used for filtering the results. - form: Option = ("x", DATA_FORMS) => DataForm, - - /// Used for paging through results. - set: Option = ("set", RSM) => SetQuery - ] -); - -impl IqGetPayload for Query {} -impl IqSetPayload for Query {} -impl IqResultPayload for Query {} - -generate_element!( - /// The wrapper around forwarded stanzas. - Result_, "result", MAM, - attributes: [ - /// The stanza-id under which the archive stored this stanza. - id: Required = "id", - - /// The same queryid as the one requested in the - /// [query](struct.Query.html). - queryid: Option = "queryid", - ], - children: [ - /// The actual stanza being forwarded. - forwarded: Required = ("forwarded", FORWARD) => Forwarded - ] -); - -impl MessagePayload for Result_ {} - -generate_attribute!( - /// True when the end of a MAM query has been reached. - Complete, - "complete", - bool -); - -generate_element!( - /// Notes the end of a page in a query. - Fin, "fin", MAM, - attributes: [ - /// True when the end of a MAM query has been reached. - complete: Default = "complete", - ], - children: [ - /// Describes the current page, it should contain at least [first] - /// (with an [index]) and [last], and generally [count]. - /// - /// [first]: ../rsm/struct.SetResult.html#structfield.first - /// [index]: ../rsm/struct.SetResult.html#structfield.first_index - /// [last]: ../rsm/struct.SetResult.html#structfield.last - /// [count]: ../rsm/struct.SetResult.html#structfield.count - set: Required = ("set", RSM) => SetResult - ] -); - -impl IqResultPayload for Fin {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use minidom::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(QueryId, 12); - assert_size!(Query, 116); - assert_size!(Result_, 236); - assert_size!(Complete, 1); - assert_size!(Fin, 44); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(QueryId, 24); - assert_size!(Query, 232); - assert_size!(Result_, 456); - assert_size!(Complete, 1); - assert_size!(Fin, 88); - } - - #[test] - fn test_query() { - let elem: Element = "".parse().unwrap(); - Query::try_from(elem).unwrap(); - } - - #[test] - fn test_result() { - #[cfg(not(feature = "component"))] - let elem: Element = r#" - - - - - Hail to thee - - - -"# - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = r#" - - - - - Hail to thee - - - -"#.parse().unwrap(); - Result_::try_from(elem).unwrap(); - } - - #[test] - fn test_fin() { - let elem: Element = r#" - - - 28482-98726-73623 - 09af3-cc343-b409f - - -"# - .parse() - .unwrap(); - Fin::try_from(elem).unwrap(); - } - - #[test] - fn test_query_x() { - let elem: Element = r#" - - - - urn:xmpp:mam:2 - - - juliet@capulet.lit - - - -"# - .parse() - .unwrap(); - Query::try_from(elem).unwrap(); - } - - #[test] - fn test_query_x_set() { - let elem: Element = r#" - - - - urn:xmpp:mam:2 - - - 2010-08-07T00:00:00Z - - - - 10 - - -"# - .parse() - .unwrap(); - Query::try_from(elem).unwrap(); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Query::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in query element."); - } - - #[test] - fn test_serialise_empty() { - let elem: Element = "".parse().unwrap(); - let replace = Query { - queryid: None, - node: None, - form: None, - set: None, - }; - let elem2 = replace.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_serialize_query_with_form() { - let reference: Element = "urn:xmpp:mam:2juliet@capulet.lit" - .parse() - .unwrap(); - - let elem: Element = "urn:xmpp:mam:2juliet@capulet.lit" - .parse() - .unwrap(); - - let form = DataForm::try_from(elem).unwrap(); - - let query = Query { - queryid: None, - node: None, - set: None, - form: Some(form), - }; - let serialized: Element = query.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn test_serialize_result() { - let reference: Element = "" - .parse() - .unwrap(); - - let elem: Element = "" - .parse() - .unwrap(); - - let forwarded = Forwarded::try_from(elem).unwrap(); - - let result = Result_ { - id: String::from("28482-98726-73623"), - queryid: Some(QueryId(String::from("f27"))), - forwarded: forwarded, - }; - let serialized: Element = result.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn test_serialize_fin() { - let reference: Element = "28482-98726-7362309af3-cc343-b409f" - .parse() - .unwrap(); - - let elem: Element = "28482-98726-7362309af3-cc343-b409f" - .parse() - .unwrap(); - - let set = SetResult::try_from(elem).unwrap(); - - let fin = Fin { - set: set, - complete: Complete::default(), - }; - let serialized: Element = fin.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/mam_prefs.rs b/xmpp-parsers/src/mam_prefs.rs deleted file mode 100644 index 04e8663..0000000 --- a/xmpp-parsers/src/mam_prefs.rs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) 2021 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use crate::ns; -use crate::util::error::Error; -use jid::Jid; -use minidom::{Element, Node}; -use std::convert::TryFrom; - -generate_attribute!( - /// Notes the default archiving preference for the user. - DefaultPrefs, "default", { - /// The default is to always log messages in the archive. - Always => "always", - - /// The default is to never log messages in the archive. - Never => "never", - - /// The default is to log messages in the archive only for contacts - /// present in the user’s [roster](../roster/index.html). - Roster => "roster", - } -); - -/// Controls the archiving preferences of the user. -#[derive(Debug, Clone)] -pub struct Prefs { - /// The default preference for JIDs in neither - /// [always](#structfield.always) or [never](#structfield.never) lists. - pub default_: DefaultPrefs, - - /// The set of JIDs for which to always store messages in the archive. - pub always: Vec, - - /// The set of JIDs for which to never store messages in the archive. - pub never: Vec, -} - -impl IqGetPayload for Prefs {} -impl IqSetPayload for Prefs {} -impl IqResultPayload for Prefs {} - -impl TryFrom for Prefs { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "prefs", MAM); - check_no_unknown_attributes!(elem, "prefs", ["default"]); - let mut always = vec![]; - let mut never = vec![]; - for child in elem.children() { - if child.is("always", ns::MAM) { - for jid_elem in child.children() { - if !jid_elem.is("jid", ns::MAM) { - return Err(Error::ParseError("Invalid jid element in always.")); - } - always.push(jid_elem.text().parse()?); - } - } else if child.is("never", ns::MAM) { - for jid_elem in child.children() { - if !jid_elem.is("jid", ns::MAM) { - return Err(Error::ParseError("Invalid jid element in never.")); - } - never.push(jid_elem.text().parse()?); - } - } else { - return Err(Error::ParseError("Unknown child in prefs element.")); - } - } - let default_ = get_attr!(elem, "default", Required); - Ok(Prefs { - default_, - always, - never, - }) - } -} - -fn serialise_jid_list(name: &str, jids: Vec) -> ::std::option::IntoIter { - if jids.is_empty() { - None.into_iter() - } else { - Some( - Element::builder(name, ns::MAM) - .append_all( - jids.into_iter() - .map(|jid| Element::builder("jid", ns::MAM).append(String::from(jid))), - ) - .into(), - ) - .into_iter() - } -} - -impl From for Element { - fn from(prefs: Prefs) -> Element { - Element::builder("prefs", ns::MAM) - .attr("default", prefs.default_) - .append_all(serialise_jid_list("always", prefs.always)) - .append_all(serialise_jid_list("never", prefs.never)) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use jid::BareJid; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(DefaultPrefs, 1); - assert_size!(Prefs, 28); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(DefaultPrefs, 1); - assert_size!(Prefs, 56); - } - - #[test] - fn test_prefs_get() { - let elem: Element = "" - .parse() - .unwrap(); - let prefs = Prefs::try_from(elem).unwrap(); - assert!(prefs.always.is_empty()); - assert!(prefs.never.is_empty()); - - let elem: Element = r#" - - - - -"# - .parse() - .unwrap(); - let prefs = Prefs::try_from(elem).unwrap(); - assert!(prefs.always.is_empty()); - assert!(prefs.never.is_empty()); - } - - #[test] - fn test_prefs_result() { - let elem: Element = r#" - - - romeo@montague.lit - - - montague@montague.lit - - -"# - .parse() - .unwrap(); - let prefs = Prefs::try_from(elem).unwrap(); - assert_eq!(prefs.always, [BareJid::new("romeo", "montague.lit")]); - assert_eq!(prefs.never, [BareJid::new("montague", "montague.lit")]); - - let elem2 = Element::from(prefs.clone()); - println!("{:?}", elem2); - let prefs2 = Prefs::try_from(elem2).unwrap(); - assert_eq!(prefs.default_, prefs2.default_); - assert_eq!(prefs.always, prefs2.always); - assert_eq!(prefs.never, prefs2.never); - } -} diff --git a/xmpp-parsers/src/media_element.rs b/xmpp-parsers/src/media_element.rs deleted file mode 100644 index e1d9338..0000000 --- a/xmpp-parsers/src/media_element.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::util::helpers::TrimmedPlainText; - -generate_element!( - /// Represents an URI used in a media element. - URI, "uri", MEDIA_ELEMENT, - attributes: [ - /// The MIME type of the URI referenced. - /// - /// See the [IANA MIME Media Types Registry][1] for a list of - /// registered types, but unregistered or yet-to-be-registered are - /// accepted too. - /// - /// [1]: https://www.iana.org/assignments/media-types/media-types.xhtml - type_: Required = "type" - ], - text: ( - /// The actual URI contained. - uri: TrimmedPlainText - ) -); - -generate_element!( - /// References a media element, to be used in [data - /// forms](../data_forms/index.html). - MediaElement, "media", MEDIA_ELEMENT, - attributes: [ - /// The recommended display width in pixels. - width: Option = "width", - - /// The recommended display height in pixels. - height: Option = "height" - ], - children: [ - /// A list of URIs referencing this media. - uris: Vec = ("uri", MEDIA_ELEMENT) => URI - ] -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::data_forms::DataForm; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(URI, 24); - assert_size!(MediaElement, 28); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(URI, 48); - assert_size!(MediaElement, 56); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - let media = MediaElement::try_from(elem).unwrap(); - assert!(media.width.is_none()); - assert!(media.height.is_none()); - assert!(media.uris.is_empty()); - } - - #[test] - fn test_width_height() { - let elem: Element = "" - .parse() - .unwrap(); - let media = MediaElement::try_from(elem).unwrap(); - assert_eq!(media.width.unwrap(), 32); - assert_eq!(media.height.unwrap(), 32); - } - - #[test] - fn test_uri() { - let elem: Element = "https://example.org/".parse().unwrap(); - let media = MediaElement::try_from(elem).unwrap(); - assert_eq!(media.uris.len(), 1); - assert_eq!(media.uris[0].type_, "text/html"); - assert_eq!(media.uris[0].uri, "https://example.org/"); - } - - #[test] - fn test_invalid_width_height() { - let elem: Element = "" - .parse() - .unwrap(); - let error = MediaElement::try_from(elem).unwrap_err(); - let error = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(error.to_string(), "cannot parse integer from empty string"); - - let elem: Element = "" - .parse() - .unwrap(); - let error = MediaElement::try_from(elem).unwrap_err(); - let error = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(error.to_string(), "invalid digit found in string"); - - let elem: Element = "" - .parse() - .unwrap(); - let error = MediaElement::try_from(elem).unwrap_err(); - let error = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(error.to_string(), "cannot parse integer from empty string"); - - let elem: Element = "" - .parse() - .unwrap(); - let error = MediaElement::try_from(elem).unwrap_err(); - let error = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(error.to_string(), "invalid digit found in string"); - } - - #[test] - fn test_unknown_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = MediaElement::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in media element."); - } - - #[test] - fn test_bad_uri() { - let elem: Element = - "https://example.org/" - .parse() - .unwrap(); - let error = MediaElement::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'type' missing."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = MediaElement::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "URI missing in uri."); - } - - #[test] - fn test_xep_ex1() { - let elem: Element = r#" - - - http://victim.example.com/challenges/speech.wav?F3A6292C - - - cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org - - - http://victim.example.com/challenges/speech.mp3?F3A6292C - -"# - .parse() - .unwrap(); - let media = MediaElement::try_from(elem).unwrap(); - assert!(media.width.is_none()); - assert!(media.height.is_none()); - assert_eq!(media.uris.len(), 3); - assert_eq!(media.uris[0].type_, "audio/x-wav"); - assert_eq!( - media.uris[0].uri, - "http://victim.example.com/challenges/speech.wav?F3A6292C" - ); - assert_eq!(media.uris[1].type_, "audio/ogg; codecs=speex"); - assert_eq!( - media.uris[1].uri, - "cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org" - ); - assert_eq!(media.uris[2].type_, "audio/mpeg"); - assert_eq!( - media.uris[2].uri, - "http://victim.example.com/challenges/speech.mp3?F3A6292C" - ); - } - - #[test] - fn test_xep_ex2() { - let elem: Element = r#" - - [ ... ] - - - - http://www.victim.com/challenges/ocr.jpeg?F3A6292C - - - cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org - - - - [ ... ] -"# - .parse() - .unwrap(); - let form = DataForm::try_from(elem).unwrap(); - assert_eq!(form.fields.len(), 1); - assert_eq!(form.fields[0].var, "ocr"); - assert_eq!(form.fields[0].media[0].width, Some(290)); - assert_eq!(form.fields[0].media[0].height, Some(80)); - assert_eq!(form.fields[0].media[0].uris[0].type_, "image/jpeg"); - assert_eq!( - form.fields[0].media[0].uris[0].uri, - "http://www.victim.com/challenges/ocr.jpeg?F3A6292C" - ); - assert_eq!(form.fields[0].media[0].uris[1].type_, "image/jpeg"); - assert_eq!( - form.fields[0].media[0].uris[1].uri, - "cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org" - ); - } -} diff --git a/xmpp-parsers/src/message.rs b/xmpp-parsers/src/message.rs deleted file mode 100644 index 281543a..0000000 --- a/xmpp-parsers/src/message.rs +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::collections::BTreeMap; -use std::convert::TryFrom; - -/// Should be implemented on every known payload of a ``. -pub trait MessagePayload: TryFrom + Into {} - -generate_attribute!( - /// The type of a message. - MessageType, "type", { - /// Standard instant messaging message. - Chat => "chat", - - /// Notifies that an error happened. - Error => "error", - - /// Standard group instant messaging message. - Groupchat => "groupchat", - - /// Used by servers to notify users when things happen. - Headline => "headline", - - /// This is an email-like message, it usually contains a - /// [subject](struct.Subject.html). - Normal => "normal", - }, Default = Normal -); - -type Lang = String; - -generate_elem_id!( - /// Represents one `` element, that is the free form text content of - /// a message. - Body, - "body", - DEFAULT_NS -); - -generate_elem_id!( - /// Defines the subject of a room, or of an email-like normal message. - Subject, - "subject", - DEFAULT_NS -); - -generate_elem_id!( - /// A thread identifier, so that other people can specify to which message - /// they are replying. - Thread, - "thread", - DEFAULT_NS -); - -/// The main structure representing the `` stanza. -#[derive(Debug, Clone, PartialEq)] -pub struct Message { - /// The JID emitting this stanza. - pub from: Option, - - /// The recipient of this stanza. - pub to: Option, - - /// The @id attribute of this stanza, which is required in order to match a - /// request with its response. - pub id: Option, - - /// The type of this message. - pub type_: MessageType, - - /// A list of bodies, sorted per language. Use - /// [get_best_body()](#method.get_best_body) to access them on reception. - pub bodies: BTreeMap, - - /// A list of subjects, sorted per language. Use - /// [get_best_subject()](#method.get_best_subject) to access them on - /// reception. - pub subjects: BTreeMap, - - /// An optional thread identifier, so that other people can reply directly - /// to this message. - pub thread: Option, - - /// A list of the extension payloads contained in this stanza. - pub payloads: Vec, -} - -impl Message { - /// Creates a new `` stanza for the given recipient. - pub fn new>>(to: J) -> Message { - Message { - from: None, - to: to.into(), - id: None, - type_: MessageType::Chat, - bodies: BTreeMap::new(), - subjects: BTreeMap::new(), - thread: None, - payloads: vec![], - } - } - - fn get_best<'a, T>( - map: &'a BTreeMap, - preferred_langs: Vec<&str>, - ) -> Option<(Lang, &'a T)> { - if map.is_empty() { - return None; - } - for lang in preferred_langs { - if let Some(value) = map.get(lang) { - return Some((Lang::from(lang), value)); - } - } - if let Some(value) = map.get("") { - return Some((Lang::new(), value)); - } - map.iter().map(|(lang, value)| (lang.clone(), value)).next() - } - - /// Returns the best matching body from a list of languages. - /// - /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English - /// body without an xml:lang attribute, and you pass ["fr", "en"] as your preferred languages, - /// `Some(("fr", the_second_body))` will be returned. - /// - /// If no body matches, an undefined body will be returned. - pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> { - Message::get_best::(&self.bodies, preferred_langs) - } - - /// Returns the best matching subject from a list of languages. - /// - /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English - /// subject without an xml:lang attribute, and you pass ["fr", "en"] as your preferred - /// languages, `Some(("fr", the_second_subject))` will be returned. - /// - /// If no subject matches, an undefined subject will be returned. - pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> { - Message::get_best::(&self.subjects, preferred_langs) - } -} - -impl TryFrom for Message { - type Error = Error; - - fn try_from(root: Element) -> Result { - check_self!(root, "message", DEFAULT_NS); - let from = get_attr!(root, "from", Option); - let to = get_attr!(root, "to", Option); - let id = get_attr!(root, "id", Option); - let type_ = get_attr!(root, "type", Default); - let mut bodies = BTreeMap::new(); - let mut subjects = BTreeMap::new(); - let mut thread = None; - let mut payloads = vec![]; - for elem in root.children() { - if elem.is("body", ns::DEFAULT_NS) { - check_no_children!(elem, "body"); - let lang = get_attr!(elem, "xml:lang", Default); - let body = Body(elem.text()); - if bodies.insert(lang, body).is_some() { - return Err(Error::ParseError( - "Body element present twice for the same xml:lang.", - )); - } - } else if elem.is("subject", ns::DEFAULT_NS) { - check_no_children!(elem, "subject"); - let lang = get_attr!(elem, "xml:lang", Default); - let subject = Subject(elem.text()); - if subjects.insert(lang, subject).is_some() { - return Err(Error::ParseError( - "Subject element present twice for the same xml:lang.", - )); - } - } else if elem.is("thread", ns::DEFAULT_NS) { - if thread.is_some() { - return Err(Error::ParseError("Thread element present twice.")); - } - check_no_children!(elem, "thread"); - thread = Some(Thread(elem.text())); - } else { - payloads.push(elem.clone()) - } - } - Ok(Message { - from, - to, - id, - type_, - bodies, - subjects, - thread, - payloads, - }) - } -} - -impl From for Element { - fn from(message: Message) -> Element { - Element::builder("message", ns::DEFAULT_NS) - .attr("from", message.from) - .attr("to", message.to) - .attr("id", message.id) - .attr("type", message.type_) - .append_all(message.subjects.into_iter().map(|(lang, subject)| { - let mut subject = Element::from(subject); - subject.set_attr( - "xml:lang", - match lang.as_ref() { - "" => None, - lang => Some(lang), - }, - ); - subject - })) - .append_all(message.bodies.into_iter().map(|(lang, body)| { - let mut body = Element::from(body); - body.set_attr( - "xml:lang", - match lang.as_ref() { - "" => None, - lang => Some(lang), - }, - ); - body - })) - .append_all(message.payloads.into_iter()) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use jid::BareJid; - use std::str::FromStr; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(MessageType, 1); - assert_size!(Body, 12); - assert_size!(Subject, 12); - assert_size!(Thread, 12); - assert_size!(Message, 144); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(MessageType, 1); - assert_size!(Body, 24); - assert_size!(Subject, 24); - assert_size!(Thread, 24); - assert_size!(Message, 288); - } - - #[test] - fn test_simple() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let message = Message::try_from(elem).unwrap(); - assert_eq!(message.from, None); - assert_eq!(message.to, None); - assert_eq!(message.id, None); - assert_eq!(message.type_, MessageType::Normal); - assert!(message.payloads.is_empty()); - } - - #[test] - fn test_serialise() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let mut message = Message::new(None); - message.type_ = MessageType::Normal; - let elem2 = message.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_body() { - #[cfg(not(feature = "component"))] - let elem: Element = "Hello world!".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "Hello world!".parse().unwrap(); - let elem1 = elem.clone(); - let message = Message::try_from(elem).unwrap(); - assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap()); - - { - let (lang, body) = message.get_best_body(vec!["en"]).unwrap(); - assert_eq!(lang, ""); - assert_eq!(body, &Body::from_str("Hello world!").unwrap()); - } - - let elem2 = message.into(); - assert_eq!(elem1, elem2); - } - - #[test] - fn test_serialise_body() { - #[cfg(not(feature = "component"))] - let elem: Element = "Hello world!".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "Hello world!".parse().unwrap(); - let mut message = Message::new(Jid::Bare(BareJid::new("coucou", "example.org"))); - message - .bodies - .insert(String::from(""), Body::from_str("Hello world!").unwrap()); - let elem2 = message.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_subject() { - #[cfg(not(feature = "component"))] - let elem: Element = "Hello world!".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "Hello world!".parse().unwrap(); - let elem1 = elem.clone(); - let message = Message::try_from(elem).unwrap(); - assert_eq!( - message.subjects[""], - Subject::from_str("Hello world!").unwrap() - ); - - { - let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap(); - assert_eq!(lang, ""); - assert_eq!(subject, &Subject::from_str("Hello world!").unwrap()); - } - - let elem2 = message.into(); - assert_eq!(elem1, elem2); - } - - #[test] - fn get_best_body() { - #[cfg(not(feature = "component"))] - let elem: Element = "Hallo Welt!Salut le monde !Hello world!".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "Hello world!".parse().unwrap(); - let message = Message::try_from(elem).unwrap(); - - // Tests basic feature. - { - let (lang, body) = message.get_best_body(vec!["fr"]).unwrap(); - assert_eq!(lang, "fr"); - assert_eq!(body, &Body::from_str("Salut le monde !").unwrap()); - } - - // Tests order. - { - let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap(); - assert_eq!(lang, "de"); - assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap()); - } - - // Tests fallback. - { - let (lang, body) = message.get_best_body(vec![]).unwrap(); - assert_eq!(lang, ""); - assert_eq!(body, &Body::from_str("Hello world!").unwrap()); - } - - // Tests fallback. - { - let (lang, body) = message.get_best_body(vec!["ja"]).unwrap(); - assert_eq!(lang, ""); - assert_eq!(body, &Body::from_str("Hello world!").unwrap()); - } - - let message = Message::new(None); - - // Tests without a body. - assert_eq!(message.get_best_body(vec!("ja")), None); - } - - #[test] - fn test_attention() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "".parse().unwrap(); - let elem1 = elem.clone(); - let message = Message::try_from(elem).unwrap(); - let elem2 = message.into(); - assert_eq!(elem1, elem2); - } -} diff --git a/xmpp-parsers/src/message_correct.rs b/xmpp-parsers/src/message_correct.rs deleted file mode 100644 index 374900f..0000000 --- a/xmpp-parsers/src/message_correct.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; - -generate_element!( - /// Defines that the message containing this payload should replace a - /// previous message, identified by the id. - Replace, "replace", MESSAGE_CORRECT, - attributes: [ - /// The 'id' attribute of the message getting corrected. - id: Required = "id", - ] -); - -impl MessagePayload for Replace {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Replace, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Replace, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - Replace::try_from(elem).unwrap(); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Replace::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in replace element."); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Replace::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in replace element."); - } - - #[test] - fn test_invalid_id() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Replace::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'id' missing."); - } - - #[test] - fn test_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let replace = Replace { - id: String::from("coucou"), - }; - let elem2 = replace.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/mix.rs b/xmpp-parsers/src/mix.rs deleted file mode 100644 index 7043f8a..0000000 --- a/xmpp-parsers/src/mix.rs +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright (c) 2020 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// TODO: validate nicks by applying the “nickname” profile of the PRECIS OpaqueString class, as -// defined in RFC 7700. - -use crate::iq::{IqResultPayload, IqSetPayload}; -use crate::message::MessagePayload; -use crate::pubsub::{NodeName, PubSubPayload}; -use jid::BareJid; - -generate_id!( - /// The identifier a participant receives when joining a channel. - ParticipantId -); - -impl ParticipantId { - /// Create a new ParticipantId. - pub fn new>(participant: P) -> ParticipantId { - ParticipantId(participant.into()) - } -} - -generate_id!( - /// A MIX channel identifier. - ChannelId -); - -generate_element!( - /// Represents a participant in a MIX channel, usually returned on the - /// urn:xmpp:mix:nodes:participants PubSub node. - Participant, "participant", MIX_CORE, - children: [ - /// The nick of this participant. - nick: Required = ("nick", MIX_CORE) => String, - - /// The bare JID of this participant. - // TODO: should be a BareJid! - jid: Required = ("jid", MIX_CORE) => String - ] -); - -impl PubSubPayload for Participant {} - -impl Participant { - /// Create a new MIX participant. - pub fn new, N: Into>(jid: J, nick: N) -> Participant { - Participant { - nick: nick.into(), - jid: jid.into(), - } - } -} - -generate_element!( - /// A node to subscribe to. - Subscribe, "subscribe", MIX_CORE, - attributes: [ - /// The PubSub node to subscribe to. - node: Required = "node", - ] -); - -impl Subscribe { - /// Create a new Subscribe element. - pub fn new>(node: N) -> Subscribe { - Subscribe { - node: NodeName(node.into()), - } - } -} - -generate_element!( - /// A request from a user’s server to join a MIX channel. - Join, "join", MIX_CORE, - attributes: [ - /// The participant identifier returned by the MIX service on successful join. - id: Option = "id", - ], - children: [ - /// The nick requested by the user or set by the service. - nick: Required = ("nick", MIX_CORE) => String, - - /// Which MIX nodes to subscribe to. - subscribes: Vec = ("subscribe", MIX_CORE) => Subscribe - ] -); - -impl IqSetPayload for Join {} -impl IqResultPayload for Join {} - -impl Join { - /// Create a new Join element. - pub fn from_nick_and_nodes>(nick: N, nodes: &[&str]) -> Join { - let subscribes = nodes - .into_iter() - .cloned() - .map(|n| Subscribe::new(n)) - .collect(); - Join { - id: None, - nick: nick.into(), - subscribes, - } - } - - /// Sets the JID on this update-subscription. - pub fn with_id>(mut self, id: I) -> Self { - self.id = Some(ParticipantId(id.into())); - self - } -} - -generate_element!( - /// Update a given subscription. - UpdateSubscription, "update-subscription", MIX_CORE, - attributes: [ - /// The JID of the user to be affected. - // TODO: why is it not a participant id instead? - jid: Option = "jid", - ], - children: [ - /// The list of additional nodes to subscribe to. - // TODO: what happens when we are already subscribed? Also, how do we unsubscribe from - // just one? - subscribes: Vec = ("subscribe", MIX_CORE) => Subscribe - ] -); - -impl IqSetPayload for UpdateSubscription {} -impl IqResultPayload for UpdateSubscription {} - -impl UpdateSubscription { - /// Create a new UpdateSubscription element. - pub fn from_nodes(nodes: &[&str]) -> UpdateSubscription { - let subscribes = nodes - .into_iter() - .cloned() - .map(|n| Subscribe::new(n)) - .collect(); - UpdateSubscription { - jid: None, - subscribes, - } - } - - /// Sets the JID on this update-subscription. - pub fn with_jid(mut self, jid: BareJid) -> Self { - self.jid = Some(jid); - self - } -} - -generate_empty_element!( - /// Request to leave a given MIX channel. It will automatically unsubscribe the user from all - /// nodes on this channel. - Leave, - "leave", - MIX_CORE -); - -impl IqSetPayload for Leave {} -impl IqResultPayload for Leave {} - -generate_element!( - /// A request to change the user’s nick. - SetNick, "setnick", MIX_CORE, - children: [ - /// The new requested nick. - nick: Required = ("nick", MIX_CORE) => String - ] -); - -impl IqSetPayload for SetNick {} -impl IqResultPayload for SetNick {} - -impl SetNick { - /// Create a new SetNick element. - pub fn new>(nick: N) -> SetNick { - SetNick { nick: nick.into() } - } -} - -generate_element!( - /// Message payload describing who actually sent the message, since unlike in MUC, all messages - /// are sent from the channel’s JID. - Mix, "mix", MIX_CORE, - children: [ - /// The nick of the user who said something. - nick: Required = ("nick", MIX_CORE) => String, - - /// The JID of the user who said something. - // TODO: should be a BareJid! - jid: Required = ("jid", MIX_CORE) => String - ] -); - -impl MessagePayload for Mix {} - -impl Mix { - /// Create a new Mix element. - pub fn new, J: Into>(nick: N, jid: J) -> Mix { - Mix { - nick: nick.into(), - jid: jid.into(), - } - } -} - -generate_element!( - /// Create a new MIX channel. - Create, "create", MIX_CORE, - attributes: [ - /// The requested channel identifier. - channel: Option = "channel", - ] -); - -impl IqSetPayload for Create {} -impl IqResultPayload for Create {} - -impl Create { - /// Create a new ad-hoc Create element. - pub fn new() -> Create { - Create { channel: None } - } - - /// Create a new Create element with a channel identifier. - pub fn from_channel_id>(channel: C) -> Create { - Create { - channel: Some(ChannelId(channel.into())), - } - } -} - -generate_element!( - /// Destroy a given MIX channel. - Destroy, "destroy", MIX_CORE, - attributes: [ - /// The channel identifier to be destroyed. - channel: Required = "channel", - ] -); - -// TODO: section 7.3.4, example 33, doesn’t mirror the in the iq result unlike every -// other section so far. -impl IqSetPayload for Destroy {} - -impl Destroy { - /// Create a new Destroy element. - pub fn new>(channel: C) -> Destroy { - Destroy { - channel: ChannelId(channel.into()), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[test] - fn participant() { - let elem: Element = "foo@barcoucou" - .parse() - .unwrap(); - let participant = Participant::try_from(elem).unwrap(); - assert_eq!(participant.nick, "coucou"); - assert_eq!(participant.jid, "foo@bar"); - } - - #[test] - fn join() { - let elem: Element = "coucou" - .parse() - .unwrap(); - let join = Join::try_from(elem).unwrap(); - assert_eq!(join.nick, "coucou"); - assert_eq!(join.id, None); - assert_eq!(join.subscribes.len(), 2); - assert_eq!(join.subscribes[0].node.0, "urn:xmpp:mix:nodes:messages"); - assert_eq!(join.subscribes[1].node.0, "urn:xmpp:mix:nodes:info"); - } - - #[test] - fn update_subscription() { - let elem: Element = "" - .parse() - .unwrap(); - let update_subscription = UpdateSubscription::try_from(elem).unwrap(); - assert_eq!(update_subscription.jid, None); - assert_eq!(update_subscription.subscribes.len(), 1); - assert_eq!( - update_subscription.subscribes[0].node.0, - "urn:xmpp:mix:nodes:participants" - ); - } - - #[test] - fn leave() { - let elem: Element = "".parse().unwrap(); - Leave::try_from(elem).unwrap(); - } - - #[test] - fn setnick() { - let elem: Element = "coucou" - .parse() - .unwrap(); - let setnick = SetNick::try_from(elem).unwrap(); - assert_eq!(setnick.nick, "coucou"); - } - - #[test] - fn message_mix() { - let elem: Element = - "foo@barcoucou" - .parse() - .unwrap(); - let mix = Mix::try_from(elem).unwrap(); - assert_eq!(mix.nick, "coucou"); - assert_eq!(mix.jid, "foo@bar"); - } - - #[test] - fn create() { - let elem: Element = "" - .parse() - .unwrap(); - let create = Create::try_from(elem).unwrap(); - assert_eq!(create.channel.unwrap().0, "coucou"); - - let elem: Element = "".parse().unwrap(); - let create = Create::try_from(elem).unwrap(); - assert_eq!(create.channel, None); - } - - #[test] - fn destroy() { - let elem: Element = "" - .parse() - .unwrap(); - let destroy = Destroy::try_from(elem).unwrap(); - assert_eq!(destroy.channel.0, "coucou"); - } - - #[test] - fn serialise() { - let elem: Element = Join::from_nick_and_nodes("coucou", &["foo", "bar"]).into(); - let xml = String::from(&elem); - assert_eq!(xml, "coucou"); - - let elem: Element = UpdateSubscription::from_nodes(&["foo", "bar"]).into(); - let xml = String::from(&elem); - assert_eq!(xml, ""); - - let elem: Element = Leave.into(); - let xml = String::from(&elem); - assert_eq!(xml, ""); - - let elem: Element = SetNick::new("coucou").into(); - let xml = String::from(&elem); - assert_eq!( - xml, - "coucou" - ); - - let elem: Element = Mix::new("coucou", "coucou@example").into(); - let xml = String::from(&elem); - assert_eq!( - xml, - "coucoucoucou@example" - ); - - let elem: Element = Create::new().into(); - let xml = String::from(&elem); - assert_eq!(xml, ""); - - let elem: Element = Create::from_channel_id("coucou").into(); - let xml = String::from(&elem); - assert_eq!( - xml, - "" - ); - - let elem: Element = Destroy::new("coucou").into(); - let xml = String::from(&elem); - assert_eq!( - xml, - "" - ); - } -} diff --git a/xmpp-parsers/src/mood.rs b/xmpp-parsers/src/mood.rs deleted file mode 100644 index 4270c58..0000000 --- a/xmpp-parsers/src/mood.rs +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -generate_element_enum!( - /// Enum representing all of the possible values of the XEP-0107 moods. - MoodEnum, "mood", MOOD, { - /// Impressed with fear or apprehension; in fear; apprehensive. - Afraid => "afraid", - - /// Astonished; confounded with fear, surprise or wonder. - Amazed => "amazed", - - /// Inclined to love; having a propensity to love, or to sexual enjoyment; loving, fond, affectionate, passionate, lustful, sexual, etc. - Amorous => "amorous", - - /// Displaying or feeling anger, i.e., a strong feeling of displeasure, hostility or antagonism towards someone or something, usually combined with an urge to harm. - Angry => "angry", - - /// To be disturbed or irritated, especially by continued or repeated acts. - Annoyed => "annoyed", - - /// Full of anxiety or disquietude; greatly concerned or solicitous, esp. respecting something future or unknown; being in painful suspense. - Anxious => "anxious", - - /// To be stimulated in one's feelings, especially to be sexually stimulated. - Aroused => "aroused", - - /// Feeling shame or guilt. - Ashamed => "ashamed", - - /// Suffering from boredom; uninterested, without attention. - Bored => "bored", - - /// Strong in the face of fear; courageous. - Brave => "brave", - - /// Peaceful, quiet. - Calm => "calm", - - /// Taking care or caution; tentative. - Cautious => "cautious", - - /// Feeling the sensation of coldness, especially to the point of discomfort. - Cold => "cold", - - /// Feeling very sure of or positive about something, especially about one's own capabilities. - Confident => "confident", - - /// Chaotic, jumbled or muddled. - Confused => "confused", - - /// Feeling introspective or thoughtful. - Contemplative => "contemplative", - - /// Pleased at the satisfaction of a want or desire; satisfied. - Contented => "contented", - - /// Grouchy, irritable; easily upset. - Cranky => "cranky", - - /// Feeling out of control; feeling overly excited or enthusiastic. - Crazy => "crazy", - - /// Feeling original, expressive, or imaginative. - Creative => "creative", - - /// Inquisitive; tending to ask questions, investigate, or explore. - Curious => "curious", - - /// Feeling sad and dispirited. - Dejected => "dejected", - - /// Severely despondent and unhappy. - Depressed => "depressed", - - /// Defeated of expectation or hope; let down. - Disappointed => "disappointed", - - /// Filled with disgust; irritated and out of patience. - Disgusted => "disgusted", - - /// Feeling a sudden or complete loss of courage in the face of trouble or danger. - Dismayed => "dismayed", - - /// Having one's attention diverted; preoccupied. - Distracted => "distracted", - - /// Having a feeling of shameful discomfort. - Embarrassed => "embarrassed", - - /// Feeling pain by the excellence or good fortune of another. - Envious => "envious", - - /// Having great enthusiasm. - Excited => "excited", - - /// In the mood for flirting. - Flirtatious => "flirtatious", - - /// Suffering from frustration; dissatisfied, agitated, or discontented because one is unable to perform an action or fulfill a desire. - Frustrated => "frustrated", - - /// Feeling appreciation or thanks. - Grateful => "grateful", - - /// Feeling very sad about something, especially something lost; mournful; sorrowful. - Grieving => "grieving", - - /// Unhappy and irritable. - Grumpy => "grumpy", - - /// Feeling responsible for wrongdoing; feeling blameworthy. - Guilty => "guilty", - - /// Experiencing the effect of favourable fortune; having the feeling arising from the consciousness of well-being or of enjoyment; enjoying good of any kind, as peace, tranquillity, comfort; contented; joyous. - Happy => "happy", - - /// Having a positive feeling, belief, or expectation that something wished for can or will happen. - Hopeful => "hopeful", - - /// Feeling the sensation of heat, especially to the point of discomfort. - Hot => "hot", - - /// Having or showing a modest or low estimate of one's own importance; feeling lowered in dignity or importance. - Humbled => "humbled", - - /// Feeling deprived of dignity or self-respect. - Humiliated => "humiliated", - - /// Having a physical need for food. - Hungry => "hungry", - - /// Wounded, injured, or pained, whether physically or emotionally. - Hurt => "hurt", - - /// Favourably affected by something or someone. - Impressed => "impressed", - - /// Feeling amazement at something or someone; or feeling a combination of fear and reverence. - InAwe => "in_awe", - - /// Feeling strong affection, care, liking, or attraction.. - InLove => "in_love", - - /// Showing anger or indignation, especially at something unjust or wrong. - Indignant => "indignant", - - /// Showing great attention to something or someone; having or showing interest. - Interested => "interested", - - /// Under the influence of alcohol; drunk. - Intoxicated => "intoxicated", - - /// Feeling as if one cannot be defeated, overcome or denied. - Invincible => "invincible", - - /// Fearful of being replaced in position or affection. - Jealous => "jealous", - - /// Feeling isolated, empty, or abandoned. - Lonely => "lonely", - - /// Unable to find one's way, either physically or emotionally. - Lost => "lost", - - /// Feeling as if one will be favored by luck. - Lucky => "lucky", - - /// Causing or intending to cause intentional harm; bearing ill will towards another; cruel; malicious. - Mean => "mean", - - /// Given to sudden or frequent changes of mind or feeling; temperamental. - Moody => "moody", - - /// Easily agitated or alarmed; apprehensive or anxious. - Nervous => "nervous", - - /// Not having a strong mood or emotional state. - Neutral => "neutral", - - /// Feeling emotionally hurt, displeased, or insulted. - Offended => "offended", - - /// Feeling resentful anger caused by an extremely violent or vicious attack, or by an offensive, immoral, or indecent act. - Outraged => "outraged", - - /// Interested in play; fun, recreational, unserious, lighthearted; joking, silly. - Playful => "playful", - - /// Feeling a sense of one's own worth or accomplishment. - Proud => "proud", - - /// Having an easy-going mood; not stressed; calm. - Relaxed => "relaxed", - - /// Feeling uplifted because of the removal of stress or discomfort. - Relieved => "relieved", - - /// Feeling regret or sadness for doing something wrong. - Remorseful => "remorseful", - - /// Without rest; unable to be still or quiet; uneasy; continually moving. - Restless => "restless", - - /// Feeling sorrow; sorrowful, mournful. - Sad => "sad", - - /// Mocking and ironical. - Sarcastic => "sarcastic", - - /// Pleased at the fulfillment of a need or desire. - Satisfied => "satisfied", - - /// Without humor or expression of happiness; grave in manner or disposition; earnest; thoughtful; solemn. - Serious => "serious", - - /// Surprised, startled, confused, or taken aback. - Shocked => "shocked", - - /// Feeling easily frightened or scared; timid; reserved or coy. - Shy => "shy", - - /// Feeling in poor health; ill. - Sick => "sick", - - /// Feeling the need for sleep. - Sleepy => "sleepy", - - /// Acting without planning; natural; impulsive. - Spontaneous => "spontaneous", - - /// Suffering emotional pressure. - Stressed => "stressed", - - /// Capable of producing great physical force; or, emotionally forceful, able, determined, unyielding. - Strong => "strong", - - /// Experiencing a feeling caused by something unexpected. - Surprised => "surprised", - - /// Showing appreciation or gratitude. - Thankful => "thankful", - - /// Feeling the need to drink. - Thirsty => "thirsty", - - /// In need of rest or sleep. - Tired => "tired", - - /// [Feeling any emotion not defined here.] - Undefined => "undefined", - - /// Lacking in force or ability, either physical or emotional. - Weak => "weak", - - /// Thinking about unpleasant things that have happened or that might happen; feeling afraid and unhappy. - Worried => "worried", - } -); - -generate_elem_id!( - /// Free-form text description of the mood. - Text, - "text", - MOOD -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(MoodEnum, 1); - assert_size!(Text, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(MoodEnum, 1); - assert_size!(Text, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let mood = MoodEnum::try_from(elem).unwrap(); - assert_eq!(mood, MoodEnum::Happy); - } - - #[test] - fn test_text() { - let elem: Element = "Yay!" - .parse() - .unwrap(); - let elem2 = elem.clone(); - let text = Text::try_from(elem).unwrap(); - assert_eq!(text.0, String::from("Yay!")); - - let elem3 = text.into(); - assert_eq!(elem2, elem3); - } -} diff --git a/xmpp-parsers/src/muc/mod.rs b/xmpp-parsers/src/muc/mod.rs deleted file mode 100644 index 5875e3b..0000000 --- a/xmpp-parsers/src/muc/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2017 Maxime “pep” Buquet -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/// The http://jabber.org/protocol/muc protocol. -pub mod muc; - -/// The http://jabber.org/protocol/muc#user protocol. -pub mod user; - -pub use self::muc::Muc; -pub use self::user::MucUser; diff --git a/xmpp-parsers/src/muc/muc.rs b/xmpp-parsers/src/muc/muc.rs deleted file mode 100644 index edce303..0000000 --- a/xmpp-parsers/src/muc/muc.rs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) 2017 Maxime “pep” Buquet -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::date::DateTime; -use crate::presence::PresencePayload; - -generate_element!( - /// Represents the query for messages before our join. - #[derive(Default)] - History, "history", MUC, - attributes: [ - /// How many characters of history to send, in XML characters. - maxchars: Option = "maxchars", - - /// How many messages to send. - maxstanzas: Option = "maxstanzas", - - /// Only send messages received in these last seconds. - seconds: Option = "seconds", - - /// Only send messages after this date. - since: Option = "since", - ] -); - -impl History { - /// Create a new empty history element. - pub fn new() -> Self { - History::default() - } - - /// Set how many characters of history to send. - pub fn with_maxchars(mut self, maxchars: u32) -> Self { - self.maxchars = Some(maxchars); - self - } - - /// Set how many messages to send. - pub fn with_maxstanzas(mut self, maxstanzas: u32) -> Self { - self.maxstanzas = Some(maxstanzas); - self - } - - /// Only send messages received in these last seconds. - pub fn with_seconds(mut self, seconds: u32) -> Self { - self.seconds = Some(seconds); - self - } - - /// Only send messages received since this date. - pub fn with_since(mut self, since: DateTime) -> Self { - self.since = Some(since); - self - } -} - -generate_element!( - /// Represents a room join request. - #[derive(Default)] - Muc, "x", MUC, children: [ - /// Password to use when the room is protected by a password. - password: Option = ("password", MUC) => String, - - /// Controls how much and how old we want to receive history on join. - history: Option = ("history", MUC) => History - ] -); - -impl PresencePayload for Muc {} - -impl Muc { - /// Create a new MUC join element. - pub fn new() -> Self { - Muc::default() - } - - /// Join a room with this password. - pub fn with_password(mut self, password: String) -> Self { - self.password = Some(password); - self - } - - /// Join a room with only that much history. - pub fn with_history(mut self, history: History) -> Self { - self.history = Some(history); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - use std::str::FromStr; - - #[test] - fn test_muc_simple() { - let elem: Element = "" - .parse() - .unwrap(); - Muc::try_from(elem).unwrap(); - } - - #[test] - fn test_muc_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Muc::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in x element."); - } - - #[test] - fn test_muc_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let muc = Muc { - password: None, - history: None, - }; - let elem2 = muc.into(); - assert_eq!(elem, elem2); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_muc_invalid_attribute() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Muc::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in x element."); - } - - #[test] - fn test_muc_simple_password() { - let elem: Element = - "coucou" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let muc = Muc::try_from(elem).unwrap(); - assert_eq!(muc.password, Some("coucou".to_owned())); - - let elem2 = Element::from(muc); - assert_eq!(elem1, elem2); - } - - #[test] - fn history() { - let elem: Element = " - - - " - .parse() - .unwrap(); - let muc = Muc::try_from(elem).unwrap(); - let muc2 = Muc::new().with_history(History::new().with_maxstanzas(0)); - assert_eq!(muc, muc2); - - let history = muc.history.unwrap(); - assert_eq!(history.maxstanzas, Some(0)); - assert_eq!(history.maxchars, None); - assert_eq!(history.seconds, None); - assert_eq!(history.since, None); - - let elem: Element = " - - - " - .parse() - .unwrap(); - let muc = Muc::try_from(elem).unwrap(); - assert_eq!( - muc.history.unwrap().since.unwrap(), - DateTime::from_str("1970-01-01T00:00:00+00:00").unwrap() - ); - } -} diff --git a/xmpp-parsers/src/muc/user.rs b/xmpp-parsers/src/muc/user.rs deleted file mode 100644 index c608a83..0000000 --- a/xmpp-parsers/src/muc/user.rs +++ /dev/null @@ -1,725 +0,0 @@ -// Copyright (c) 2017 Maxime “pep” Buquet -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use jid::FullJid; -use std::convert::TryFrom; - -generate_attribute_enum!( -/// Lists all of the possible status codes used in MUC presences. -Status, "status", MUC_USER, "code", { - /// Inform user that any occupant is allowed to see the user's full JID - NonAnonymousRoom => 100, - - /// Inform user that his or her affiliation changed while not in the room - AffiliationChange => 101, - - /// Inform occupants that room now shows unavailable members - ConfigShowsUnavailableMembers => 102, - - /// Inform occupants that room now does not show unavailable members - ConfigHidesUnavailableMembers => 103, - - /// Inform occupants that a non-privacy-related room configuration change has occurred - ConfigNonPrivacyRelated => 104, - - /// Inform user that presence refers to itself - SelfPresence => 110, - - /// Inform occupants that room logging is now enabled - ConfigRoomLoggingEnabled => 170, - - /// Inform occupants that room logging is now disabled - ConfigRoomLoggingDisabled => 171, - - /// Inform occupants that the room is now non-anonymous - ConfigRoomNonAnonymous => 172, - - /// Inform occupants that the room is now semi-anonymous - ConfigRoomSemiAnonymous => 173, - - /// Inform user that a new room has been created - RoomHasBeenCreated => 201, - - /// Inform user that service has assigned or modified occupant's roomnick - AssignedNick => 210, - - /// Inform user that he or she has been banned from the room - Banned => 301, - - /// Inform all occupants of new room nickname - NewNick => 303, - - /// Inform user that he or she has been kicked from the room - Kicked => 307, - - /// Inform user that he or she is being removed from the room - /// because of an affiliation change - RemovalFromRoom => 321, - - /// Inform user that he or she is being removed from the room - /// because the room has been changed to members-only and the - /// user is not a member - ConfigMembersOnly => 322, - - /// Inform user that he or she is being removed from the room - /// because the MUC service is being shut down - ServiceShutdown => 332, -}); - -/// Optional element used in elements inside presence stanzas of type -/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking -/// purposes. -- CHANGELOG 0.17 (2002-10-23) -/// -/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real -/// JID or to a roomnick. -- CHANGELOG 1.25 (2012-02-08) -#[derive(Debug, Clone, PartialEq)] -pub enum Actor { - /// The full JID associated with this user. - Jid(FullJid), - - /// The nickname of this user. - Nick(String), -} - -impl TryFrom for Actor { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "actor", MUC_USER); - check_no_unknown_attributes!(elem, "actor", ["jid", "nick"]); - check_no_children!(elem, "actor"); - let jid: Option = get_attr!(elem, "jid", Option); - let nick = get_attr!(elem, "nick", Option); - - match (jid, nick) { - (Some(_), Some(_)) | (None, None) => Err(Error::ParseError( - "Either 'jid' or 'nick' attribute is required.", - )), - (Some(jid), _) => Ok(Actor::Jid(jid)), - (_, Some(nick)) => Ok(Actor::Nick(nick)), - } - } -} - -impl From for Element { - fn from(actor: Actor) -> Element { - let elem = Element::builder("actor", ns::MUC_USER); - - (match actor { - Actor::Jid(jid) => elem.attr("jid", jid), - Actor::Nick(nick) => elem.attr("nick", nick), - }) - .build() - } -} - -generate_element!( - /// Used to continue a one-to-one discussion in a room, with more than one - /// participant. - Continue, "continue", MUC_USER, - attributes: [ - /// The thread to continue in this room. - thread: Option = "thread", - ] -); - -generate_elem_id!( - /// A reason for inviting, declining, etc. a request. - Reason, - "reason", - MUC_USER -); - -generate_attribute!( - /// The affiliation of an entity with a room, which isn’t tied to its - /// presence in it. - Affiliation, "affiliation", { - /// The user who created the room, or who got appointed by its creator - /// to be their equal. - Owner => "owner", - - /// A user who has been empowered by an owner to do administrative - /// operations. - Admin => "admin", - - /// A user who is whitelisted to speak in moderated rooms, or to join a - /// member-only room. - Member => "member", - - /// A user who has been banned from this room. - Outcast => "outcast", - - /// A normal participant. - None => "none", - }, Default = None -); - -generate_attribute!( - /// The current role of an entity in a room, it can be changed by an owner - /// or an administrator but will be lost once they leave the room. - Role, "role", { - /// This user can kick other participants, as well as grant and revoke - /// them voice. - Moderator => "moderator", - - /// A user who can speak in this room. - Participant => "participant", - - /// A user who cannot speak in this room, and must request voice before - /// doing so. - Visitor => "visitor", - - /// A user who is absent from the room. - None => "none", - }, Default = None -); - -generate_element!( - /// An item representing a user in a room. - Item, "item", MUC_USER, attributes: [ - /// The affiliation of this user with the room. - affiliation: Required = "affiliation", - - /// The real JID of this user, if you are allowed to see it. - jid: Option = "jid", - - /// The current nickname of this user. - nick: Option = "nick", - - /// The current role of this user. - role: Required = "role", - ], children: [ - /// The actor affected by this item. - actor: Option = ("actor", MUC_USER) => Actor, - - /// Whether this continues a one-to-one discussion. - continue_: Option = ("continue", MUC_USER) => Continue, - - /// A reason for this item. - reason: Option = ("reason", MUC_USER) => Reason - ] -); - -impl Item { - /// Creates a new item with the given affiliation and role. - pub fn new(affiliation: Affiliation, role: Role) -> Item { - Item { - affiliation, - role, - jid: None, - nick: None, - actor: None, - continue_: None, - reason: None, - } - } -} - -generate_element!( - /// The main muc#user element. - MucUser, "x", MUC_USER, children: [ - /// List of statuses applying to this item. - status: Vec = ("status", MUC_USER) => Status, - - /// List of items. - items: Vec = ("item", MUC_USER) => Item - ] -); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_simple() { - let elem: Element = " - - " - .parse() - .unwrap(); - MucUser::try_from(elem).unwrap(); - } - - #[test] - fn statuses_and_items() { - let elem: Element = " - - - - - - " - .parse() - .unwrap(); - let muc_user = MucUser::try_from(elem).unwrap(); - assert_eq!(muc_user.status.len(), 2); - assert_eq!(muc_user.status[0], Status::AffiliationChange); - assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers); - assert_eq!(muc_user.items.len(), 1); - assert_eq!(muc_user.items[0].affiliation, Affiliation::Member); - assert_eq!(muc_user.items[0].role, Role::Moderator); - } - - #[test] - fn test_invalid_child() { - let elem: Element = " - - - - " - .parse() - .unwrap(); - let error = MucUser::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in x element."); - } - - #[test] - fn test_serialise() { - let elem: Element = " - - " - .parse() - .unwrap(); - let muc = MucUser { - status: vec![], - items: vec![], - }; - let elem2 = muc.into(); - assert_eq!(elem, elem2); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = MucUser::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in x element."); - } - - #[test] - fn test_status_simple() { - let elem: Element = " - - " - .parse() - .unwrap(); - Status::try_from(elem).unwrap(); - } - - #[test] - fn test_status_invalid() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Status::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'code' missing."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_status_invalid_child() { - let elem: Element = " - - - - " - .parse() - .unwrap(); - let error = Status::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in status element."); - } - - #[test] - fn test_status_simple_code() { - let elem: Element = " - - " - .parse() - .unwrap(); - let status = Status::try_from(elem).unwrap(); - assert_eq!(status, Status::Kicked); - } - - #[test] - fn test_status_invalid_code() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Status::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Invalid status code value."); - } - - #[test] - fn test_status_invalid_code2() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Status::try_from(elem).unwrap_err(); - let error = match error { - Error::ParseIntError(error) => error, - _ => panic!(), - }; - assert_eq!(error.to_string(), "invalid digit found in string"); - } - - #[test] - fn test_actor_required_attributes() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Actor::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Either 'jid' or 'nick' attribute is required."); - } - - #[test] - fn test_actor_required_attributes2() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Actor::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Either 'jid' or 'nick' attribute is required."); - } - - #[test] - fn test_actor_jid() { - let elem: Element = " - - " - .parse() - .unwrap(); - let actor = Actor::try_from(elem).unwrap(); - let jid = match actor { - Actor::Jid(jid) => jid, - _ => panic!(), - }; - assert_eq!(jid, "foo@bar/baz".parse::().unwrap()); - } - - #[test] - fn test_actor_nick() { - let elem: Element = " - - " - .parse() - .unwrap(); - let actor = Actor::try_from(elem).unwrap(); - let nick = match actor { - Actor::Nick(nick) => nick, - _ => panic!(), - }; - assert_eq!(nick, "baz".to_owned()); - } - - #[test] - fn test_continue_simple() { - let elem: Element = " - - " - .parse() - .unwrap(); - Continue::try_from(elem).unwrap(); - } - - #[test] - fn test_continue_thread_attribute() { - let elem: Element = " - - " - .parse() - .unwrap(); - let continue_ = Continue::try_from(elem).unwrap(); - assert_eq!(continue_.thread, Some("foo".to_owned())); - } - - #[test] - fn test_continue_invalid() { - let elem: Element = " - - - - " - .parse() - .unwrap(); - let continue_ = Continue::try_from(elem).unwrap_err(); - let message = match continue_ { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in continue element.".to_owned()); - } - - #[test] - fn test_reason_simple() { - let elem: Element = " - Reason" - .parse() - .unwrap(); - let elem2 = elem.clone(); - let reason = Reason::try_from(elem).unwrap(); - assert_eq!(reason.0, "Reason".to_owned()); - - let elem3 = reason.into(); - assert_eq!(elem2, elem3); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_reason_invalid_attribute() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Reason::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in reason element.".to_owned()); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_reason_invalid() { - let elem: Element = " - - - - " - .parse() - .unwrap(); - let error = Reason::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in reason element.".to_owned()); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_item_invalid_attr() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Item::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in item element.".to_owned()); - } - - #[test] - fn test_item_affiliation_role_attr() { - let elem: Element = " - - " - .parse() - .unwrap(); - Item::try_from(elem).unwrap(); - } - - #[test] - fn test_item_affiliation_role_invalid_attr() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Item::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'role' missing.".to_owned()); - } - - #[test] - fn test_item_nick_attr() { - let elem: Element = " - - " - .parse() - .unwrap(); - let item = Item::try_from(elem).unwrap(); - match item { - Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())), - } - } - - #[test] - fn test_item_affiliation_role_invalid_attr2() { - let elem: Element = " - - " - .parse() - .unwrap(); - let error = Item::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!( - message, - "Required attribute 'affiliation' missing.".to_owned() - ); - } - - #[test] - fn test_item_role_actor_child() { - let elem: Element = " - - - - " - .parse() - .unwrap(); - let item = Item::try_from(elem).unwrap(); - match item { - Item { actor, .. } => assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))), - } - } - - #[test] - fn test_item_role_continue_child() { - let elem: Element = " - - - - " - .parse() - .unwrap(); - let item = Item::try_from(elem).unwrap(); - let continue_1 = Continue { - thread: Some("foobar".to_owned()), - }; - match item { - Item { - continue_: Some(continue_2), - .. - } => assert_eq!(continue_2.thread, continue_1.thread), - _ => panic!(), - } - } - - #[test] - fn test_item_role_reason_child() { - let elem: Element = " - - foobar - - " - .parse() - .unwrap(); - let item = Item::try_from(elem).unwrap(); - match item { - Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))), - } - } - - #[test] - fn test_serialize_item() { - let reference: Element = "foobar" - .parse() - .unwrap(); - - let elem: Element = "" - .parse() - .unwrap(); - let actor = Actor::try_from(elem).unwrap(); - - let elem: Element = - "" - .parse() - .unwrap(); - let continue_ = Continue::try_from(elem).unwrap(); - - let elem: Element = "foobar" - .parse() - .unwrap(); - let reason = Reason::try_from(elem).unwrap(); - - let item = Item { - affiliation: Affiliation::Member, - role: Role::Moderator, - jid: None, - nick: None, - actor: Some(actor), - reason: Some(reason), - continue_: Some(continue_), - }; - - let serialized: Element = item.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/nick.rs b/xmpp-parsers/src/nick.rs deleted file mode 100644 index 20ae7a9..0000000 --- a/xmpp-parsers/src/nick.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -generate_elem_id!( - /// Represents a global, memorable, friendly or informal name chosen by a user. - Nick, - "nick", - NICK -); - -#[cfg(test)] -mod tests { - use super::*; - #[cfg(not(feature = "disable-validation"))] - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Nick, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Nick, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "Link Mauve" - .parse() - .unwrap(); - let nick = Nick::try_from(elem).unwrap(); - assert_eq!(&nick.0, "Link Mauve"); - } - - #[test] - fn test_serialise() { - let elem1 = Element::from(Nick(String::from("Link Mauve"))); - let elem2: Element = "Link Mauve" - .parse() - .unwrap(); - assert_eq!(elem1, elem2); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Nick::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in nick element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Nick::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in nick element."); - } -} diff --git a/xmpp-parsers/src/ns.rs b/xmpp-parsers/src/ns.rs deleted file mode 100644 index 4002402..0000000 --- a/xmpp-parsers/src/ns.rs +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) 2017-2018 Emmanuel Gil Peyrot -// Copyright (c) 2017 Maxime “pep” Buquet -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub const JABBER_CLIENT: &str = "jabber:client"; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub const XMPP_STANZAS: &str = "urn:ietf:params:xml:ns:xmpp-stanzas"; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub const STREAM: &str = "http://etherx.jabber.org/streams"; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub const TLS: &str = "urn:ietf:params:xml:ns:xmpp-tls"; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub const SASL: &str = "urn:ietf:params:xml:ns:xmpp-sasl"; -/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core -pub const BIND: &str = "urn:ietf:params:xml:ns:xmpp-bind"; - -/// RFC 6121: Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence -pub const ROSTER: &str = "jabber:iq:roster"; - -/// RFC 7395: An Extensible Messaging and Presence Protocol (XMPP) Subprotocol for WebSocket -pub const WEBSOCKET: &str = "urn:ietf:params:xml:ns:xmpp-framing"; - -/// XEP-0004: Data Forms -pub const DATA_FORMS: &str = "jabber:x:data"; - -/// XEP-0030: Service Discovery -pub const DISCO_INFO: &str = "http://jabber.org/protocol/disco#info"; -/// XEP-0030: Service Discovery -pub const DISCO_ITEMS: &str = "http://jabber.org/protocol/disco#items"; - -/// XEP-0045: Multi-User Chat -pub const MUC: &str = "http://jabber.org/protocol/muc"; -/// XEP-0045: Multi-User Chat -pub const MUC_USER: &str = "http://jabber.org/protocol/muc#user"; - -/// XEP-0047: In-Band Bytestreams -pub const IBB: &str = "http://jabber.org/protocol/ibb"; - -/// XEP-0048: Bookmarks -pub const BOOKMARKS: &str = "storage:bookmarks"; - -/// XEP-0059: Result Set Management -pub const RSM: &str = "http://jabber.org/protocol/rsm"; - -/// XEP-0060: Publish-Subscribe -pub const PUBSUB: &str = "http://jabber.org/protocol/pubsub"; -/// XEP-0060: Publish-Subscribe -pub const PUBSUB_ERRORS: &str = "http://jabber.org/protocol/pubsub#errors"; -/// XEP-0060: Publish-Subscribe -pub const PUBSUB_EVENT: &str = "http://jabber.org/protocol/pubsub#event"; -/// XEP-0060: Publish-Subscribe -pub const PUBSUB_OWNER: &str = "http://jabber.org/protocol/pubsub#owner"; -/// XEP-0060: Publish-Subscribe node configuration -pub const PUBSUB_CONFIGURE: &str = "http://jabber.org/protocol/pubsub#node_config"; - -/// XEP-0071: XHTML-IM -pub const XHTML_IM: &str = "http://jabber.org/protocol/xhtml-im"; -/// XEP-0071: XHTML-IM -pub const XHTML: &str = "http://www.w3.org/1999/xhtml"; - -/// XEP-0077: In-Band Registration -pub const REGISTER: &str = "jabber:iq:register"; - -/// XEP-0084: User Avatar -pub const AVATAR_DATA: &str = "urn:xmpp:avatar:data"; -/// XEP-0084: User Avatar -pub const AVATAR_METADATA: &str = "urn:xmpp:avatar:metadata"; - -/// XEP-0085: Chat State Notifications -pub const CHATSTATES: &str = "http://jabber.org/protocol/chatstates"; - -/// XEP-0092: Software Version -pub const VERSION: &str = "jabber:iq:version"; - -/// XEP-0107: User Mood -pub const MOOD: &str = "http://jabber.org/protocol/mood"; - -/// XEP-0114: Jabber Component Protocol -pub const COMPONENT_ACCEPT: &str = "jabber:component:accept"; - -/// XEP-0114: Jabber Component Protocol -pub const COMPONENT: &str = "jabber:component:accept"; - -/// XEP-0115: Entity Capabilities -pub const CAPS: &str = "http://jabber.org/protocol/caps"; - -/// XEP-0118: User Tune -pub const TUNE: &str = "http://jabber.org/protocol/tune"; - -/// XEP-0157: Contact Addresses for XMPP Services -pub const SERVER_INFO: &str = "http://jabber.org/network/serverinfo"; - -/// XEP-0166: Jingle -pub const JINGLE: &str = "urn:xmpp:jingle:1"; - -/// XEP-0167: Jingle RTP Sessions -pub const JINGLE_RTP: &str = "urn:xmpp:jingle:apps:rtp:1"; -/// XEP-0167: Jingle RTP Sessions -pub const JINGLE_RTP_AUDIO: &str = "urn:xmpp:jingle:apps:rtp:audio"; -/// XEP-0167: Jingle RTP Sessions -pub const JINGLE_RTP_VIDEO: &str = "urn:xmpp:jingle:apps:rtp:video"; - -/// XEP-0172: User Nickname -pub const NICK: &str = "http://jabber.org/protocol/nick"; - -/// XEP-0176: Jingle ICE-UDP Transport Method -pub const JINGLE_ICE_UDP: &str = "urn:xmpp:jingle:transports:ice-udp:1"; - -/// XEP-0177: Jingle Raw UDP Transport Method -pub const JINGLE_RAW_UDP: &str = "urn:xmpp:jingle:transports:raw-udp:1"; - -/// XEP-0184: Message Delivery Receipts -pub const RECEIPTS: &str = "urn:xmpp:receipts"; - -/// XEP-0191: Blocking Command -pub const BLOCKING: &str = "urn:xmpp:blocking"; -/// XEP-0191: Blocking Command -pub const BLOCKING_ERRORS: &str = "urn:xmpp:blocking:errors"; - -/// XEP-0198: Stream Management -pub const SM: &str = "urn:xmpp:sm:3"; - -/// XEP-0199: XMPP Ping -pub const PING: &str = "urn:xmpp:ping"; - -/// XEP-0202: Entity Time -pub const TIME: &str = "urn:xmpp:time"; - -/// XEP-0203: Delayed Delivery -pub const DELAY: &str = "urn:xmpp:delay"; - -/// XEP-0221: Data Forms Media Element -pub const MEDIA_ELEMENT: &str = "urn:xmpp:media-element"; - -/// XEP-0224: Attention -pub const ATTENTION: &str = "urn:xmpp:attention:0"; - -/// XEP-0231: Bits of Binary -pub const BOB: &str = "urn:xmpp:bob"; - -/// XEP-0234: Jingle File Transfer -pub const JINGLE_FT: &str = "urn:xmpp:jingle:apps:file-transfer:5"; -/// XEP-0234: Jingle File Transfer -pub const JINGLE_FT_ERROR: &str = "urn:xmpp:jingle:apps:file-transfer:errors:0"; - -/// XEP-0257: Client Certificate Management for SASL EXTERNAL -pub const SASL_CERT: &str = "urn:xmpp:saslcert:1"; - -/// XEP-0260: Jingle SOCKS5 Bytestreams Transport Method -pub const JINGLE_S5B: &str = "urn:xmpp:jingle:transports:s5b:1"; - -/// XEP-0261: Jingle In-Band Bytestreams Transport Method -pub const JINGLE_IBB: &str = "urn:xmpp:jingle:transports:ibb:1"; - -/// XEP-0277: Microblogging over XMPP -pub const MICROBLOG: &str = "urn:xmpp:microblog:0"; - -/// XEP-0280: Message Carbons -pub const CARBONS: &str = "urn:xmpp:carbons:2"; - -/// XEP-0293: Jingle RTP Feedback Negotiation -pub const JINGLE_RTCP_FB: &str = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; - -/// XEP-0294: Jingle RTP Header Extensions Negociation -pub const JINGLE_RTP_HDREXT: &str = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"; - -/// XEP-0297: Stanza Forwarding -pub const FORWARD: &str = "urn:xmpp:forward:0"; - -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub const HASHES: &str = "urn:xmpp:hashes:2"; -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub const HASH_ALGO_SHA_256: &str = "urn:xmpp:hash-function-text-names:sha-256"; -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub const HASH_ALGO_SHA_512: &str = "urn:xmpp:hash-function-text-names:sha-512"; -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub const HASH_ALGO_SHA3_256: &str = "urn:xmpp:hash-function-text-names:sha3-256"; -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub const HASH_ALGO_SHA3_512: &str = "urn:xmpp:hash-function-text-names:sha3-512"; -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub const HASH_ALGO_BLAKE2B_256: &str = "urn:xmpp:hash-function-text-names:id-blake2b256"; -/// XEP-0300: Use of Cryptographic Hash Functions in XMPP -pub const HASH_ALGO_BLAKE2B_512: &str = "urn:xmpp:hash-function-text-names:id-blake2b512"; - -/// XEP-0308: Last Message Correction -pub const MESSAGE_CORRECT: &str = "urn:xmpp:message-correct:0"; - -/// XEP-0313: Message Archive Management -pub const MAM: &str = "urn:xmpp:mam:2"; - -/// XEP-0319: Last User Interaction in Presence -pub const IDLE: &str = "urn:xmpp:idle:1"; - -/// XEP-0320: Use of DTLS-SRTP in Jingle Sessions -pub const JINGLE_DTLS: &str = "urn:xmpp:jingle:apps:dtls:0"; - -/// XEP-0328: JID Prep -pub const JID_PREP: &str = "urn:xmpp:jidprep:0"; - -/// XEP-0338: Jingle Grouping Framework -pub const JINGLE_GROUPING: &str = "urn:xmpp:jingle:apps:grouping:0"; - -/// XEP-0339: Source-Specific Media Attributes in Jingle -pub const JINGLE_SSMA: &str = "urn:xmpp:jingle:apps:rtp:ssma:0"; - -/// XEP-0352: Client State Indication -pub const CSI: &str = "urn:xmpp:csi:0"; - -/// XEP-0353: Jingle Message Initiation -pub const JINGLE_MESSAGE: &str = "urn:xmpp:jingle-message:0"; - -/// XEP-0359: Unique and Stable Stanza IDs -pub const SID: &str = "urn:xmpp:sid:0"; - -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_CORE: &str = "urn:xmpp:mix:core:1"; -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_CORE_SEARCHABLE: &str = "urn:xmpp:mix:core:1#searchable"; -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_CORE_CREATE_CHANNEL: &str = "urn:xmpp:mix:core:1#create-channel"; -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_NODES_PRESENCE: &str = "urn:xmpp:mix:nodes:presence"; -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_NODES_PARTICIPANTS: &str = "urn:xmpp:mix:nodes:participants"; -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_NODES_MESSAGES: &str = "urn:xmpp:mix:nodes:messages"; -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_NODES_CONFIG: &str = "urn:xmpp:mix:nodes:config"; -/// XEP-0369: Mediated Information eXchange (MIX) -pub const MIX_NODES_INFO: &str = "urn:xmpp:mix:nodes:info"; - -/// XEP-0373: OpenPGP for XMPP -pub const OX: &str = "urn:xmpp:openpgp:0"; -/// XEP-0373: OpenPGP for XMPP -pub const OX_PUBKEYS: &str = "urn:xmpp:openpgp:0:public-keys"; - -/// XEP-0380: Explicit Message Encryption -pub const EME: &str = "urn:xmpp:eme:0"; - -/// XEP-0390: Entity Capabilities 2.0 -pub const ECAPS2: &str = "urn:xmpp:caps"; -/// XEP-0390: Entity Capabilities 2.0 -pub const ECAPS2_OPTIMIZE: &str = "urn:xmpp:caps:optimize"; - -/// XEP-0402: Bookmarks 2 (This Time it's Serious) -pub const BOOKMARKS2: &str = "urn:xmpp:bookmarks:1"; -/// XEP-0402: Bookmarks 2 (This Time it's Serious) -pub const BOOKMARKS2_COMPAT: &str = "urn:xmpp:bookmarks:0#compat"; - -/// XEP-0421: Anonymous unique occupant identifiers for MUCs -pub const OID: &str = "urn:xmpp:occupant-id:0"; - -/// Alias for the main namespace of the stream, that is "jabber:client" when -/// the component feature isn’t enabled. -#[cfg(not(feature = "component"))] -pub const DEFAULT_NS: &str = JABBER_CLIENT; - -/// Alias for the main namespace of the stream, that is -/// "jabber:component:accept" when the component feature is enabled. -#[cfg(feature = "component")] -pub const DEFAULT_NS: &str = COMPONENT_ACCEPT; - -/// Jitsi Meet general namespace -pub const JITSI_MEET: &str = "http://jitsi.org/jitmeet"; - -/// Jitsi Meet Colibri namespace -pub const JITSI_COLIBRI: &str = "http://jitsi.org/protocol/colibri"; diff --git a/xmpp-parsers/src/occupant_id.rs b/xmpp-parsers/src/occupant_id.rs deleted file mode 100644 index fd11b5b..0000000 --- a/xmpp-parsers/src/occupant_id.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; -use crate::presence::PresencePayload; - -generate_element!( - /// Unique identifier given to a MUC participant. - /// - /// It allows clients to identify a MUC participant across reconnects and - /// renames. It thus prevents impersonification of anonymous users. - OccupantId, "occupant-id", OID, - - attributes: [ - /// The id associated to the sending user by the MUC service. - id: Required = "id", - ] -); - -impl MessagePayload for OccupantId {} -impl PresencePayload for OccupantId {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(OccupantId, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(OccupantId, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let origin_id = OccupantId::try_from(elem).unwrap(); - assert_eq!(origin_id.id, "coucou"); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = OccupantId::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in occupant-id element."); - } - - #[test] - fn test_invalid_id() { - let elem: Element = "" - .parse() - .unwrap(); - let error = OccupantId::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'id' missing."); - } - - #[test] - fn test_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let occupant_id = OccupantId { - id: String::from("coucou"), - }; - let elem2 = occupant_id.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/openpgp.rs b/xmpp-parsers/src/openpgp.rs deleted file mode 100644 index b862e3f..0000000 --- a/xmpp-parsers/src/openpgp.rs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2019 Maxime “pep” Buquet -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::date::DateTime; -use crate::pubsub::PubSubPayload; -use crate::util::helpers::Base64; - -// TODO: Merge this container with the PubKey struct -generate_element!( - /// Data contained in the PubKey element - PubKeyData, "data", OX, - text: ( - /// Base64 data - data: Base64> - ) -); - -generate_element!( - /// Pubkey element to be used in PubSub publish payloads. - PubKey, "pubkey", OX, - attributes: [ - /// Last updated date - date: Option = "date" - ], - children: [ - /// Public key as base64 data - data: Required = ("data", OX) => PubKeyData - ] -); - -impl PubSubPayload for PubKey {} - -generate_element!( - /// Public key metadata - PubKeyMeta, "pubkey-metadata", OX, - attributes: [ - /// OpenPGP v4 fingerprint - v4fingerprint: Required = "v4-fingerprint", - /// Time the key was published or updated - date: Required = "date", - ] -); - -generate_element!( - /// List of public key metadata - PubKeysMeta, "public-key-list", OX, - children: [ - /// Public keys - pubkeys: Vec = ("pubkey-metadata", OX) => PubKeyMeta - ] -); - -impl PubSubPayload for PubKeysMeta {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ns; - use crate::pubsub::{ - pubsub::{Item as PubSubItem, Publish}, - Item, NodeName, - }; - use crate::Element; - use std::str::FromStr; - - #[test] - fn pubsub_publish_pubkey_data() { - let pubkey = PubKey { - date: None, - data: PubKeyData { - data: (&"Foo").as_bytes().to_vec(), - }, - }; - println!("Foo1: {:?}", pubkey); - - let pubsub = Publish { - node: NodeName(format!("{}:{}", ns::OX_PUBKEYS, "some-fingerprint")), - items: vec![PubSubItem(Item::new(None, None, Some(pubkey)))], - }; - println!("Foo2: {:?}", pubsub); - } - - #[test] - fn pubsub_publish_pubkey_meta() { - let pubkeymeta = PubKeysMeta { - pubkeys: vec![PubKeyMeta { - v4fingerprint: "some-fingerprint".to_owned(), - date: DateTime::from_str("2019-03-30T18:30:25Z").unwrap(), - }], - }; - println!("Foo1: {:?}", pubkeymeta); - - let pubsub = Publish { - node: NodeName("foo".to_owned()), - items: vec![PubSubItem(Item::new(None, None, Some(pubkeymeta)))], - }; - println!("Foo2: {:?}", pubsub); - } - - #[test] - fn test_serialize_pubkey() { - let reference: Element = "AAAA" - .parse() - .unwrap(); - - let pubkey = PubKey { - date: None, - data: PubKeyData { - data: b"\0\0\0".to_vec(), - }, - }; - - let serialized: Element = pubkey.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/ping.rs b/xmpp-parsers/src/ping.rs deleted file mode 100644 index 70663fa..0000000 --- a/xmpp-parsers/src/ping.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// Copyright (c) 2017 Maxime “pep” Buquet -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::IqGetPayload; - -generate_empty_element!( - /// Represents a ping to the recipient, which must be answered with an - /// empty `` or with an error. - Ping, - "ping", - PING -); - -impl IqGetPayload for Ping {} - -#[cfg(test)] -mod tests { - use super::*; - #[cfg(not(feature = "disable-validation"))] - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[test] - fn test_size() { - assert_size!(Ping, 0); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - Ping::try_from(elem).unwrap(); - } - - #[test] - fn test_serialise() { - let elem1 = Element::from(Ping); - let elem2: Element = "".parse().unwrap(); - assert_eq!(elem1, elem2); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Ping::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in ping element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - let elem: Element = "".parse().unwrap(); - let error = Ping::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in ping element."); - } -} diff --git a/xmpp-parsers/src/presence.rs b/xmpp-parsers/src/presence.rs deleted file mode 100644 index 2d70714..0000000 --- a/xmpp-parsers/src/presence.rs +++ /dev/null @@ -1,655 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// Copyright (c) 2017 Maxime “pep” Buquet -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::util::error::Error; -use jid::Jid; -use minidom::{Element, IntoAttributeValue, Node}; -use std::collections::BTreeMap; -use std::convert::TryFrom; -use std::str::FromStr; - -/// Should be implemented on every known payload of a ``. -pub trait PresencePayload: TryFrom + Into {} - -/// Specifies the availability of an entity or resource. -#[derive(Debug, Clone, PartialEq)] -pub enum Show { - /// The entity or resource is temporarily away. - Away, - - /// The entity or resource is actively interested in chatting. - Chat, - - /// The entity or resource is busy (dnd = "Do Not Disturb"). - Dnd, - - /// The entity or resource is away for an extended period (xa = "eXtended - /// Away"). - Xa, -} - -impl FromStr for Show { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "away" => Show::Away, - "chat" => Show::Chat, - "dnd" => Show::Dnd, - "xa" => Show::Xa, - - _ => return Err(Error::ParseError("Invalid value for show.")), - }) - } -} - -impl Into for Show { - fn into(self) -> Node { - Element::builder("show", ns::DEFAULT_NS) - .append(match self { - Show::Away => "away", - Show::Chat => "chat", - Show::Dnd => "dnd", - Show::Xa => "xa", - }) - .build() - .into() - } -} - -type Lang = String; -type Status = String; - -type Priority = i8; - -/// -#[derive(Debug, Clone, PartialEq)] -pub enum Type { - /// This value is not an acceptable 'type' attribute, it is only used - /// internally to signal the absence of 'type'. - None, - - /// An error has occurred regarding processing of a previously sent - /// presence stanza; if the presence stanza is of type "error", it MUST - /// include an child element (refer to [XMPP‑CORE]). - Error, - - /// A request for an entity's current presence; SHOULD be generated only by - /// a server on behalf of a user. - Probe, - - /// The sender wishes to subscribe to the recipient's presence. - Subscribe, - - /// The sender has allowed the recipient to receive their presence. - Subscribed, - - /// The sender is no longer available for communication. - Unavailable, - - /// The sender is unsubscribing from the receiver's presence. - Unsubscribe, - - /// The subscription request has been denied or a previously granted - /// subscription has been canceled. - Unsubscribed, -} - -impl Default for Type { - fn default() -> Type { - Type::None - } -} - -impl FromStr for Type { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "error" => Type::Error, - "probe" => Type::Probe, - "subscribe" => Type::Subscribe, - "subscribed" => Type::Subscribed, - "unavailable" => Type::Unavailable, - "unsubscribe" => Type::Unsubscribe, - "unsubscribed" => Type::Unsubscribed, - - _ => { - return Err(Error::ParseError( - "Invalid 'type' attribute on presence element.", - )); - } - }) - } -} - -impl IntoAttributeValue for Type { - fn into_attribute_value(self) -> Option { - Some( - match self { - Type::None => return None, - - Type::Error => "error", - Type::Probe => "probe", - Type::Subscribe => "subscribe", - Type::Subscribed => "subscribed", - Type::Unavailable => "unavailable", - Type::Unsubscribe => "unsubscribe", - Type::Unsubscribed => "unsubscribed", - } - .to_owned(), - ) - } -} - -/// The main structure representing the `` stanza. -#[derive(Debug, Clone)] -pub struct Presence { - /// The sender of this presence. - pub from: Option, - - /// The recipient of this presence. - pub to: Option, - - /// The identifier, unique on this stream, of this stanza. - pub id: Option, - - /// The type of this presence stanza. - pub type_: Type, - - /// The availability of the sender of this presence. - pub show: Option, - - /// A localised list of statuses defined in this presence. - pub statuses: BTreeMap, - - /// The sender’s resource priority, if negative it won’t receive messages - /// that haven’t been directed to it. - pub priority: Priority, - - /// A list of payloads contained in this presence. - pub payloads: Vec, -} - -impl Presence { - /// Create a new presence of this type. - pub fn new(type_: Type) -> Presence { - Presence { - from: None, - to: None, - id: None, - type_, - show: None, - statuses: BTreeMap::new(), - priority: 0i8, - payloads: vec![], - } - } - - /// Set the emitter of this presence, this should only be useful for - /// servers and components, as clients can only send presences from their - /// own resource (which is implicit). - pub fn with_from>(mut self, from: J) -> Presence { - self.from = Some(from.into()); - self - } - - /// Set the recipient of this presence, this is only useful for directed - /// presences. - pub fn with_to>(mut self, to: J) -> Presence { - self.to = Some(to.into()); - self - } - - /// Set the identifier for this presence. - pub fn with_id(mut self, id: String) -> Presence { - self.id = Some(id); - self - } - - /// Set the availability information of this presence. - pub fn with_show(mut self, show: Show) -> Presence { - self.show = Some(show); - self - } - - /// Set the priority of this presence. - pub fn with_priority(mut self, priority: i8) -> Presence { - self.priority = priority; - self - } - - /// Set the payloads of this presence. - pub fn with_payloads(mut self, payloads: Vec) -> Presence { - self.payloads = payloads; - self - } - - /// Set the availability information of this presence. - pub fn set_status(&mut self, lang: L, status: S) - where - L: Into, - S: Into, - { - self.statuses.insert(lang.into(), status.into()); - } - - /// Add a payload to this presence. - pub fn add_payload(&mut self, payload: P) { - self.payloads.push(payload.into()); - } -} - -impl TryFrom for Presence { - type Error = Error; - - fn try_from(root: Element) -> Result { - check_self!(root, "presence", DEFAULT_NS); - let mut show = None; - let mut priority = None; - let mut presence = Presence { - from: get_attr!(root, "from", Option), - to: get_attr!(root, "to", Option), - id: get_attr!(root, "id", Option), - type_: get_attr!(root, "type", Default), - show: None, - statuses: BTreeMap::new(), - priority: 0i8, - payloads: vec![], - }; - for elem in root.children() { - if elem.is("show", ns::DEFAULT_NS) { - if show.is_some() { - return Err(Error::ParseError( - "More than one show element in a presence.", - )); - } - check_no_attributes!(elem, "show"); - check_no_children!(elem, "show"); - show = Some(Show::from_str(elem.text().as_ref())?); - } else if elem.is("status", ns::DEFAULT_NS) { - check_no_unknown_attributes!(elem, "status", ["xml:lang"]); - check_no_children!(elem, "status"); - let lang = get_attr!(elem, "xml:lang", Default); - if presence.statuses.insert(lang, elem.text()).is_some() { - return Err(Error::ParseError( - "Status element present twice for the same xml:lang.", - )); - } - } else if elem.is("priority", ns::DEFAULT_NS) { - if priority.is_some() { - return Err(Error::ParseError( - "More than one priority element in a presence.", - )); - } - check_no_attributes!(elem, "priority"); - check_no_children!(elem, "priority"); - priority = Some(Priority::from_str(elem.text().as_ref())?); - } else { - presence.payloads.push(elem.clone()); - } - } - presence.show = show; - if let Some(priority) = priority { - presence.priority = priority; - } - Ok(presence) - } -} - -impl From for Element { - fn from(presence: Presence) -> Element { - Element::builder("presence", ns::DEFAULT_NS) - .attr("from", presence.from) - .attr("to", presence.to) - .attr("id", presence.id) - .attr("type", presence.type_) - .append_all(presence.show.into_iter()) - .append_all(presence.statuses.into_iter().map(|(lang, status)| { - Element::builder("status", ns::DEFAULT_NS) - .attr( - "xml:lang", - match lang.as_ref() { - "" => None, - lang => Some(lang), - }, - ) - .append(status) - })) - .append_all(if presence.priority == 0 { - None - } else { - Some( - Element::builder("priority", ns::DEFAULT_NS) - .append(format!("{}", presence.priority)), - ) - }) - .append_all(presence.payloads.into_iter()) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use jid::{BareJid, FullJid}; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Show, 1); - assert_size!(Type, 1); - assert_size!(Presence, 120); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Show, 1); - assert_size!(Type, 1); - assert_size!(Presence, 240); - } - - #[test] - fn test_simple() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let presence = Presence::try_from(elem).unwrap(); - assert_eq!(presence.from, None); - assert_eq!(presence.to, None); - assert_eq!(presence.id, None); - assert_eq!(presence.type_, Type::None); - assert!(presence.payloads.is_empty()); - } - - #[test] - fn test_serialise() { - #[cfg(not(feature = "component"))] - let elem: Element = "/>" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "/>" - .parse() - .unwrap(); - let presence = Presence::new(Type::Unavailable); - let elem2 = presence.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_show() { - #[cfg(not(feature = "component"))] - let elem: Element = "chat" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "chat" - .parse() - .unwrap(); - let presence = Presence::try_from(elem).unwrap(); - assert_eq!(presence.payloads.len(), 0); - assert_eq!(presence.show, Some(Show::Chat)); - } - - #[test] - fn test_empty_show_value() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let presence = Presence::try_from(elem).unwrap(); - assert_eq!(presence.show, None); - } - - #[test] - fn test_missing_show_value() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let error = Presence::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Invalid value for show."); - } - - #[test] - fn test_invalid_show() { - // "online" used to be a pretty common mistake. - #[cfg(not(feature = "component"))] - let elem: Element = "online" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "online" - .parse() - .unwrap(); - let error = Presence::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Invalid value for show."); - } - - #[test] - fn test_empty_status() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let presence = Presence::try_from(elem).unwrap(); - assert_eq!(presence.payloads.len(), 0); - assert_eq!(presence.statuses.len(), 1); - assert_eq!(presence.statuses[""], ""); - } - - #[test] - fn test_status() { - #[cfg(not(feature = "component"))] - let elem: Element = "Here!" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "Here!" - .parse() - .unwrap(); - let presence = Presence::try_from(elem).unwrap(); - assert_eq!(presence.payloads.len(), 0); - assert_eq!(presence.statuses.len(), 1); - assert_eq!(presence.statuses[""], "Here!"); - } - - #[test] - fn test_multiple_statuses() { - #[cfg(not(feature = "component"))] - let elem: Element = "Here!Là!".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "Here!Là!".parse().unwrap(); - let presence = Presence::try_from(elem).unwrap(); - assert_eq!(presence.payloads.len(), 0); - assert_eq!(presence.statuses.len(), 2); - assert_eq!(presence.statuses[""], "Here!"); - assert_eq!(presence.statuses["fr"], "Là!"); - } - - #[test] - fn test_invalid_multiple_statuses() { - #[cfg(not(feature = "component"))] - let elem: Element = "Here!Là!".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "Here!Là!".parse().unwrap(); - let error = Presence::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!( - message, - "Status element present twice for the same xml:lang." - ); - } - - #[test] - fn test_priority() { - #[cfg(not(feature = "component"))] - let elem: Element = "-1" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "-1" - .parse() - .unwrap(); - let presence = Presence::try_from(elem).unwrap(); - assert_eq!(presence.payloads.len(), 0); - assert_eq!(presence.priority, -1i8); - } - - #[test] - fn test_invalid_priority() { - #[cfg(not(feature = "component"))] - let elem: Element = "128" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "128" - .parse() - .unwrap(); - let error = Presence::try_from(elem).unwrap_err(); - match error { - Error::ParseIntError(_) => (), - _ => panic!(), - }; - } - - #[test] - fn test_unknown_child() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "" - .parse() - .unwrap(); - let presence = Presence::try_from(elem).unwrap(); - let payload = &presence.payloads[0]; - assert!(payload.is("test", "invalid")); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_status_child() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "" - .parse() - .unwrap(); - let error = Presence::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in status element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = - "" - .parse() - .unwrap(); - let error = Presence::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in status element."); - } - - #[test] - fn test_serialise_status() { - let status = Status::from("Hello world!"); - let mut presence = Presence::new(Type::Unavailable); - presence.statuses.insert(String::from(""), status); - let elem: Element = presence.into(); - assert!(elem.is("presence", ns::DEFAULT_NS)); - assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS)); - } - - #[test] - fn test_serialise_priority() { - let presence = Presence::new(Type::None).with_priority(42); - let elem: Element = presence.into(); - assert!(elem.is("presence", ns::DEFAULT_NS)); - let priority = elem.children().next().unwrap(); - assert!(priority.is("priority", ns::DEFAULT_NS)); - assert_eq!(priority.text(), "42"); - } - - #[test] - fn presence_with_to() { - let presence = Presence::new(Type::None); - let elem: Element = presence.into(); - assert_eq!(elem.attr("to"), None); - - let presence = Presence::new(Type::None).with_to(Jid::Bare(BareJid::domain("localhost"))); - let elem: Element = presence.into(); - assert_eq!(elem.attr("to"), Some("localhost")); - - let presence = Presence::new(Type::None).with_to(BareJid::domain("localhost")); - let elem: Element = presence.into(); - assert_eq!(elem.attr("to"), Some("localhost")); - - let presence = Presence::new(Type::None).with_to(Jid::Full(FullJid::new( - "test", - "localhost", - "coucou", - ))); - let elem: Element = presence.into(); - assert_eq!(elem.attr("to"), Some("test@localhost/coucou")); - - let presence = - Presence::new(Type::None).with_to(FullJid::new("test", "localhost", "coucou")); - let elem: Element = presence.into(); - assert_eq!(elem.attr("to"), Some("test@localhost/coucou")); - } -} diff --git a/xmpp-parsers/src/pubsub/event.rs b/xmpp-parsers/src/pubsub/event.rs deleted file mode 100644 index 6ad2bc9..0000000 --- a/xmpp-parsers/src/pubsub/event.rs +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::DataForm; -use crate::date::DateTime; -use crate::message::MessagePayload; -use crate::ns; -use crate::pubsub::{Item as PubSubItem, ItemId, NodeName, Subscription, SubscriptionId}; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::convert::TryFrom; - -/// Event wrapper for a PubSub ``. -#[derive(Debug, Clone)] -pub struct Item(pub PubSubItem); - -impl_pubsub_item!(Item, PUBSUB_EVENT); - -/// Represents an event happening to a PubSub node. -#[derive(Debug, Clone)] -pub enum PubSubEvent { - /* - Collection { - }, - */ - /// This node’s configuration changed. - Configuration { - /// The node affected. - node: NodeName, - - /// The new configuration of this node. - form: Option, - }, - - /// This node has been deleted, with an optional redirect to another node. - Delete { - /// The node affected. - node: NodeName, - - /// The xmpp: URI of another node replacing this one. - redirect: Option, - }, - - /// Some items have been published on this node. - PublishedItems { - /// The node affected. - node: NodeName, - - /// The list of published items. - items: Vec, - }, - - /// Some items have been removed from this node. - RetractedItems { - /// The node affected. - node: NodeName, - - /// The list of retracted items. - items: Vec, - }, - - /// All items of this node just got removed at once. - Purge { - /// The node affected. - node: NodeName, - }, - - /// The user’s subscription to this node has changed. - Subscription { - /// The node affected. - node: NodeName, - - /// The time at which this subscription will expire. - expiry: Option, - - /// The JID of the user affected. - jid: Option, - - /// An identifier for this subscription. - subid: Option, - - /// The state of this subscription. - subscription: Option, - }, -} - -fn parse_items(elem: Element, node: NodeName) -> Result { - let mut is_retract = None; - let mut items = vec![]; - let mut retracts = vec![]; - for child in elem.children() { - if child.is("item", ns::PUBSUB_EVENT) { - match is_retract { - None => is_retract = Some(false), - Some(false) => (), - Some(true) => { - return Err(Error::ParseError( - "Mix of item and retract in items element.", - )); - } - } - items.push(Item::try_from(child.clone())?); - } else if child.is("retract", ns::PUBSUB_EVENT) { - match is_retract { - None => is_retract = Some(true), - Some(true) => (), - Some(false) => { - return Err(Error::ParseError( - "Mix of item and retract in items element.", - )); - } - } - check_no_children!(child, "retract"); - check_no_unknown_attributes!(child, "retract", ["id"]); - let id = get_attr!(child, "id", Required); - retracts.push(id); - } else { - return Err(Error::ParseError("Invalid child in items element.")); - } - } - Ok(match is_retract { - Some(false) => PubSubEvent::PublishedItems { node, items }, - Some(true) => PubSubEvent::RetractedItems { - node, - items: retracts, - }, - None => return Err(Error::ParseError("Missing children in items element.")), - }) -} - -impl TryFrom for PubSubEvent { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "event", PUBSUB_EVENT); - check_no_attributes!(elem, "event"); - - let mut payload = None; - for child in elem.children() { - let node = get_attr!(child, "node", Required); - if child.is("configuration", ns::PUBSUB_EVENT) { - let mut payloads = child.children().cloned().collect::>(); - let item = payloads.pop(); - if !payloads.is_empty() { - return Err(Error::ParseError( - "More than a single payload in configuration element.", - )); - } - let form = match item { - None => None, - Some(payload) => Some(DataForm::try_from(payload)?), - }; - payload = Some(PubSubEvent::Configuration { node, form }); - } else if child.is("delete", ns::PUBSUB_EVENT) { - let mut redirect = None; - for item in child.children() { - if item.is("redirect", ns::PUBSUB_EVENT) { - if redirect.is_some() { - return Err(Error::ParseError( - "More than one redirect in delete element.", - )); - } - let uri = get_attr!(item, "uri", Required); - redirect = Some(uri); - } else { - return Err(Error::ParseError("Unknown child in delete element.")); - } - } - payload = Some(PubSubEvent::Delete { node, redirect }); - } else if child.is("items", ns::PUBSUB_EVENT) { - payload = Some(parse_items(child.clone(), node)?); - } else if child.is("purge", ns::PUBSUB_EVENT) { - check_no_children!(child, "purge"); - payload = Some(PubSubEvent::Purge { node }); - } else if child.is("subscription", ns::PUBSUB_EVENT) { - check_no_children!(child, "subscription"); - payload = Some(PubSubEvent::Subscription { - node, - expiry: get_attr!(child, "expiry", Option), - jid: get_attr!(child, "jid", Option), - subid: get_attr!(child, "subid", Option), - subscription: get_attr!(child, "subscription", Option), - }); - } else { - return Err(Error::ParseError("Unknown child in event element.")); - } - } - Ok(payload.ok_or(Error::ParseError("No payload in event element."))?) - } -} - -impl From for Element { - fn from(event: PubSubEvent) -> Element { - let payload = match event { - PubSubEvent::Configuration { node, form } => { - Element::builder("configuration", ns::PUBSUB_EVENT) - .attr("node", node) - .append_all(form.map(Element::from)) - } - PubSubEvent::Delete { node, redirect } => Element::builder("purge", ns::PUBSUB_EVENT) - .attr("node", node) - .append_all(redirect.map(|redirect| { - Element::builder("redirect", ns::PUBSUB_EVENT).attr("uri", redirect) - })), - PubSubEvent::PublishedItems { node, items } => { - Element::builder("items", ns::PUBSUB_EVENT) - .attr("node", node) - .append_all(items.into_iter()) - } - PubSubEvent::RetractedItems { node, items } => { - Element::builder("items", ns::PUBSUB_EVENT) - .attr("node", node) - .append_all( - items - .into_iter() - .map(|id| Element::builder("retract", ns::PUBSUB_EVENT).attr("id", id)), - ) - } - PubSubEvent::Purge { node } => { - Element::builder("purge", ns::PUBSUB_EVENT).attr("node", node) - } - PubSubEvent::Subscription { - node, - expiry, - jid, - subid, - subscription, - } => Element::builder("subscription", ns::PUBSUB_EVENT) - .attr("node", node) - .attr("expiry", expiry) - .attr("jid", jid) - .attr("subid", subid) - .attr("subscription", subscription), - }; - Element::builder("event", ns::PUBSUB_EVENT) - .append(payload) - .build() - } -} - -impl MessagePayload for PubSubEvent {} - -#[cfg(test)] -mod tests { - use super::*; - use jid::BareJid; - - #[test] - fn missing_items() { - let elem: Element = - "" - .parse() - .unwrap(); - let error = PubSubEvent::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Missing children in items element."); - } - - #[test] - fn test_simple_items() { - let elem: Element = "".parse().unwrap(); - let event = PubSubEvent::try_from(elem).unwrap(); - match event { - PubSubEvent::PublishedItems { node, items } => { - assert_eq!(node, NodeName(String::from("coucou"))); - assert_eq!(items[0].id, Some(ItemId(String::from("test")))); - assert_eq!( - items[0].publisher.clone().unwrap(), - BareJid::new("test", "coucou") - ); - assert_eq!(items[0].payload, None); - } - _ => panic!(), - } - } - - #[test] - fn test_simple_pep() { - let elem: Element = "".parse().unwrap(); - let event = PubSubEvent::try_from(elem).unwrap(); - match event { - PubSubEvent::PublishedItems { node, items } => { - assert_eq!(node, NodeName(String::from("something"))); - assert_eq!(items[0].id, None); - assert_eq!(items[0].publisher, None); - match items[0].payload { - Some(ref elem) => assert!(elem.is("foreign", "example:namespace")), - _ => panic!(), - } - } - _ => panic!(), - } - } - - #[test] - fn test_simple_retract() { - let elem: Element = "".parse().unwrap(); - let event = PubSubEvent::try_from(elem).unwrap(); - match event { - PubSubEvent::RetractedItems { node, items } => { - assert_eq!(node, NodeName(String::from("something"))); - assert_eq!(items[0], ItemId(String::from("coucou"))); - assert_eq!(items[1], ItemId(String::from("test"))); - } - _ => panic!(), - } - } - - #[test] - fn test_simple_delete() { - let elem: Element = "".parse().unwrap(); - let event = PubSubEvent::try_from(elem).unwrap(); - match event { - PubSubEvent::Delete { node, redirect } => { - assert_eq!(node, NodeName(String::from("coucou"))); - assert_eq!(redirect, Some(String::from("hello"))); - } - _ => panic!(), - } - } - - #[test] - fn test_simple_purge() { - let elem: Element = - "" - .parse() - .unwrap(); - let event = PubSubEvent::try_from(elem).unwrap(); - match event { - PubSubEvent::Purge { node } => { - assert_eq!(node, NodeName(String::from("coucou"))); - } - _ => panic!(), - } - } - - #[test] - fn test_simple_configure() { - let elem: Element = "http://jabber.org/protocol/pubsub#node_config".parse().unwrap(); - let event = PubSubEvent::try_from(elem).unwrap(); - match event { - PubSubEvent::Configuration { node, form: _ } => { - assert_eq!(node, NodeName(String::from("coucou"))); - //assert_eq!(form.type_, Result_); - } - _ => panic!(), - } - } - - #[test] - fn test_invalid() { - let elem: Element = - "" - .parse() - .unwrap(); - let error = PubSubEvent::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in event element."); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid_attribute() { - let elem: Element = "" - .parse() - .unwrap(); - let error = PubSubEvent::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in event element."); - } - - #[test] - fn test_ex221_subscription() { - let elem: Element = "" - .parse() - .unwrap(); - let event = PubSubEvent::try_from(elem.clone()).unwrap(); - match event.clone() { - PubSubEvent::Subscription { - node, - expiry, - jid, - subid, - subscription, - } => { - assert_eq!(node, NodeName(String::from("princely_musings"))); - assert_eq!( - subid, - Some(SubscriptionId(String::from( - "ba49252aaa4f5d320c24d3766f0bdcade78c78d3" - ))) - ); - assert_eq!(subscription, Some(Subscription::Subscribed)); - assert_eq!(jid.unwrap(), BareJid::new("francisco", "denmark.lit")); - assert_eq!(expiry, Some("2006-02-28T23:59:59Z".parse().unwrap())); - } - _ => panic!(), - } - - let elem2: Element = event.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/pubsub/mod.rs b/xmpp-parsers/src/pubsub/mod.rs deleted file mode 100644 index 008d7a0..0000000 --- a/xmpp-parsers/src/pubsub/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/// The `http://jabber.org/protocol/pubsub#event` protocol. -pub mod event; - -/// The `http://jabber.org/protocol/pubsub#owner` protocol. -pub mod owner; - -/// The `http://jabber.org/protocol/pubsub` protocol. -pub mod pubsub; - -pub use self::event::PubSubEvent; -pub use self::owner::PubSubOwner; -pub use self::pubsub::PubSub; - -use crate::{Element, Jid}; - -generate_id!( - /// The name of a PubSub node, used to identify it on a JID. - NodeName -); - -generate_id!( - /// The identifier of an item, which is unique per node. - ItemId -); - -generate_id!( - /// The identifier of a subscription to a PubSub node. - SubscriptionId -); - -generate_attribute!( - /// The state of a subscription to a node. - Subscription, "subscription", { - /// The user is not subscribed to this node. - None => "none", - - /// The user’s subscription to this node is still pending. - Pending => "pending", - - /// The user is subscribed to this node. - Subscribed => "subscribed", - - /// The user’s subscription to this node will only be valid once - /// configured. - Unconfigured => "unconfigured", - }, Default = None -); - -generate_attribute!( - /// A list of possible affiliations to a node. - AffiliationAttribute, "affiliation", { - /// You are a member of this node, you can subscribe and retrieve items. - Member => "member", - - /// You don’t have a specific affiliation with this node, you can only subscribe to it. - None => "none", - - /// You are banned from this node. - Outcast => "outcast", - - /// You are an owner of this node, and can do anything with it. - Owner => "owner", - - /// You are a publisher on this node, you can publish and retract items to it. - Publisher => "publisher", - - /// You can publish and retract items on this node, but not subscribe or retrieve items. - PublishOnly => "publish-only", - } -); - -/// An item from a PubSub node. -#[derive(Debug, Clone, PartialEq)] -pub struct Item { - /// The identifier for this item, unique per node. - pub id: Option, - - /// The JID of the entity who published this item. - pub publisher: Option, - - /// The payload of this item, in an arbitrary namespace. - pub payload: Option, -} - -impl Item { - /// Create a new item, accepting only payloads implementing `PubSubPayload`. - pub fn new( - id: Option, - publisher: Option, - payload: Option

, - ) -> Item { - Item { - id, - publisher, - payload: payload.map(Into::into), - } - } -} - -/// This trait should be implemented on any element which can be included as a PubSub payload. -pub trait PubSubPayload: ::std::convert::TryFrom + Into {} diff --git a/xmpp-parsers/src/pubsub/owner.rs b/xmpp-parsers/src/pubsub/owner.rs deleted file mode 100644 index a890523..0000000 --- a/xmpp-parsers/src/pubsub/owner.rs +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) 2020 Paul Fariello -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::DataForm; -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use crate::ns; -use crate::pubsub::{AffiliationAttribute, NodeName, Subscription}; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::convert::TryFrom; - -generate_element!( - /// A list of affiliations you have on a service, or on a node. - Affiliations, "affiliations", PUBSUB_OWNER, - attributes: [ - /// The node name this request pertains to. - node: Required = "node", - ], - children: [ - /// The actual list of affiliation elements. - affiliations: Vec = ("affiliation", PUBSUB_OWNER) => Affiliation - ] -); - -generate_element!( - /// An affiliation element. - Affiliation, "affiliation", PUBSUB_OWNER, - attributes: [ - /// The node this affiliation pertains to. - jid: Required = "jid", - - /// The affiliation you currently have on this node. - affiliation: Required = "affiliation", - ] -); - -generate_element!( - /// Request to configure a node. - Configure, "configure", PUBSUB_OWNER, - attributes: [ - /// The node to be configured. - node: Option = "node", - ], - children: [ - /// The form to configure it. - form: Option = ("x", DATA_FORMS) => DataForm - ] -); - -generate_element!( - /// Request to change default configuration. - Default, "default", PUBSUB_OWNER, - children: [ - /// The form to configure it. - form: Option = ("x", DATA_FORMS) => DataForm - ] -); - -generate_element!( - /// Request to delete a node. - Delete, "delete", PUBSUB_OWNER, - attributes: [ - /// The node to be configured. - node: Required = "node", - ], - children: [ - /// Redirection to replace the deleted node. - redirect: Option = ("redirect", PUBSUB_OWNER) => Redirect - ] -); - -generate_element!( - /// A redirect element. - Redirect, "redirect", PUBSUB_OWNER, - attributes: [ - /// The node this node will be redirected to. - uri: Required = "uri", - ] -); - -generate_element!( - /// Request to delete a node. - Purge, "purge", PUBSUB_OWNER, - attributes: [ - /// The node to be configured. - node: Required = "node", - ] -); - -generate_element!( - /// A request for current subscriptions. - Subscriptions, "subscriptions", PUBSUB_OWNER, - attributes: [ - /// The node to query. - node: Required = "node", - ], - children: [ - /// The list of subscription elements returned. - subscriptions: Vec = ("subscription", PUBSUB_OWNER) => SubscriptionElem - ] -); - -generate_element!( - /// A subscription element, describing the state of a subscription. - SubscriptionElem, "subscription", PUBSUB_OWNER, - attributes: [ - /// The JID affected by this subscription. - jid: Required = "jid", - - /// The state of the subscription. - subscription: Required = "subscription", - - /// Subscription unique id. - subid: Option = "subid", - ] -); - -/// Main payload used to communicate with a PubSubOwner service. -/// -/// `` -#[derive(Debug, Clone)] -pub enum PubSubOwner { - /// Manage the affiliations of a node. - Affiliations(Affiliations), - /// Request to configure a node, with optional suggested name and suggested configuration. - Configure(Configure), - /// Request the default node configuration. - Default(Default), - /// Delete a node. - Delete(Delete), - /// Purge all items from node. - Purge(Purge), - /// Request subscriptions of a node. - Subscriptions(Subscriptions), -} - -impl IqGetPayload for PubSubOwner {} -impl IqSetPayload for PubSubOwner {} -impl IqResultPayload for PubSubOwner {} - -impl TryFrom for PubSubOwner { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "pubsub", PUBSUB_OWNER); - check_no_attributes!(elem, "pubsub"); - - let mut payload = None; - for child in elem.children() { - if child.is("configure", ns::PUBSUB_OWNER) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub owner element.", - )); - } - let configure = Configure::try_from(child.clone())?; - payload = Some(PubSubOwner::Configure(configure)); - } else { - return Err(Error::ParseError("Unknown child in pubsub element.")); - } - } - Ok(payload.ok_or(Error::ParseError("No payload in pubsub element."))?) - } -} - -impl From for Element { - fn from(pubsub: PubSubOwner) -> Element { - Element::builder("pubsub", ns::PUBSUB_OWNER) - .append_all(match pubsub { - PubSubOwner::Affiliations(affiliations) => vec![Element::from(affiliations)], - PubSubOwner::Configure(configure) => vec![Element::from(configure)], - PubSubOwner::Default(default) => vec![Element::from(default)], - PubSubOwner::Delete(delete) => vec![Element::from(delete)], - PubSubOwner::Purge(purge) => vec![Element::from(purge)], - PubSubOwner::Subscriptions(subscriptions) => vec![Element::from(subscriptions)], - }) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::data_forms::{DataForm, DataFormType, Field, FieldType}; - use jid::BareJid; - use std::str::FromStr; - - #[test] - fn affiliations() { - let elem: Element = "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - - let pubsub = PubSubOwner::Affiliations(Affiliations { - node: NodeName(String::from("foo")), - affiliations: vec![ - Affiliation { - jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()), - affiliation: AffiliationAttribute::Owner, - }, - Affiliation { - jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()), - affiliation: AffiliationAttribute::Outcast, - }, - ], - }); - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn configure() { - let elem: Element = "http://jabber.org/protocol/pubsub#node_configwhitelist" - .parse() - .unwrap(); - let elem1 = elem.clone(); - - let pubsub = PubSubOwner::Configure(Configure { - node: Some(NodeName(String::from("foo"))), - form: Some(DataForm { - type_: DataFormType::Submit, - form_type: Some(String::from(ns::PUBSUB_CONFIGURE)), - title: None, - instructions: None, - fields: vec![Field { - var: String::from("pubsub#access_model"), - type_: FieldType::ListSingle, - label: None, - required: false, - options: vec![], - values: vec![String::from("whitelist")], - media: vec![], - }], - }), - }); - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn test_serialize_configure() { - let reference: Element = "" - .parse() - .unwrap(); - - let elem: Element = "".parse().unwrap(); - - let form = DataForm::try_from(elem).unwrap(); - - let configure = PubSubOwner::Configure(Configure { - node: Some(NodeName(String::from("foo"))), - form: Some(form), - }); - let serialized: Element = configure.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn default() { - let elem: Element = "http://jabber.org/protocol/pubsub#node_configwhitelist" - .parse() - .unwrap(); - let elem1 = elem.clone(); - - let pubsub = PubSubOwner::Default(Default { - form: Some(DataForm { - type_: DataFormType::Submit, - form_type: Some(String::from(ns::PUBSUB_CONFIGURE)), - title: None, - instructions: None, - fields: vec![Field { - var: String::from("pubsub#access_model"), - type_: FieldType::ListSingle, - label: None, - required: false, - options: vec![], - values: vec![String::from("whitelist")], - media: vec![], - }], - }), - }); - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn delete() { - let elem: Element = "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - - let pubsub = PubSubOwner::Delete(Delete { - node: NodeName(String::from("foo")), - redirect: Some(Redirect { - uri: String::from("xmpp:hamlet@denmark.lit?;node=blog"), - }), - }); - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn purge() { - let elem: Element = "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - - let pubsub = PubSubOwner::Purge(Purge { - node: NodeName(String::from("foo")), - }); - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn subscriptions() { - let elem: Element = "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - - let pubsub = PubSubOwner::Subscriptions(Subscriptions { - node: NodeName(String::from("foo")), - subscriptions: vec![ - SubscriptionElem { - jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()), - subscription: Subscription::Subscribed, - subid: None, - }, - SubscriptionElem { - jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()), - subscription: Subscription::Unconfigured, - subid: None, - }, - SubscriptionElem { - jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()), - subscription: Subscription::Subscribed, - subid: Some(String::from("123-abc")), - }, - SubscriptionElem { - jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()), - subscription: Subscription::Subscribed, - subid: Some(String::from("004-yyy")), - }, - ], - }); - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } -} diff --git a/xmpp-parsers/src/pubsub/pubsub.rs b/xmpp-parsers/src/pubsub/pubsub.rs deleted file mode 100644 index 1811a8d..0000000 --- a/xmpp-parsers/src/pubsub/pubsub.rs +++ /dev/null @@ -1,772 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::DataForm; -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use crate::ns; -use crate::pubsub::{ - AffiliationAttribute, Item as PubSubItem, NodeName, Subscription, SubscriptionId, -}; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::convert::TryFrom; - -// TODO: a better solution would be to split this into a query and a result elements, like for -// XEP-0030. -generate_element!( - /// A list of affiliations you have on a service, or on a node. - Affiliations, "affiliations", PUBSUB, - attributes: [ - /// The optional node name this request pertains to. - node: Option = "node", - ], - children: [ - /// The actual list of affiliation elements. - affiliations: Vec = ("affiliation", PUBSUB) => Affiliation - ] -); - -generate_element!( - /// An affiliation element. - Affiliation, "affiliation", PUBSUB, - attributes: [ - /// The node this affiliation pertains to. - node: Required = "node", - - /// The affiliation you currently have on this node. - affiliation: Required = "affiliation", - ] -); - -generate_element!( - /// Request to configure a new node. - Configure, "configure", PUBSUB, - children: [ - /// The form to configure it. - form: Option = ("x", DATA_FORMS) => DataForm - ] -); - -generate_element!( - /// Request to create a new node. - Create, "create", PUBSUB, - attributes: [ - /// The node name to create, if `None` the service will generate one. - node: Option = "node", - ] -); - -generate_element!( - /// Request for a default node configuration. - Default, "default", PUBSUB, - attributes: [ - /// The node targeted by this request, otherwise the entire service. - node: Option = "node", - - // TODO: do we really want to support collection nodes? - // type: Option = "type", - ] -); - -generate_element!( - /// A request for a list of items. - Items, "items", PUBSUB, - attributes: [ - // TODO: should be an xs:positiveInteger, that is, an unbounded int ≥ 1. - /// Maximum number of items returned. - max_items: Option = "max_items", - - /// The node queried by this request. - node: Required = "node", - - /// The subscription identifier related to this request. - subid: Option = "subid", - ], - children: [ - /// The actual list of items returned. - items: Vec = ("item", PUBSUB) => Item - ] -); - -impl Items { - /// Create a new items request. - pub fn new(node: &str) -> Items { - Items { - node: NodeName(String::from(node)), - max_items: None, - subid: None, - items: Vec::new(), - } - } -} - -/// Response wrapper for a PubSub ``. -#[derive(Debug, Clone, PartialEq)] -pub struct Item(pub PubSubItem); - -impl_pubsub_item!(Item, PUBSUB); - -generate_element!( - /// The options associated to a subscription request. - Options, "options", PUBSUB, - attributes: [ - /// The JID affected by this request. - jid: Required = "jid", - - /// The node affected by this request. - node: Option = "node", - - /// The subscription identifier affected by this request. - subid: Option = "subid", - ], - children: [ - /// The form describing the subscription. - form: Option = ("x", DATA_FORMS) => DataForm - ] -); - -generate_element!( - /// Request to publish items to a node. - Publish, "publish", PUBSUB, - attributes: [ - /// The target node for this operation. - node: Required = "node", - ], - children: [ - /// The items you want to publish. - items: Vec = ("item", PUBSUB) => Item - ] -); - -generate_element!( - /// The options associated to a publish request. - PublishOptions, "publish-options", PUBSUB, - children: [ - /// The form describing these options. - form: Option = ("x", DATA_FORMS) => DataForm - ] -); - -generate_attribute!( - /// Whether a retract request should notify subscribers or not. - Notify, - "notify", - bool -); - -generate_element!( - /// A request to retract some items from a node. - Retract, "retract", PUBSUB, - attributes: [ - /// The node affected by this request. - node: Required = "node", - - /// Whether a retract request should notify subscribers or not. - notify: Default = "notify", - ], - children: [ - /// The items affected by this request. - items: Vec = ("item", PUBSUB) => Item - ] -); - -/// Indicate that the subscription can be configured. -#[derive(Debug, Clone, PartialEq)] -pub struct SubscribeOptions { - /// If `true`, the configuration is actually required. - required: bool, -} - -impl TryFrom for SubscribeOptions { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "subscribe-options", PUBSUB); - check_no_attributes!(elem, "subscribe-options"); - let mut required = false; - for child in elem.children() { - if child.is("required", ns::PUBSUB) { - if required { - return Err(Error::ParseError( - "More than one required element in subscribe-options.", - )); - } - required = true; - } else { - return Err(Error::ParseError( - "Unknown child in subscribe-options element.", - )); - } - } - Ok(SubscribeOptions { required }) - } -} - -impl From for Element { - fn from(subscribe_options: SubscribeOptions) -> Element { - Element::builder("subscribe-options", ns::PUBSUB) - .append_all(if subscribe_options.required { - Some(Element::builder("required", ns::PUBSUB)) - } else { - None - }) - .build() - } -} - -generate_element!( - /// A request to subscribe a JID to a node. - Subscribe, "subscribe", PUBSUB, - attributes: [ - /// The JID being subscribed. - jid: Required = "jid", - - /// The node to subscribe to. - node: Option = "node", - ] -); - -generate_element!( - /// A request for current subscriptions. - Subscriptions, "subscriptions", PUBSUB, - attributes: [ - /// The node to query. - node: Option = "node", - ], - children: [ - /// The list of subscription elements returned. - subscription: Vec = ("subscription", PUBSUB) => SubscriptionElem - ] -); - -generate_element!( - /// A subscription element, describing the state of a subscription. - SubscriptionElem, "subscription", PUBSUB, - attributes: [ - /// The JID affected by this subscription. - jid: Required = "jid", - - /// The node affected by this subscription. - node: Option = "node", - - /// The subscription identifier for this subscription. - subid: Option = "subid", - - /// The state of the subscription. - subscription: Option = "subscription", - ], - children: [ - /// The options related to this subscription. - subscribe_options: Option = ("subscribe-options", PUBSUB) => SubscribeOptions - ] -); - -generate_element!( - /// An unsubscribe request. - Unsubscribe, "unsubscribe", PUBSUB, - attributes: [ - /// The JID affected by this request. - jid: Required = "jid", - - /// The node affected by this request. - node: Option = "node", - - /// The subscription identifier for this subscription. - subid: Option = "subid", - ] -); - -/// Main payload used to communicate with a PubSub service. -/// -/// `` -#[derive(Debug, Clone, PartialEq)] -pub enum PubSub { - /// Request to create a new node, with optional suggested name and suggested configuration. - Create { - /// The create request. - create: Create, - - /// The configure request for the new node. - configure: Option, - }, - - /// A subcribe request. - Subscribe { - /// The subscribe request. - subscribe: Option, - - /// The options related to this subscribe request. - options: Option, - }, - - /// Request to publish items to a node, with optional options. - Publish { - /// The publish request. - publish: Publish, - - /// The options related to this publish request. - publish_options: Option, - }, - - /// A list of affiliations you have on a service, or on a node. - Affiliations(Affiliations), - - /// Request for a default node configuration. - Default(Default), - - /// A request for a list of items. - Items(Items), - - /// A request to retract some items from a node. - Retract(Retract), - - /// A request about a subscription. - Subscription(SubscriptionElem), - - /// A request for current subscriptions. - Subscriptions(Subscriptions), - - /// An unsubscribe request. - Unsubscribe(Unsubscribe), -} - -impl IqGetPayload for PubSub {} -impl IqSetPayload for PubSub {} -impl IqResultPayload for PubSub {} - -impl TryFrom for PubSub { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "pubsub", PUBSUB); - check_no_attributes!(elem, "pubsub"); - - let mut payload = None; - for child in elem.children() { - if child.is("create", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let create = Create::try_from(child.clone())?; - payload = Some(PubSub::Create { - create, - configure: None, - }); - } else if child.is("subscribe", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let subscribe = Subscribe::try_from(child.clone())?; - payload = Some(PubSub::Subscribe { - subscribe: Some(subscribe), - options: None, - }); - } else if child.is("options", ns::PUBSUB) { - if let Some(PubSub::Subscribe { subscribe, options }) = payload { - if options.is_some() { - return Err(Error::ParseError( - "Options is already defined in pubsub element.", - )); - } - let options = Some(Options::try_from(child.clone())?); - payload = Some(PubSub::Subscribe { subscribe, options }); - } else if payload.is_none() { - let options = Options::try_from(child.clone())?; - payload = Some(PubSub::Subscribe { - subscribe: None, - options: Some(options), - }); - } else { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - } else if child.is("configure", ns::PUBSUB) { - if let Some(PubSub::Create { create, configure }) = payload { - if configure.is_some() { - return Err(Error::ParseError( - "Configure is already defined in pubsub element.", - )); - } - let configure = Some(Configure::try_from(child.clone())?); - payload = Some(PubSub::Create { create, configure }); - } else { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - } else if child.is("publish", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let publish = Publish::try_from(child.clone())?; - payload = Some(PubSub::Publish { - publish, - publish_options: None, - }); - } else if child.is("publish-options", ns::PUBSUB) { - if let Some(PubSub::Publish { - publish, - publish_options, - }) = payload - { - if publish_options.is_some() { - return Err(Error::ParseError( - "Publish-options are already defined in pubsub element.", - )); - } - let publish_options = Some(PublishOptions::try_from(child.clone())?); - payload = Some(PubSub::Publish { - publish, - publish_options, - }); - } else { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - } else if child.is("affiliations", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let affiliations = Affiliations::try_from(child.clone())?; - payload = Some(PubSub::Affiliations(affiliations)); - } else if child.is("default", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let default = Default::try_from(child.clone())?; - payload = Some(PubSub::Default(default)); - } else if child.is("items", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let items = Items::try_from(child.clone())?; - payload = Some(PubSub::Items(items)); - } else if child.is("retract", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let retract = Retract::try_from(child.clone())?; - payload = Some(PubSub::Retract(retract)); - } else if child.is("subscription", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let subscription = SubscriptionElem::try_from(child.clone())?; - payload = Some(PubSub::Subscription(subscription)); - } else if child.is("subscriptions", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let subscriptions = Subscriptions::try_from(child.clone())?; - payload = Some(PubSub::Subscriptions(subscriptions)); - } else if child.is("unsubscribe", ns::PUBSUB) { - if payload.is_some() { - return Err(Error::ParseError( - "Payload is already defined in pubsub element.", - )); - } - let unsubscribe = Unsubscribe::try_from(child.clone())?; - payload = Some(PubSub::Unsubscribe(unsubscribe)); - } else { - return Err(Error::ParseError("Unknown child in pubsub element.")); - } - } - Ok(payload.ok_or(Error::ParseError("No payload in pubsub element."))?) - } -} - -impl From for Element { - fn from(pubsub: PubSub) -> Element { - Element::builder("pubsub", ns::PUBSUB) - .append_all(match pubsub { - PubSub::Create { create, configure } => { - let mut elems = vec![Element::from(create)]; - if let Some(configure) = configure { - elems.push(Element::from(configure)); - } - elems - } - PubSub::Subscribe { subscribe, options } => { - let mut elems = vec![]; - if let Some(subscribe) = subscribe { - elems.push(Element::from(subscribe)); - } - if let Some(options) = options { - elems.push(Element::from(options)); - } - elems - } - PubSub::Publish { - publish, - publish_options, - } => { - let mut elems = vec![Element::from(publish)]; - if let Some(publish_options) = publish_options { - elems.push(Element::from(publish_options)); - } - elems - } - PubSub::Affiliations(affiliations) => vec![Element::from(affiliations)], - PubSub::Default(default) => vec![Element::from(default)], - PubSub::Items(items) => vec![Element::from(items)], - PubSub::Retract(retract) => vec![Element::from(retract)], - PubSub::Subscription(subscription) => vec![Element::from(subscription)], - PubSub::Subscriptions(subscriptions) => vec![Element::from(subscriptions)], - PubSub::Unsubscribe(unsubscribe) => vec![Element::from(unsubscribe)], - }) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::data_forms::{DataForm, DataFormType, Field, FieldType}; - use jid::FullJid; - - #[test] - fn create() { - let elem: Element = "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let pubsub = PubSub::try_from(elem).unwrap(); - match pubsub.clone() { - PubSub::Create { create, configure } => { - assert!(create.node.is_none()); - assert!(configure.is_none()); - } - _ => panic!(), - } - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - - let elem: Element = - "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let pubsub = PubSub::try_from(elem).unwrap(); - match pubsub.clone() { - PubSub::Create { create, configure } => { - assert_eq!(&create.node.unwrap().0, "coucou"); - assert!(configure.is_none()); - } - _ => panic!(), - } - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn create_and_configure_empty() { - let elem: Element = - "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let pubsub = PubSub::try_from(elem).unwrap(); - match pubsub.clone() { - PubSub::Create { create, configure } => { - assert!(create.node.is_none()); - assert!(configure.unwrap().form.is_none()); - } - _ => panic!(), - } - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn create_and_configure_simple() { - // XXX: Do we want xmpp-parsers to always specify the field type in the output Element? - let elem: Element = "http://jabber.org/protocol/pubsub#node_configwhitelist" - .parse() - .unwrap(); - let elem1 = elem.clone(); - - let pubsub = PubSub::Create { - create: Create { - node: Some(NodeName(String::from("foo"))), - }, - configure: Some(Configure { - form: Some(DataForm { - type_: DataFormType::Submit, - form_type: Some(String::from(ns::PUBSUB_CONFIGURE)), - title: None, - instructions: None, - fields: vec![Field { - var: String::from("pubsub#access_model"), - type_: FieldType::ListSingle, - label: None, - required: false, - options: vec![], - values: vec![String::from("whitelist")], - media: vec![], - }], - }), - }), - }; - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn publish() { - let elem: Element = - "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let pubsub = PubSub::try_from(elem).unwrap(); - match pubsub.clone() { - PubSub::Publish { - publish, - publish_options, - } => { - assert_eq!(&publish.node.0, "coucou"); - assert!(publish_options.is_none()); - } - _ => panic!(), - } - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn publish_with_publish_options() { - let elem: Element = "".parse().unwrap(); - let elem1 = elem.clone(); - let pubsub = PubSub::try_from(elem).unwrap(); - match pubsub.clone() { - PubSub::Publish { - publish, - publish_options, - } => { - assert_eq!(&publish.node.0, "coucou"); - assert!(publish_options.unwrap().form.is_none()); - } - _ => panic!(), - } - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn invalid_empty_pubsub() { - let elem: Element = "" - .parse() - .unwrap(); - let error = PubSub::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "No payload in pubsub element."); - } - - #[test] - fn publish_option() { - let elem: Element = "http://jabber.org/protocol/pubsub#publish-options".parse().unwrap(); - let publish_options = PublishOptions::try_from(elem).unwrap(); - assert_eq!( - &publish_options.form.unwrap().form_type.unwrap(), - "http://jabber.org/protocol/pubsub#publish-options" - ); - } - - #[test] - fn subscribe_options() { - let elem1: Element = "" - .parse() - .unwrap(); - let subscribe_options1 = SubscribeOptions::try_from(elem1).unwrap(); - assert_eq!(subscribe_options1.required, false); - - let elem2: Element = "".parse().unwrap(); - let subscribe_options2 = SubscribeOptions::try_from(elem2).unwrap(); - assert_eq!(subscribe_options2.required, true); - } - - #[test] - fn test_options_without_subscribe() { - let elem: Element = "".parse().unwrap(); - let elem1 = elem.clone(); - let pubsub = PubSub::try_from(elem).unwrap(); - match pubsub.clone() { - PubSub::Subscribe { subscribe, options } => { - assert!(subscribe.is_none()); - assert!(options.is_some()); - } - _ => panic!(), - } - - let elem2 = Element::from(pubsub); - assert_eq!(elem1, elem2); - } - - #[test] - fn test_serialize_options() { - let reference: Element = "" - .parse() - .unwrap(); - - let elem: Element = "".parse().unwrap(); - - let form = DataForm::try_from(elem).unwrap(); - - let options = Options { - jid: Jid::Full(FullJid::new("juliet", "capulet.lit", "balcony")), - node: None, - subid: None, - form: Some(form), - }; - let serialized: Element = options.into(); - assert_eq!(serialized, reference); - } - - #[test] - fn test_serialize_publish_options() { - let reference: Element = "" - .parse() - .unwrap(); - - let elem: Element = "".parse().unwrap(); - - let form = DataForm::try_from(elem).unwrap(); - - let options = PublishOptions { form: Some(form) }; - let serialized: Element = options.into(); - assert_eq!(serialized, reference); - } -} diff --git a/xmpp-parsers/src/receipts.rs b/xmpp-parsers/src/receipts.rs deleted file mode 100644 index 62fd7b7..0000000 --- a/xmpp-parsers/src/receipts.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; - -generate_empty_element!( - /// Requests that this message is acked by the final recipient once - /// received. - Request, - "request", - RECEIPTS -); - -impl MessagePayload for Request {} - -generate_element!( - /// Notes that a previous message has correctly been received, it is - /// referenced by its 'id' attribute. - Received, "received", RECEIPTS, - attributes: [ - /// The 'id' attribute of the received message. - id: Required = "id", - ] -); - -impl MessagePayload for Received {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ns; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Request, 0); - assert_size!(Received, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Request, 0); - assert_size!(Received, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - Request::try_from(elem).unwrap(); - - let elem: Element = "" - .parse() - .unwrap(); - Received::try_from(elem).unwrap(); - } - - #[test] - fn test_missing_id() { - let elem: Element = "".parse().unwrap(); - let error = Received::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'id' missing."); - } - - #[test] - fn test_serialise() { - let receipt = Request; - let elem: Element = receipt.into(); - assert!(elem.is("request", ns::RECEIPTS)); - assert_eq!(elem.attrs().count(), 0); - - let receipt = Received { - id: String::from("coucou"), - }; - let elem: Element = receipt.into(); - assert!(elem.is("received", ns::RECEIPTS)); - assert_eq!(elem.attr("id"), Some("coucou")); - } -} diff --git a/xmpp-parsers/src/roster.rs b/xmpp-parsers/src/roster.rs deleted file mode 100644 index 1695296..0000000 --- a/xmpp-parsers/src/roster.rs +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload}; -use jid::BareJid; - -generate_elem_id!( - /// Represents a group a contact is part of. - Group, - "group", - ROSTER -); - -generate_attribute!( - /// The state of your mutual subscription with a contact. - Subscription, "subscription", { - /// The user doesn’t have any subscription to this contact’s presence, - /// and neither does this contact. - None => "none", - - /// Only this contact has a subscription with you, not the opposite. - From => "from", - - /// Only you have a subscription with this contact, not the opposite. - To => "to", - - /// Both you and your contact are subscribed to each other’s presence. - Both => "both", - - /// In a roster set, this asks the server to remove this contact item - /// from your roster. - Remove => "remove", - }, Default = None -); - -generate_attribute!( - /// The sub-state of subscription with a contact. - Ask, "ask", ( - /// Pending sub-state of the 'none' subscription state. - Subscribe => "subscribe" - ) -); - -generate_element!( - /// Contact from the user’s contact list. - Item, "item", ROSTER, - attributes: [ - /// JID of this contact. - jid: Required = "jid", - - /// Name of this contact. - name: OptionEmpty = "name", - - /// Subscription status of this contact. - subscription: Default = "subscription", - - /// Indicates “Pending Out” sub-states for this contact. - ask: Default = "ask", - ], - - children: [ - /// Groups this contact is part of. - groups: Vec = ("group", ROSTER) => Group - ] -); - -generate_element!( - /// The contact list of the user. - Roster, "query", ROSTER, - attributes: [ - /// Version of the contact list. - /// - /// This is an opaque string that should only be sent back to the server on - /// a new connection, if this client is storing the contact list between - /// connections. - ver: Option = "ver" - ], - children: [ - /// List of the contacts of the user. - items: Vec = ("item", ROSTER) => Item - ] -); - -impl IqGetPayload for Roster {} -impl IqSetPayload for Roster {} -impl IqResultPayload for Roster {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use std::convert::TryFrom; - use std::str::FromStr; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Group, 12); - assert_size!(Subscription, 1); - assert_size!(Ask, 1); - assert_size!(Item, 52); - assert_size!(Roster, 24); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Group, 24); - assert_size!(Subscription, 1); - assert_size!(Ask, 1); - assert_size!(Item, 104); - assert_size!(Roster, 48); - } - - #[test] - fn test_get() { - let elem: Element = "".parse().unwrap(); - let roster = Roster::try_from(elem).unwrap(); - assert!(roster.ver.is_none()); - assert!(roster.items.is_empty()); - } - - #[test] - fn test_result() { - let elem: Element = "".parse().unwrap(); - let roster = Roster::try_from(elem).unwrap(); - assert_eq!(roster.ver, Some(String::from("ver7"))); - assert_eq!(roster.items.len(), 2); - - let elem2: Element = "".parse().unwrap(); - let roster2 = Roster::try_from(elem2).unwrap(); - assert_eq!(roster.items, roster2.items); - - let elem: Element = "" - .parse() - .unwrap(); - let roster = Roster::try_from(elem).unwrap(); - assert_eq!(roster.ver, Some(String::from("ver9"))); - assert!(roster.items.is_empty()); - - let elem: Element = r#" - - - Friends - - - - - MyBuddies - - -"# - .parse() - .unwrap(); - let roster = Roster::try_from(elem).unwrap(); - assert_eq!(roster.ver, Some(String::from("ver11"))); - assert_eq!(roster.items.len(), 4); - assert_eq!(roster.items[0].jid, BareJid::new("romeo", "example.net")); - assert_eq!(roster.items[0].name, Some(String::from("Romeo"))); - assert_eq!(roster.items[0].subscription, Subscription::Both); - assert_eq!(roster.items[0].ask, Ask::None); - assert_eq!( - roster.items[0].groups, - vec!(Group::from_str("Friends").unwrap()) - ); - - assert_eq!(roster.items[3].jid, BareJid::new("contact", "example.org")); - assert_eq!(roster.items[3].name, Some(String::from("MyContact"))); - assert_eq!(roster.items[3].subscription, Subscription::None); - assert_eq!(roster.items[3].ask, Ask::Subscribe); - assert_eq!( - roster.items[3].groups, - vec!(Group::from_str("MyBuddies").unwrap()) - ); - } - - #[test] - fn test_multiple_groups() { - let elem: Element = "AB" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let roster = Roster::try_from(elem).unwrap(); - assert!(roster.ver.is_none()); - assert_eq!(roster.items.len(), 1); - assert_eq!(roster.items[0].jid, BareJid::new("test", "example.org")); - assert_eq!(roster.items[0].name, None); - assert_eq!(roster.items[0].groups.len(), 2); - assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap()); - assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap()); - let elem2 = roster.into(); - assert_eq!(elem1, elem2); - } - - #[test] - fn test_set() { - let elem: Element = - "" - .parse() - .unwrap(); - let roster = Roster::try_from(elem).unwrap(); - assert!(roster.ver.is_none()); - assert_eq!(roster.items.len(), 1); - - let elem: Element = r#" - - - Servants - - -"# - .parse() - .unwrap(); - let roster = Roster::try_from(elem).unwrap(); - assert!(roster.ver.is_none()); - assert_eq!(roster.items.len(), 1); - assert_eq!(roster.items[0].jid, BareJid::new("nurse", "example.com")); - assert_eq!(roster.items[0].name, Some(String::from("Nurse"))); - assert_eq!(roster.items[0].groups.len(), 1); - assert_eq!( - roster.items[0].groups[0], - Group::from_str("Servants").unwrap() - ); - - let elem: Element = r#" - - - -"# - .parse() - .unwrap(); - let roster = Roster::try_from(elem).unwrap(); - assert!(roster.ver.is_none()); - assert_eq!(roster.items.len(), 1); - assert_eq!(roster.items[0].jid, BareJid::new("nurse", "example.com")); - assert!(roster.items[0].name.is_none()); - assert!(roster.items[0].groups.is_empty()); - assert_eq!(roster.items[0].subscription, Subscription::Remove); - } - - #[cfg(not(feature = "disable-validation"))] - #[test] - fn test_invalid() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Roster::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in query element."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = Roster::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown attribute in query element."); - } - - #[test] - fn test_invalid_item() { - let elem: Element = "" - .parse() - .unwrap(); - let error = Roster::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'jid' missing."); - - /* - let elem: Element = "".parse().unwrap(); - let error = Roster::try_from(elem).unwrap_err(); - let error = match error { - Error::JidParseError(error) => error, - _ => panic!(), - }; - assert_eq!(error.description(), "Invalid JID, I guess?"); - */ - - let elem: Element = - "" - .parse() - .unwrap(); - let error = Roster::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in item element."); - } -} diff --git a/xmpp-parsers/src/rsm.rs b/xmpp-parsers/src/rsm.rs deleted file mode 100644 index f5649c7..0000000 --- a/xmpp-parsers/src/rsm.rs +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use std::convert::TryFrom; - -/// Requests paging through a potentially big set of items (represented by an -/// UID). -#[derive(Debug, Clone, PartialEq)] -pub struct SetQuery { - /// Limit the number of items, or use the recipient’s defaults if None. - pub max: Option, - - /// The UID after which to give results, or if None it is the element - /// “before” the first item, effectively an index of negative one. - pub after: Option, - - /// The UID before which to give results, or if None it starts with the - /// last page of the full set. - pub before: Option, - - /// Numerical index of the page (deprecated). - pub index: Option, -} - -impl TryFrom for SetQuery { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "set", RSM, "RSM set"); - let mut set = SetQuery { - max: None, - after: None, - before: None, - index: None, - }; - for child in elem.children() { - if child.is("max", ns::RSM) { - if set.max.is_some() { - return Err(Error::ParseError("Set can’t have more than one max.")); - } - set.max = Some(child.text().parse()?); - } else if child.is("after", ns::RSM) { - if set.after.is_some() { - return Err(Error::ParseError("Set can’t have more than one after.")); - } - set.after = Some(child.text()); - } else if child.is("before", ns::RSM) { - if set.before.is_some() { - return Err(Error::ParseError("Set can’t have more than one before.")); - } - set.before = Some(child.text()); - } else if child.is("index", ns::RSM) { - if set.index.is_some() { - return Err(Error::ParseError("Set can’t have more than one index.")); - } - set.index = Some(child.text().parse()?); - } else { - return Err(Error::ParseError("Unknown child in set element.")); - } - } - Ok(set) - } -} - -impl From for Element { - fn from(set: SetQuery) -> Element { - Element::builder("set", ns::RSM) - .append_all( - set.max - .map(|max| Element::builder("max", ns::RSM).append(format!("{}", max))), - ) - .append_all( - set.after - .map(|after| Element::builder("after", ns::RSM).append(after)), - ) - .append_all( - set.before - .map(|before| Element::builder("before", ns::RSM).append(before)), - ) - .append_all( - set.index - .map(|index| Element::builder("index", ns::RSM).append(format!("{}", index))), - ) - .build() - } -} - -/// Describes the paging result of a [query](struct.SetQuery.html). -#[derive(Debug, Clone, PartialEq)] -pub struct SetResult { - /// The UID of the first item of the page. - pub first: Option, - - /// The position of the [first item](#structfield.first) in the full set - /// (which may be approximate). - pub first_index: Option, - - /// The UID of the last item of the page. - pub last: Option, - - /// How many items there are in the full set (which may be approximate). - pub count: Option, -} - -impl TryFrom for SetResult { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "set", RSM, "RSM set"); - let mut set = SetResult { - first: None, - first_index: None, - last: None, - count: None, - }; - for child in elem.children() { - if child.is("first", ns::RSM) { - if set.first.is_some() { - return Err(Error::ParseError("Set can’t have more than one first.")); - } - set.first_index = get_attr!(child, "index", Option); - set.first = Some(child.text()); - } else if child.is("last", ns::RSM) { - if set.last.is_some() { - return Err(Error::ParseError("Set can’t have more than one last.")); - } - set.last = Some(child.text()); - } else if child.is("count", ns::RSM) { - if set.count.is_some() { - return Err(Error::ParseError("Set can’t have more than one count.")); - } - set.count = Some(child.text().parse()?); - } else { - return Err(Error::ParseError("Unknown child in set element.")); - } - } - Ok(set) - } -} - -impl From for Element { - fn from(set: SetResult) -> Element { - let first = set.first.clone().map(|first| { - Element::builder("first", ns::RSM) - .attr("index", set.first_index) - .append(first) - }); - Element::builder("set", ns::RSM) - .append_all(first) - .append_all( - set.last - .map(|last| Element::builder("last", ns::RSM).append(last)), - ) - .append_all( - set.count - .map(|count| Element::builder("count", ns::RSM).append(format!("{}", count))), - ) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(SetQuery, 40); - assert_size!(SetResult, 40); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(SetQuery, 80); - assert_size!(SetResult, 80); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let set = SetQuery::try_from(elem).unwrap(); - assert_eq!(set.max, None); - assert_eq!(set.after, None); - assert_eq!(set.before, None); - assert_eq!(set.index, None); - - let elem: Element = "" - .parse() - .unwrap(); - let set = SetResult::try_from(elem).unwrap(); - match set.first { - Some(_) => panic!(), - None => (), - } - assert_eq!(set.last, None); - assert_eq!(set.count, None); - } - - #[test] - fn test_unknown() { - let elem: Element = "" - .parse() - .unwrap(); - let error = SetQuery::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "This is not a RSM set element."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = SetResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "This is not a RSM set element."); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = SetQuery::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in set element."); - - let elem: Element = "" - .parse() - .unwrap(); - let error = SetResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in set element."); - } - - #[test] - fn test_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let rsm = SetQuery { - max: None, - after: None, - before: None, - index: None, - }; - let elem2 = rsm.into(); - assert_eq!(elem, elem2); - - let elem: Element = "" - .parse() - .unwrap(); - let rsm = SetResult { - first: None, - first_index: None, - last: None, - count: None, - }; - let elem2 = rsm.into(); - assert_eq!(elem, elem2); - } - - #[test] - fn test_first_index() { - let elem: Element = - "coucou" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let set = SetResult::try_from(elem).unwrap(); - assert_eq!(set.first, Some(String::from("coucou"))); - assert_eq!(set.first_index, Some(4)); - - let set2 = SetResult { - first: Some(String::from("coucou")), - first_index: Some(4), - last: None, - count: None, - }; - let elem2 = set2.into(); - assert_eq!(elem1, elem2); - } -} diff --git a/xmpp-parsers/src/sasl.rs b/xmpp-parsers/src/sasl.rs deleted file mode 100644 index a486c13..0000000 --- a/xmpp-parsers/src/sasl.rs +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::util::error::Error; -use crate::util::helpers::Base64; -use crate::Element; -use std::collections::BTreeMap; -use std::convert::TryFrom; - -generate_attribute!( - /// The list of available SASL mechanisms. - Mechanism, "mechanism", { - /// Uses no hashing mechanism and transmit the password in clear to the - /// server, using a single step. - Plain => "PLAIN", - - /// Challenge-based mechanism using HMAC and SHA-1, allows both the - /// client and the server to avoid having to store the password in - /// clear. - /// - /// See https://tools.ietf.org/html/rfc5802 - ScramSha1 => "SCRAM-SHA-1", - - /// Same as [ScramSha1](#structfield.ScramSha1), with the addition of - /// channel binding. - ScramSha1Plus => "SCRAM-SHA-1-PLUS", - - /// Same as [ScramSha1](#structfield.ScramSha1), but using SHA-256 - /// instead of SHA-1 as the hash function. - ScramSha256 => "SCRAM-SHA-256", - - /// Same as [ScramSha256](#structfield.ScramSha256), with the addition - /// of channel binding. - ScramSha256Plus => "SCRAM-SHA-256-PLUS", - - /// Creates a temporary JID on login, which will be destroyed on - /// disconnect. - Anonymous => "ANONYMOUS", - } -); - -generate_element!( - /// The first step of the SASL process, selecting the mechanism and sending - /// the first part of the handshake. - Auth, "auth", SASL, - attributes: [ - /// The mechanism used. - mechanism: Required = "mechanism" - ], - text: ( - /// The content of the handshake. - data: Base64> - ) -); - -generate_element!( - /// In case the mechanism selected at the [auth](struct.Auth.html) step - /// requires a second step, the server sends this element with additional - /// data. - Challenge, "challenge", SASL, - text: ( - /// The challenge data. - data: Base64> - ) -); - -generate_element!( - /// In case the mechanism selected at the [auth](struct.Auth.html) step - /// requires a second step, this contains the client’s response to the - /// server’s [challenge](struct.Challenge.html). - Response, "response", SASL, - text: ( - /// The response data. - data: Base64> - ) -); - -generate_empty_element!( - /// Sent by the client at any point after [auth](struct.Auth.html) if it - /// wants to cancel the current authentication process. - Abort, - "abort", - SASL -); - -generate_element!( - /// Sent by the server on SASL success. - Success, "success", SASL, - text: ( - /// Possible data sent on success. - data: Base64> - ) -); - -generate_element_enum!( - /// List of possible failure conditions for SASL. - DefinedCondition, "defined-condition", SASL, { - /// The client aborted the authentication with - /// [abort](struct.Abort.html). - Aborted => "aborted", - - /// The account the client is trying to authenticate against has been - /// disabled. - AccountDisabled => "account-disabled", - - /// The credentials for this account have expired. - CredentialsExpired => "credentials-expired", - - /// You must enable StartTLS or use direct TLS before using this - /// authentication mechanism. - EncryptionRequired => "encryption-required", - - /// The base64 data sent by the client is invalid. - IncorrectEncoding => "incorrect-encoding", - - /// The authzid provided by the client is invalid. - InvalidAuthzid => "invalid-authzid", - - /// The client tried to use an invalid mechanism, or none. - InvalidMechanism => "invalid-mechanism", - - /// The client sent a bad request. - MalformedRequest => "malformed-request", - - /// The mechanism selected is weaker than what the server allows. - MechanismTooWeak => "mechanism-too-weak", - - /// The credentials provided are invalid. - NotAuthorized => "not-authorized", - - /// The server encountered an issue which may be fixed later, the - /// client should retry at some point. - TemporaryAuthFailure => "temporary-auth-failure", - } -); - -type Lang = String; - -/// Sent by the server on SASL failure. -#[derive(Debug, Clone)] -pub struct Failure { - /// One of the allowed defined-conditions for SASL. - pub defined_condition: DefinedCondition, - - /// A human-readable explanation for the failure. - pub texts: BTreeMap, -} - -impl TryFrom for Failure { - type Error = Error; - - fn try_from(root: Element) -> Result { - check_self!(root, "failure", SASL); - check_no_attributes!(root, "failure"); - - let mut defined_condition = None; - let mut texts = BTreeMap::new(); - - for child in root.children() { - if child.is("text", ns::SASL) { - check_no_unknown_attributes!(child, "text", ["xml:lang"]); - check_no_children!(child, "text"); - let lang = get_attr!(child, "xml:lang", Default); - if texts.insert(lang, child.text()).is_some() { - return Err(Error::ParseError( - "Text element present twice for the same xml:lang in failure element.", - )); - } - } else if child.has_ns(ns::SASL) { - if defined_condition.is_some() { - return Err(Error::ParseError( - "Failure must not have more than one defined-condition.", - )); - } - check_no_attributes!(child, "defined-condition"); - check_no_children!(child, "defined-condition"); - let condition = match DefinedCondition::try_from(child.clone()) { - Ok(condition) => condition, - // TODO: do we really want to eat this error? - Err(_) => DefinedCondition::NotAuthorized, - }; - defined_condition = Some(condition); - } else { - return Err(Error::ParseError("Unknown element in Failure.")); - } - } - let defined_condition = - defined_condition.ok_or(Error::ParseError("Failure must have a defined-condition."))?; - - Ok(Failure { - defined_condition, - texts, - }) - } -} - -impl From for Element { - fn from(failure: Failure) -> Element { - Element::builder("failure", ns::SASL) - .append(failure.defined_condition) - .append_all(failure.texts.into_iter().map(|(lang, text)| { - Element::builder("text", ns::SASL) - .attr("xml:lang", lang) - .append(text) - })) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Mechanism, 1); - assert_size!(Auth, 16); - assert_size!(Challenge, 12); - assert_size!(Response, 12); - assert_size!(Abort, 0); - assert_size!(Success, 12); - assert_size!(DefinedCondition, 1); - assert_size!(Failure, 16); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Mechanism, 1); - assert_size!(Auth, 32); - assert_size!(Challenge, 24); - assert_size!(Response, 24); - assert_size!(Abort, 0); - assert_size!(Success, 24); - assert_size!(DefinedCondition, 1); - assert_size!(Failure, 32); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let auth = Auth::try_from(elem).unwrap(); - assert_eq!(auth.mechanism, Mechanism::Plain); - assert!(auth.data.is_empty()); - } - - #[test] - fn section_6_5_1() { - let elem: Element = - "" - .parse() - .unwrap(); - let failure = Failure::try_from(elem).unwrap(); - assert_eq!(failure.defined_condition, DefinedCondition::Aborted); - assert!(failure.texts.is_empty()); - } - - #[test] - fn section_6_5_2() { - let elem: Element = " - - Call 212-555-1212 for assistance. - " - .parse() - .unwrap(); - let failure = Failure::try_from(elem).unwrap(); - assert_eq!(failure.defined_condition, DefinedCondition::AccountDisabled); - assert_eq!( - failure.texts["en"], - String::from("Call 212-555-1212 for assistance.") - ); - } - - /// Some servers apparently use a non-namespaced 'lang' attribute, which is invalid as not part - /// of the schema. This tests whether we can parse it when disabling validation. - #[cfg(feature = "disable-validation")] - #[test] - fn invalid_failure_with_non_prefixed_text_lang() { - let elem: Element = " - - Invalid username or password - " - .parse() - .unwrap(); - let failure = Failure::try_from(elem).unwrap(); - assert_eq!(failure.defined_condition, DefinedCondition::NotAuthorized); - assert_eq!( - failure.texts[""], - String::from("Invalid username or password") - ); - } -} diff --git a/xmpp-parsers/src/server_info.rs b/xmpp-parsers/src/server_info.rs deleted file mode 100644 index 3ad9e00..0000000 --- a/xmpp-parsers/src/server_info.rs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (C) 2019 Maxime “pep” Buquet -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::data_forms::{DataForm, DataFormType, Field, FieldType}; -use crate::ns; -use crate::util::error::Error; -use std::convert::TryFrom; - -/// Structure representing a `http://jabber.org/network/serverinfo` form type. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct ServerInfo { - /// Abuse addresses - pub abuse: Vec, - - /// Admin addresses - pub admin: Vec, - - /// Feedback addresses - pub feedback: Vec, - - /// Sales addresses - pub sales: Vec, - - /// Security addresses - pub security: Vec, - - /// Support addresses - pub support: Vec, -} - -impl TryFrom for ServerInfo { - type Error = Error; - - fn try_from(form: DataForm) -> Result { - if form.type_ != DataFormType::Result_ { - return Err(Error::ParseError("Wrong type of form.")); - } - if form.form_type != Some(String::from(ns::SERVER_INFO)) { - return Err(Error::ParseError("Wrong FORM_TYPE for form.")); - } - let mut server_info = ServerInfo::default(); - for field in form.fields { - if field.type_ != FieldType::ListMulti { - return Err(Error::ParseError("Field is not of the required type.")); - } - if field.var == "abuse-addresses" { - server_info.abuse = field.values; - } else if field.var == "admin-addresses" { - server_info.admin = field.values; - } else if field.var == "feedback-addresses" { - server_info.feedback = field.values; - } else if field.var == "sales-addresses" { - server_info.sales = field.values; - } else if field.var == "security-addresses" { - server_info.security = field.values; - } else if field.var == "support-addresses" { - server_info.support = field.values; - } else { - return Err(Error::ParseError("Unknown form field var.")); - } - } - - Ok(server_info) - } -} - -impl From for DataForm { - fn from(server_info: ServerInfo) -> DataForm { - DataForm { - type_: DataFormType::Result_, - form_type: Some(String::from(ns::SERVER_INFO)), - title: None, - instructions: None, - fields: vec![ - generate_address_field("abuse-addresses", server_info.abuse), - generate_address_field("admin-addresses", server_info.admin), - generate_address_field("feedback-addresses", server_info.feedback), - generate_address_field("sales-addresses", server_info.sales), - generate_address_field("security-addresses", server_info.security), - generate_address_field("support-addresses", server_info.support), - ], - } - } -} - -/// Generate `Field` for addresses -pub fn generate_address_field>(var: S, values: Vec) -> Field { - Field { - var: var.into(), - type_: FieldType::ListMulti, - label: None, - required: false, - options: vec![], - values, - media: vec![], - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::data_forms::{DataForm, DataFormType, Field, FieldType}; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(ServerInfo, 72); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(ServerInfo, 144); - } - - #[test] - fn test_simple() { - let form = DataForm { - type_: DataFormType::Result_, - form_type: Some(String::from(ns::SERVER_INFO)), - title: None, - instructions: None, - fields: vec![ - Field { - var: String::from("abuse-addresses"), - type_: FieldType::ListMulti, - label: None, - required: false, - options: vec![], - values: vec![], - media: vec![], - }, - Field { - var: String::from("admin-addresses"), - type_: FieldType::ListMulti, - label: None, - required: false, - options: vec![], - values: vec![ - String::from("xmpp:admin@foo.bar"), - String::from("https://foo.bar/chat/"), - String::from("mailto:admin@foo.bar"), - ], - media: vec![], - }, - Field { - var: String::from("feedback-addresses"), - type_: FieldType::ListMulti, - label: None, - required: false, - options: vec![], - values: vec![], - media: vec![], - }, - Field { - var: String::from("sales-addresses"), - type_: FieldType::ListMulti, - label: None, - required: false, - options: vec![], - values: vec![], - media: vec![], - }, - Field { - var: String::from("security-addresses"), - type_: FieldType::ListMulti, - label: None, - required: false, - options: vec![], - values: vec![ - String::from("xmpp:security@foo.bar"), - String::from("mailto:security@foo.bar"), - ], - media: vec![], - }, - Field { - var: String::from("support-addresses"), - type_: FieldType::ListMulti, - label: None, - required: false, - options: vec![], - values: vec![String::from("mailto:support@foo.bar")], - media: vec![], - }, - ], - }; - - let server_info = ServerInfo { - abuse: vec![], - admin: vec![ - String::from("xmpp:admin@foo.bar"), - String::from("https://foo.bar/chat/"), - String::from("mailto:admin@foo.bar"), - ], - feedback: vec![], - sales: vec![], - security: vec![ - String::from("xmpp:security@foo.bar"), - String::from("mailto:security@foo.bar"), - ], - support: vec![String::from("mailto:support@foo.bar")], - }; - - // assert_eq!(DataForm::from(server_info), form); - assert_eq!(ServerInfo::try_from(form).unwrap(), server_info); - } -} diff --git a/xmpp-parsers/src/sm.rs b/xmpp-parsers/src/sm.rs deleted file mode 100644 index 315761e..0000000 --- a/xmpp-parsers/src/sm.rs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::stanza_error::DefinedCondition; - -generate_element!( - /// Acknowledgement of the currently received stanzas. - A, "a", SM, - attributes: [ - /// The last handled stanza. - h: Required = "h", - ] -); - -impl A { - /// Generates a new `` element. - pub fn new(h: u32) -> A { - A { h } - } -} - -generate_attribute!( - /// Whether to allow resumption of a previous stream. - ResumeAttr, - "resume", - bool -); - -generate_element!( - /// Client request for enabling stream management. - #[derive(Default)] - Enable, "enable", SM, - attributes: [ - /// The preferred resumption time in seconds by the client. - // TODO: should be the infinite integer set ≥ 1. - max: Option = "max", - - /// Whether the client wants to be allowed to resume the stream. - resume: Default = "resume", - ] -); - -impl Enable { - /// Generates a new `` element. - pub fn new() -> Self { - Enable::default() - } - - /// Sets the preferred resumption time in seconds. - pub fn with_max(mut self, max: u32) -> Self { - self.max = Some(max); - self - } - - /// Asks for resumption to be possible. - pub fn with_resume(mut self) -> Self { - self.resume = ResumeAttr::True; - self - } -} - -generate_id!( - /// A random identifier used for stream resumption. - StreamId -); - -generate_element!( - /// Server response once stream management is enabled. - Enabled, "enabled", SM, - attributes: [ - /// A random identifier used for stream resumption. - id: Option = "id", - - /// The preferred IP, domain, IP:port or domain:port location for - /// resumption. - location: Option = "location", - - /// The preferred resumption time in seconds by the server. - // TODO: should be the infinite integer set ≥ 1. - max: Option = "max", - - /// Whether stream resumption is allowed. - resume: Default = "resume", - ] -); - -generate_element!( - /// A stream management error happened. - Failed, "failed", SM, - attributes: [ - /// The last handled stanza. - h: Option = "h", - ], - children: [ - /// The error returned. - // XXX: implement the * handling. - error: Option = ("*", XMPP_STANZAS) => DefinedCondition - ] -); - -generate_empty_element!( - /// Requests the currently received stanzas by the other party. - R, - "r", - SM -); - -generate_element!( - /// Requests a stream resumption. - Resume, "resume", SM, - attributes: [ - /// The last handled stanza. - h: Required = "h", - - /// The previous id given by the server on - /// [enabled](struct.Enabled.html). - previd: Required = "previd", - ] -); - -generate_element!( - /// The response by the server for a successfully resumed stream. - Resumed, "resumed", SM, - attributes: [ - /// The last handled stanza. - h: Required = "h", - - /// The previous id given by the server on - /// [enabled](struct.Enabled.html). - previd: Required = "previd", - ] -); - -// TODO: add support for optional and required. -generate_empty_element!( - /// Represents availability of Stream Management in ``. - StreamManagement, - "sm", - SM -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(A, 4); - assert_size!(ResumeAttr, 1); - assert_size!(Enable, 12); - assert_size!(StreamId, 12); - assert_size!(Enabled, 36); - assert_size!(Failed, 12); - assert_size!(R, 0); - assert_size!(Resume, 16); - assert_size!(Resumed, 16); - assert_size!(StreamManagement, 0); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(A, 4); - assert_size!(ResumeAttr, 1); - assert_size!(Enable, 12); - assert_size!(StreamId, 24); - assert_size!(Enabled, 64); - assert_size!(Failed, 12); - assert_size!(R, 0); - assert_size!(Resume, 32); - assert_size!(Resumed, 32); - assert_size!(StreamManagement, 0); - } - - #[test] - fn a() { - let elem: Element = " -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; -use crate::ns; -use crate::presence::PresencePayload; -use crate::util::error::Error; -use crate::Element; -use jid::Jid; -use std::collections::BTreeMap; -use std::convert::TryFrom; - -generate_attribute!( - /// The type of the error. - ErrorType, "type", { - /// Retry after providing credentials. - Auth => "auth", - - /// Do not retry (the error cannot be remedied). - Cancel => "cancel", - - /// Proceed (the condition was only a warning). - Continue => "continue", - - /// Retry after changing the data sent. - Modify => "modify", - - /// Retry after waiting (the error is temporary). - Wait => "wait", - } -); - -generate_element_enum!( - /// List of valid error conditions. - DefinedCondition, "condition", XMPP_STANZAS, { - /// The sender has sent a stanza containing XML that does not conform - /// to the appropriate schema or that cannot be processed (e.g., an IQ - /// stanza that includes an unrecognized value of the 'type' attribute, - /// or an element that is qualified by a recognized namespace but that - /// violates the defined syntax for the element); the associated error - /// type SHOULD be "modify". - BadRequest => "bad-request", - - /// Access cannot be granted because an existing resource exists with - /// the same name or address; the associated error type SHOULD be - /// "cancel". - Conflict => "conflict", - - /// The feature represented in the XML stanza is not implemented by the - /// intended recipient or an intermediate server and therefore the - /// stanza cannot be processed (e.g., the entity understands the - /// namespace but does not recognize the element name); the associated - /// error type SHOULD be "cancel" or "modify". - FeatureNotImplemented => "feature-not-implemented", - - /// The requesting entity does not possess the necessary permissions to - /// perform an action that only certain authorized roles or individuals - /// are allowed to complete (i.e., it typically relates to - /// authorization rather than authentication); the associated error - /// type SHOULD be "auth". - Forbidden => "forbidden", - - /// The recipient or server can no longer be contacted at this address, - /// typically on a permanent basis (as opposed to the error - /// condition, which is used for temporary addressing failures); the - /// associated error type SHOULD be "cancel" and the error stanza - /// SHOULD include a new address (if available) as the XML character - /// data of the element (which MUST be a Uniform Resource - /// Identifier [URI] or Internationalized Resource Identifier [IRI] at - /// which the entity can be contacted, typically an XMPP IRI as - /// specified in [XMPP‑URI]). - Gone => "gone", - - /// The server has experienced a misconfiguration or other internal - /// error that prevents it from processing the stanza; the associated - /// error type SHOULD be "cancel". - InternalServerError => "internal-server-error", - - /// The addressed JID or item requested cannot be found; the associated - /// error type SHOULD be "cancel". - ItemNotFound => "item-not-found", - - /// The sending entity has provided (e.g., during resource binding) or - /// communicated (e.g., in the 'to' address of a stanza) an XMPP - /// address or aspect thereof that violates the rules defined in - /// [XMPP‑ADDR]; the associated error type SHOULD be "modify". - JidMalformed => "jid-malformed", - - /// The recipient or server understands the request but cannot process - /// it because the request does not meet criteria defined by the - /// recipient or server (e.g., a request to subscribe to information - /// that does not simultaneously include configuration parameters - /// needed by the recipient); the associated error type SHOULD be - /// "modify". - NotAcceptable => "not-acceptable", - - /// The recipient or server does not allow any entity to perform the - /// action (e.g., sending to entities at a blacklisted domain); the - /// associated error type SHOULD be "cancel". - NotAllowed => "not-allowed", - - /// The sender needs to provide credentials before being allowed to - /// perform the action, or has provided improper credentials (the name - /// "not-authorized", which was borrowed from the "401 Unauthorized" - /// error of [HTTP], might lead the reader to think that this condition - /// relates to authorization, but instead it is typically used in - /// relation to authentication); the associated error type SHOULD be - /// "auth". - NotAuthorized => "not-authorized", - - /// The entity has violated some local service policy (e.g., a message - /// contains words that are prohibited by the service) and the server - /// MAY choose to specify the policy in the element or in an - /// application-specific condition element; the associated error type - /// SHOULD be "modify" or "wait" depending on the policy being - /// violated. - PolicyViolation => "policy-violation", - - /// The intended recipient is temporarily unavailable, undergoing - /// maintenance, etc.; the associated error type SHOULD be "wait". - RecipientUnavailable => "recipient-unavailable", - - /// The recipient or server is redirecting requests for this - /// information to another entity, typically in a temporary fashion (as - /// opposed to the error condition, which is used for permanent - /// addressing failures); the associated error type SHOULD be "modify" - /// and the error stanza SHOULD contain the alternate address in the - /// XML character data of the element (which MUST be a URI - /// or IRI with which the sender can communicate, typically an XMPP IRI - /// as specified in [XMPP‑URI]). - Redirect => "redirect", - - /// The requesting entity is not authorized to access the requested - /// service because prior registration is necessary (examples of prior - /// registration include members-only rooms in XMPP multi-user chat - /// [XEP‑0045] and gateways to non-XMPP instant messaging services, - /// which traditionally required registration in order to use the - /// gateway [XEP‑0100]); the associated error type SHOULD be "auth". - RegistrationRequired => "registration-required", - - /// A remote server or service specified as part or all of the JID of - /// the intended recipient does not exist or cannot be resolved (e.g., - /// there is no _xmpp-server._tcp DNS SRV record, the A or AAAA - /// fallback resolution fails, or A/AAAA lookups succeed but there is - /// no response on the IANA-registered port 5269); the associated error - /// type SHOULD be "cancel". - RemoteServerNotFound => "remote-server-not-found", - - /// A remote server or service specified as part or all of the JID of - /// the intended recipient (or needed to fulfill a request) was - /// resolved but communications could not be established within a - /// reasonable amount of time (e.g., an XML stream cannot be - /// established at the resolved IP address and port, or an XML stream - /// can be established but stream negotiation fails because of problems - /// with TLS, SASL, Server Dialback, etc.); the associated error type - /// SHOULD be "wait" (unless the error is of a more permanent nature, - /// e.g., the remote server is found but it cannot be authenticated or - /// it violates security policies). - RemoteServerTimeout => "remote-server-timeout", - - /// The server or recipient is busy or lacks the system resources - /// necessary to service the request; the associated error type SHOULD - /// be "wait". - ResourceConstraint => "resource-constraint", - - /// The server or recipient does not currently provide the requested - /// service; the associated error type SHOULD be "cancel". - ServiceUnavailable => "service-unavailable", - - /// The requesting entity is not authorized to access the requested - /// service because a prior subscription is necessary (examples of - /// prior subscription include authorization to receive presence - /// information as defined in [XMPP‑IM] and opt-in data feeds for XMPP - /// publish-subscribe as defined in [XEP‑0060]); the associated error - /// type SHOULD be "auth". - SubscriptionRequired => "subscription-required", - - /// The error condition is not one of those defined by the other - /// conditions in this list; any error type can be associated with this - /// condition, and it SHOULD NOT be used except in conjunction with an - /// application-specific condition. - UndefinedCondition => "undefined-condition", - - /// The recipient or server understood the request but was not - /// expecting it at this time (e.g., the request was out of order); the - /// associated error type SHOULD be "wait" or "modify". - UnexpectedRequest => "unexpected-request", - } -); - -type Lang = String; - -/// The representation of a stanza error. -#[derive(Debug, Clone)] -pub struct StanzaError { - /// The type of this error. - pub type_: ErrorType, - - /// The JID of the entity who set this error. - pub by: Option, - - /// One of the defined conditions for this error to happen. - pub defined_condition: DefinedCondition, - - /// Human-readable description of this error. - pub texts: BTreeMap, - - /// A protocol-specific extension for this error. - pub other: Option, -} - -impl MessagePayload for StanzaError {} -impl PresencePayload for StanzaError {} - -impl StanzaError { - /// Create a new `` with the according content. - pub fn new( - type_: ErrorType, - defined_condition: DefinedCondition, - lang: L, - text: T, - ) -> StanzaError - where - L: Into, - T: Into, - { - StanzaError { - type_, - by: None, - defined_condition, - texts: { - let mut map = BTreeMap::new(); - map.insert(lang.into(), text.into()); - map - }, - other: None, - } - } -} - -impl TryFrom for StanzaError { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "error", DEFAULT_NS); - check_no_unknown_attributes!(elem, "error", ["type", "by"]); - - let mut stanza_error = StanzaError { - type_: get_attr!(elem, "type", Required), - by: get_attr!(elem, "by", Option), - defined_condition: DefinedCondition::UndefinedCondition, - texts: BTreeMap::new(), - other: None, - }; - let mut defined_condition = None; - - for child in elem.children() { - if child.is("text", ns::XMPP_STANZAS) { - check_no_children!(child, "text"); - check_no_unknown_attributes!(child, "text", ["xml:lang"]); - let lang = get_attr!(elem, "xml:lang", Default); - if stanza_error.texts.insert(lang, child.text()).is_some() { - return Err(Error::ParseError( - "Text element present twice for the same xml:lang.", - )); - } - } else if child.has_ns(ns::XMPP_STANZAS) { - if defined_condition.is_some() { - return Err(Error::ParseError( - "Error must not have more than one defined-condition.", - )); - } - check_no_children!(child, "defined-condition"); - check_no_attributes!(child, "defined-condition"); - let condition = DefinedCondition::try_from(child.clone())?; - defined_condition = Some(condition); - } else { - if stanza_error.other.is_some() { - return Err(Error::ParseError( - "Error must not have more than one other element.", - )); - } - stanza_error.other = Some(child.clone()); - } - } - stanza_error.defined_condition = - defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?; - - Ok(stanza_error) - } -} - -impl From for Element { - fn from(err: StanzaError) -> Element { - Element::builder("error", ns::DEFAULT_NS) - .attr("type", err.type_) - .attr("by", err.by) - .append(err.defined_condition) - .append_all(err.texts.into_iter().map(|(lang, text)| { - Element::builder("text", ns::XMPP_STANZAS) - .attr("xml:lang", lang) - .append(text) - })) - .append_all(err.other) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(ErrorType, 1); - assert_size!(DefinedCondition, 1); - assert_size!(StanzaError, 132); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(ErrorType, 1); - assert_size!(DefinedCondition, 1); - assert_size!(StanzaError, 264); - } - - #[test] - fn test_simple() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "".parse().unwrap(); - let error = StanzaError::try_from(elem).unwrap(); - assert_eq!(error.type_, ErrorType::Cancel); - assert_eq!( - error.defined_condition, - DefinedCondition::UndefinedCondition - ); - } - - #[test] - fn test_invalid_type() { - #[cfg(not(feature = "component"))] - let elem: Element = "".parse().unwrap(); - #[cfg(feature = "component")] - let elem: Element = "".parse().unwrap(); - let error = StanzaError::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'type' missing."); - - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let error = StanzaError::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown value for 'type' attribute."); - } - - #[test] - fn test_invalid_condition() { - #[cfg(not(feature = "component"))] - let elem: Element = "" - .parse() - .unwrap(); - #[cfg(feature = "component")] - let elem: Element = "" - .parse() - .unwrap(); - let error = StanzaError::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Error must have a defined-condition."); - } -} diff --git a/xmpp-parsers/src/stanza_id.rs b/xmpp-parsers/src/stanza_id.rs deleted file mode 100644 index 940db3f..0000000 --- a/xmpp-parsers/src/stanza_id.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; -use jid::Jid; - -generate_element!( - /// Gives the identifier a service has stamped on this stanza, often in - /// order to identify it inside of [an archive](../mam/index.html). - StanzaId, "stanza-id", SID, - attributes: [ - /// The id associated to this stanza by another entity. - id: Required = "id", - - /// The entity who stamped this stanza-id. - by: Required = "by", - ] -); - -impl MessagePayload for StanzaId {} - -generate_element!( - /// A hack for MUC before version 1.31 to track a message which may have - /// its 'id' attribute changed. - OriginId, "origin-id", SID, - attributes: [ - /// The id this client set for this stanza. - id: Required = "id", - ] -); - -impl MessagePayload for OriginId {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::error::Error; - use crate::Element; - use jid::BareJid; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(StanzaId, 52); - assert_size!(OriginId, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(StanzaId, 104); - assert_size!(OriginId, 24); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let stanza_id = StanzaId::try_from(elem).unwrap(); - assert_eq!(stanza_id.id, String::from("coucou")); - assert_eq!(stanza_id.by, BareJid::new("coucou", "coucou")); - - let elem: Element = "" - .parse() - .unwrap(); - let origin_id = OriginId::try_from(elem).unwrap(); - assert_eq!(origin_id.id, String::from("coucou")); - } - - #[test] - fn test_invalid_child() { - let elem: Element = "" - .parse() - .unwrap(); - let error = StanzaId::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Unknown child in stanza-id element."); - } - - #[test] - fn test_invalid_id() { - let elem: Element = "".parse().unwrap(); - let error = StanzaId::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'id' missing."); - } - - #[test] - fn test_invalid_by() { - let elem: Element = "" - .parse() - .unwrap(); - let error = StanzaId::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Required attribute 'by' missing."); - } - - #[test] - fn test_serialise() { - let elem: Element = "" - .parse() - .unwrap(); - let stanza_id = StanzaId { - id: String::from("coucou"), - by: Jid::Bare(BareJid::new("coucou", "coucou")), - }; - let elem2 = stanza_id.into(); - assert_eq!(elem, elem2); - } -} diff --git a/xmpp-parsers/src/stream.rs b/xmpp-parsers/src/stream.rs deleted file mode 100644 index c1b5e1e..0000000 --- a/xmpp-parsers/src/stream.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use jid::BareJid; - -generate_element!( - /// The stream opening for client-server communications. - Stream, "stream", STREAM, - attributes: [ - /// The JID of the entity opening this stream. - from: Option = "from", - - /// The JID of the entity receiving this stream opening. - to: Option = "to", - - /// The id of the stream, used for authentication challenges. - id: Option = "id", - - /// The XMPP version used during this stream. - version: Option = "version", - - /// The default human language for all subsequent stanzas, which will - /// be transmitted to other entities for better localisation. - xml_lang: Option = "xml:lang", - ] -); - -impl Stream { - /// Creates a simple client→server `` element. - pub fn new(to: BareJid) -> Stream { - Stream { - from: None, - to: Some(to), - id: None, - version: Some(String::from("1.0")), - xml_lang: None, - } - } - - /// Sets the [@from](#structfield.from) attribute on this `` - /// element. - pub fn with_from(mut self, from: BareJid) -> Stream { - self.from = Some(from); - self - } - - /// Sets the [@id](#structfield.id) attribute on this `` - /// element. - pub fn with_id(mut self, id: String) -> Stream { - self.id = Some(id); - self - } - - /// Sets the [@xml:lang](#structfield.xml_lang) attribute on this - /// `` element. - pub fn with_lang(mut self, xml_lang: String) -> Stream { - self.xml_lang = Some(xml_lang); - self - } - - /// Checks whether the version matches the expected one. - pub fn is_version(&self, version: &str) -> bool { - match self.version { - None => false, - Some(ref self_version) => self_version == &String::from(version), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Stream, 84); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Stream, 168); - } - - #[test] - fn test_simple() { - let elem: Element = "".parse().unwrap(); - let stream = Stream::try_from(elem).unwrap(); - assert_eq!(stream.from, Some(BareJid::domain("some-server.example"))); - assert_eq!(stream.to, None); - assert_eq!(stream.id, Some(String::from("abc"))); - assert_eq!(stream.version, Some(String::from("1.0"))); - assert_eq!(stream.xml_lang, Some(String::from("en"))); - } -} diff --git a/xmpp-parsers/src/time.rs b/xmpp-parsers/src/time.rs deleted file mode 100644 index d97c941..0000000 --- a/xmpp-parsers/src/time.rs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::date::DateTime; -use crate::iq::{IqGetPayload, IqResultPayload}; -use crate::ns; -use crate::util::error::Error; -use crate::Element; -use chrono::FixedOffset; -use std::convert::TryFrom; -use std::str::FromStr; - -generate_empty_element!( - /// An entity time query. - TimeQuery, - "time", - TIME -); - -impl IqGetPayload for TimeQuery {} - -/// An entity time result, containing an unique DateTime. -#[derive(Debug, Clone)] -pub struct TimeResult(pub DateTime); - -impl IqResultPayload for TimeResult {} - -impl TryFrom for TimeResult { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "time", TIME); - check_no_attributes!(elem, "time"); - - let mut tzo = None; - let mut utc = None; - - for child in elem.children() { - if child.is("tzo", ns::TIME) { - if tzo.is_some() { - return Err(Error::ParseError("More than one tzo element in time.")); - } - check_no_children!(child, "tzo"); - check_no_attributes!(child, "tzo"); - // TODO: Add a FromStr implementation to FixedOffset to avoid this hack. - let fake_date = String::from("2019-04-22T11:38:00") + &child.text(); - let date_time = DateTime::from_str(&fake_date)?; - tzo = Some(date_time.timezone()); - } else if child.is("utc", ns::TIME) { - if utc.is_some() { - return Err(Error::ParseError("More than one utc element in time.")); - } - check_no_children!(child, "utc"); - check_no_attributes!(child, "utc"); - let date_time = DateTime::from_str(&child.text())?; - if date_time.timezone() != FixedOffset::east(0) { - return Err(Error::ParseError("Non-UTC timezone for utc element.")); - } - utc = Some(date_time); - } else { - return Err(Error::ParseError("Unknown child in time element.")); - } - } - - let tzo = tzo.ok_or(Error::ParseError("Missing tzo child in time element."))?; - let utc = utc.ok_or(Error::ParseError("Missing utc child in time element."))?; - let date = utc.with_timezone(tzo); - - Ok(TimeResult(date)) - } -} - -impl From for Element { - fn from(time: TimeResult) -> Element { - Element::builder("time", ns::TIME) - .append(Element::builder("tzo", ns::TIME).append(format!("{}", time.0.timezone()))) - .append( - Element::builder("utc", ns::TIME) - .append(time.0.with_timezone(FixedOffset::east(0)).format("%FT%TZ")), - ) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // DateTime’s size doesn’t depend on the architecture. - #[test] - fn test_size() { - assert_size!(TimeQuery, 0); - assert_size!(TimeResult, 16); - } - - #[test] - fn parse_response() { - let elem: Element = - "" - .parse() - .unwrap(); - let elem1 = elem.clone(); - let time = TimeResult::try_from(elem).unwrap(); - assert_eq!(time.0.timezone(), FixedOffset::west(6 * 3600)); - assert_eq!( - time.0, - DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap() - ); - let elem2 = Element::from(time); - assert_eq!(elem1, elem2); - } -} diff --git a/xmpp-parsers/src/tune.rs b/xmpp-parsers/src/tune.rs deleted file mode 100644 index e80dad4..0000000 --- a/xmpp-parsers/src/tune.rs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::ns; -use crate::pubsub::PubSubPayload; -use crate::util::error::Error; -use crate::Element; -use std::convert::TryFrom; - -generate_elem_id!( - /// The artist or performer of the song or piece. - Artist, - "artist", - TUNE -); - -generate_elem_id!( - /// The duration of the song or piece in seconds. - Length, - "length", - TUNE, - u16 -); - -generate_elem_id!( - /// The user's rating of the song or piece, from 1 (lowest) to 10 (highest). - Rating, - "rating", - TUNE, - u8 -); - -generate_elem_id!( - /// The collection (e.g., album) or other source (e.g., a band website that hosts streams or - /// audio files). - Source, - "source", - TUNE -); - -generate_elem_id!( - /// The title of the song or piece. - Title, - "title", - TUNE -); - -generate_elem_id!( - /// A unique identifier for the tune; e.g., the track number within a collection or the - /// specific URI for the object (e.g., a stream or audio file). - Track, - "track", - TUNE -); - -generate_elem_id!( - /// A URI or URL pointing to information about the song, collection, or artist. - Uri, - "uri", - TUNE -); - -/// Container for formatted text. -#[derive(Debug, Clone)] -pub struct Tune { - /// The artist or performer of the song or piece. - artist: Option, - - /// The duration of the song or piece in seconds. - length: Option, - - /// The user's rating of the song or piece, from 1 (lowest) to 10 (highest). - rating: Option, - - /// The collection (e.g., album) or other source (e.g., a band website that hosts streams or - /// audio files). - source: Option, - - /// The title of the song or piece. - title: Option, - - /// A unique identifier for the tune; e.g., the track number within a collection or the - /// specific URI for the object (e.g., a stream or audio file). - track: Option<Track>, - - /// A URI or URL pointing to information about the song, collection, or artist. - uri: Option<Uri>, -} - -impl PubSubPayload for Tune {} - -impl Tune { - fn new() -> Tune { - Tune { - artist: None, - length: None, - rating: None, - source: None, - title: None, - track: None, - uri: None, - } - } -} - -impl TryFrom<Element> for Tune { - type Error = Error; - - fn try_from(elem: Element) -> Result<Tune, Error> { - check_self!(elem, "tune", TUNE); - check_no_attributes!(elem, "tune"); - - let mut tune = Tune::new(); - for child in elem.children() { - if child.is("artist", ns::TUNE) { - if tune.artist.is_some() { - return Err(Error::ParseError("Tune can’t have more than one artist.")); - } - tune.artist = Some(Artist::try_from(child.clone())?); - } else if child.is("length", ns::TUNE) { - if tune.length.is_some() { - return Err(Error::ParseError("Tune can’t have more than one length.")); - } - tune.length = Some(Length::try_from(child.clone())?); - } else if child.is("rating", ns::TUNE) { - if tune.rating.is_some() { - return Err(Error::ParseError("Tune can’t have more than one rating.")); - } - tune.rating = Some(Rating::try_from(child.clone())?); - } else if child.is("source", ns::TUNE) { - if tune.source.is_some() { - return Err(Error::ParseError("Tune can’t have more than one source.")); - } - tune.source = Some(Source::try_from(child.clone())?); - } else if child.is("title", ns::TUNE) { - if tune.title.is_some() { - return Err(Error::ParseError("Tune can’t have more than one title.")); - } - tune.title = Some(Title::try_from(child.clone())?); - } else if child.is("track", ns::TUNE) { - if tune.track.is_some() { - return Err(Error::ParseError("Tune can’t have more than one track.")); - } - tune.track = Some(Track::try_from(child.clone())?); - } else if child.is("uri", ns::TUNE) { - if tune.uri.is_some() { - return Err(Error::ParseError("Tune can’t have more than one uri.")); - } - tune.uri = Some(Uri::try_from(child.clone())?); - } else { - return Err(Error::ParseError("Unknown element in User Tune.")); - } - } - - Ok(tune) - } -} - -impl From<Tune> for Element { - fn from(tune: Tune) -> Element { - Element::builder("tune", ns::TUNE) - .append_all(tune.artist) - .append_all(tune.length) - .append_all(tune.rating) - .append_all(tune.source) - .append_all(tune.title) - .append_all(tune.track) - .append_all(tune.uri) - .build() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str::FromStr; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Tune, 68); - assert_size!(Artist, 12); - assert_size!(Length, 2); - assert_size!(Rating, 1); - assert_size!(Source, 12); - assert_size!(Title, 12); - assert_size!(Track, 12); - assert_size!(Uri, 12); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Tune, 128); - assert_size!(Artist, 24); - assert_size!(Length, 2); - assert_size!(Rating, 1); - assert_size!(Source, 24); - assert_size!(Title, 24); - assert_size!(Track, 24); - assert_size!(Uri, 24); - } - - #[test] - fn empty() { - let elem: Element = "<tune xmlns='http://jabber.org/protocol/tune'/>" - .parse() - .unwrap(); - let elem2 = elem.clone(); - let tune = Tune::try_from(elem).unwrap(); - assert!(tune.artist.is_none()); - assert!(tune.length.is_none()); - assert!(tune.rating.is_none()); - assert!(tune.source.is_none()); - assert!(tune.title.is_none()); - assert!(tune.track.is_none()); - assert!(tune.uri.is_none()); - - let elem3 = tune.into(); - assert_eq!(elem2, elem3); - } - - #[test] - fn full() { - let elem: Element = "<tune xmlns='http://jabber.org/protocol/tune'><artist>Yes</artist><length>686</length><rating>8</rating><source>Yessongs</source><title>Heart of the Sunrise3http://www.yesworld.com/lyrics/Fragile.html#9" - .parse() - .unwrap(); - let tune = Tune::try_from(elem).unwrap(); - assert_eq!(tune.artist, Some(Artist::from_str("Yes").unwrap())); - assert_eq!(tune.length, Some(Length(686))); - assert_eq!(tune.rating, Some(Rating(8))); - assert_eq!(tune.source, Some(Source::from_str("Yessongs").unwrap())); - assert_eq!( - tune.title, - Some(Title::from_str("Heart of the Sunrise").unwrap()) - ); - assert_eq!(tune.track, Some(Track::from_str("3").unwrap())); - assert_eq!( - tune.uri, - Some(Uri::from_str("http://www.yesworld.com/lyrics/Fragile.html#9").unwrap()) - ); - } -} diff --git a/xmpp-parsers/src/util/error.rs b/xmpp-parsers/src/util/error.rs deleted file mode 100644 index 33b2a69..0000000 --- a/xmpp-parsers/src/util/error.rs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) 2017-2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::error::Error as StdError; -use std::fmt; - -/// Contains one of the potential errors triggered while parsing an -/// [Element](../struct.Element.html) into a specialised struct. -#[derive(Debug)] -pub enum Error { - /// The usual error when parsing something. - /// - /// TODO: use a structured error so the user can report it better, instead - /// of a freeform string. - ParseError(&'static str), - - /// Generated when some base64 content fails to decode, usually due to - /// extra characters. - Base64Error(base64::DecodeError), - - /// Generated when text which should be an integer fails to parse. - ParseIntError(std::num::ParseIntError), - - /// Generated when text which should be a string fails to parse. - ParseStringError(std::string::ParseError), - - /// Generated when text which should be an IP address (IPv4 or IPv6) fails - /// to parse. - ParseAddrError(std::net::AddrParseError), - - /// Generated when text which should be a [JID](../../jid/struct.Jid.html) - /// fails to parse. - JidParseError(jid::JidParseError), - - /// Generated when text which should be a - /// [DateTime](../date/struct.DateTime.html) fails to parse. - ChronoParseError(chrono::ParseError), -} - -impl StdError for Error { - fn cause(&self) -> Option<&dyn StdError> { - match self { - Error::ParseError(_) => None, - Error::Base64Error(e) => Some(e), - Error::ParseIntError(e) => Some(e), - Error::ParseStringError(e) => Some(e), - Error::ParseAddrError(e) => Some(e), - Error::JidParseError(e) => Some(e), - Error::ChronoParseError(e) => Some(e), - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::ParseError(s) => write!(fmt, "parse error: {}", s), - Error::Base64Error(e) => write!(fmt, "base64 error: {}", e), - Error::ParseIntError(e) => write!(fmt, "integer parsing error: {}", e), - Error::ParseStringError(e) => write!(fmt, "string parsing error: {}", e), - Error::ParseAddrError(e) => write!(fmt, "IP address parsing error: {}", e), - Error::JidParseError(e) => write!(fmt, "JID parsing error: {}", e), - Error::ChronoParseError(e) => write!(fmt, "time parsing error: {}", e), - } - } -} - -impl From for Error { - fn from(err: base64::DecodeError) -> Error { - Error::Base64Error(err) - } -} - -impl From for Error { - fn from(err: std::num::ParseIntError) -> Error { - Error::ParseIntError(err) - } -} - -impl From for Error { - fn from(err: std::string::ParseError) -> Error { - Error::ParseStringError(err) - } -} - -impl From for Error { - fn from(err: std::net::AddrParseError) -> Error { - Error::ParseAddrError(err) - } -} - -impl From for Error { - fn from(err: jid::JidParseError) -> Error { - Error::JidParseError(err) - } -} - -impl From for Error { - fn from(err: chrono::ParseError) -> Error { - Error::ChronoParseError(err) - } -} diff --git a/xmpp-parsers/src/util/helpers.rs b/xmpp-parsers/src/util/helpers.rs deleted file mode 100644 index 570e7cf..0000000 --- a/xmpp-parsers/src/util/helpers.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::util::error::Error; -use jid::Jid; -use std::str::FromStr; - -/// Codec for text content. -pub struct Text; - -impl Text { - pub fn decode(s: &str) -> Result { - Ok(s.to_owned()) - } - - pub fn encode(string: &str) -> Option { - Some(string.to_owned()) - } -} - -/// Codec for plain text content. -pub struct PlainText; - -impl PlainText { - pub fn decode(s: &str) -> Result, Error> { - Ok(match s { - "" => None, - text => Some(text.to_owned()), - }) - } - - pub fn encode(string: &Option) -> Option { - string.as_ref().map(ToOwned::to_owned) - } -} - -/// Codec for trimmed plain text content. -pub struct TrimmedPlainText; - -impl TrimmedPlainText { - pub fn decode(s: &str) -> Result { - Ok(match s.trim() { - "" => return Err(Error::ParseError("URI missing in uri.")), - text => text.to_owned(), - }) - } - - pub fn encode(string: &str) -> Option { - Some(string.to_owned()) - } -} - -/// Codec wrapping base64 encode/decode. -pub struct Base64; - -impl Base64 { - pub fn decode(s: &str) -> Result, Error> { - Ok(base64::decode(s)?) - } - - pub fn encode(b: &[u8]) -> Option { - Some(base64::encode(b)) - } -} - -/// Codec wrapping base64 encode/decode, while ignoring whitespace characters. -pub struct WhitespaceAwareBase64; - -impl WhitespaceAwareBase64 { - pub fn decode(s: &str) -> Result, Error> { - let s: String = s - .chars() - .filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t') - .collect(); - Ok(base64::decode(&s)?) - } - - pub fn encode(b: &[u8]) -> Option { - Some(base64::encode(b)) - } -} - -/// Codec for colon-separated bytes of uppercase hexadecimal. -pub struct ColonSeparatedHex; - -impl ColonSeparatedHex { - pub fn decode(s: &str) -> Result, Error> { - let mut bytes = vec![]; - for i in 0..(1 + s.len()) / 3 { - let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?; - if 3 * i + 2 < s.len() { - assert_eq!(&s[3 * i + 2..3 * i + 3], ":"); - } - bytes.push(byte); - } - Ok(bytes) - } - - pub fn encode(b: &[u8]) -> Option { - let mut bytes = vec![]; - for byte in b { - bytes.push(format!("{:02X}", byte)); - } - Some(bytes.join(":")) - } -} - -/// Codec for a JID. -pub struct JidCodec; - -impl JidCodec { - pub fn decode(s: &str) -> Result { - Ok(Jid::from_str(s)?) - } - - pub fn encode(jid: &Jid) -> Option { - Some(jid.to_string()) - } -} diff --git a/xmpp-parsers/src/util/macros.rs b/xmpp-parsers/src/util/macros.rs deleted file mode 100644 index 702a16f..0000000 --- a/xmpp-parsers/src/util/macros.rs +++ /dev/null @@ -1,767 +0,0 @@ -// Copyright (c) 2017-2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -macro_rules! get_attr { - ($elem:ident, $attr:tt, $type:tt) => { - get_attr!($elem, $attr, $type, value, value.parse()?) - }; - ($elem:ident, $attr:tt, OptionEmpty, $value:ident, $func:expr) => { - match $elem.attr($attr) { - Some("") => None, - Some($value) => Some($func), - None => None, - } - }; - ($elem:ident, $attr:tt, Option, $value:ident, $func:expr) => { - match $elem.attr($attr) { - Some($value) => Some($func), - None => None, - } - }; - ($elem:ident, $attr:tt, Required, $value:ident, $func:expr) => { - match $elem.attr($attr) { - Some($value) => $func, - None => { - return Err(crate::util::error::Error::ParseError(concat!( - "Required attribute '", - $attr, - "' missing." - ))); - } - } - }; - ($elem:ident, $attr:tt, RequiredNonEmpty, $value:ident, $func:expr) => { - match $elem.attr($attr) { - Some("") => { - return Err(crate::util::error::Error::ParseError(concat!( - "Required attribute '", - $attr, - "' must not be empty." - ))); - } - Some($value) => $func, - None => { - return Err(crate::util::error::Error::ParseError(concat!( - "Required attribute '", - $attr, - "' missing." - ))); - } - } - }; - ($elem:ident, $attr:tt, Default, $value:ident, $func:expr) => { - match $elem.attr($attr) { - Some($value) => $func, - None => ::std::default::Default::default(), - } - }; -} - -macro_rules! generate_attribute { - ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}) => ( - generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+}); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}, Default = $default:ident) => ( - generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+}, Default = $default); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub enum $elem { - $( - $(#[$a_meta])* - $a - ),+ - } - impl ::std::str::FromStr for $elem { - type Err = crate::util::error::Error; - fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> { - Ok(match s { - $($b => $elem::$a),+, - _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), - }) - } - } - impl std::fmt::Display for $elem { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(fmt, "{}", match self { - $($elem::$a => $b),+ - }) - } - } - impl ::minidom::IntoAttributeValue for $elem { - fn into_attribute_value(self) -> Option { - Some(String::from(match self { - $($elem::$a => $b),+ - })) - } - } - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}, Default = $default:ident) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub enum $elem { - $( - $(#[$a_meta])* - $a - ),+ - } - impl ::std::str::FromStr for $elem { - type Err = crate::util::error::Error; - fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> { - Ok(match s { - $($b => $elem::$a),+, - _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), - }) - } - } - impl ::minidom::IntoAttributeValue for $elem { - #[allow(unreachable_patterns)] - fn into_attribute_value(self) -> Option { - Some(String::from(match self { - $elem::$default => return None, - $($elem::$a => $b),+ - })) - } - } - impl ::std::default::Default for $elem { - fn default() -> $elem { - $elem::$default - } - } - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, ($(#[$meta_symbol:meta])* $symbol:ident => $value:tt)) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub enum $elem { - $(#[$meta_symbol])* - $symbol, - /// Value when absent. - None, - } - impl ::std::str::FromStr for $elem { - type Err = crate::util::error::Error; - fn from_str(s: &str) -> Result { - Ok(match s { - $value => $elem::$symbol, - _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), - }) - } - } - impl ::minidom::IntoAttributeValue for $elem { - fn into_attribute_value(self) -> Option { - match self { - $elem::$symbol => Some(String::from($value)), - $elem::None => None - } - } - } - impl ::std::default::Default for $elem { - fn default() -> $elem { - $elem::None - } - } - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, bool) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub enum $elem { - /// True value, represented by either 'true' or '1'. - True, - /// False value, represented by either 'false' or '0'. - False, - } - impl ::std::str::FromStr for $elem { - type Err = crate::util::error::Error; - fn from_str(s: &str) -> Result { - Ok(match s { - "true" | "1" => $elem::True, - "false" | "0" => $elem::False, - _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))), - }) - } - } - impl ::minidom::IntoAttributeValue for $elem { - fn into_attribute_value(self) -> Option { - match self { - $elem::True => Some(String::from("true")), - $elem::False => None - } - } - } - impl ::std::default::Default for $elem { - fn default() -> $elem { - $elem::False - } - } - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $type:tt, Default = $default:expr) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub struct $elem(pub $type); - impl ::std::str::FromStr for $elem { - type Err = crate::util::error::Error; - fn from_str(s: &str) -> Result { - Ok($elem($type::from_str(s)?)) - } - } - impl ::minidom::IntoAttributeValue for $elem { - fn into_attribute_value(self) -> Option { - match self { - $elem($default) => None, - $elem(value) => Some(format!("{}", value)), - } - } - } - impl ::std::default::Default for $elem { - fn default() -> $elem { - $elem($default) - } - } - ); -} - -macro_rules! generate_element_enum { - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => ( - generate_element_enum!($(#[$meta])* $elem, $name, $ns, {$($(#[$enum_meta])* $enum => $enum_name),+}); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub enum $elem { - $( - $(#[$enum_meta])* - $enum - ),+ - } - impl ::std::convert::TryFrom for $elem { - type Error = crate::util::error::Error; - fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> { - check_ns_only!(elem, $name, $ns); - check_no_children!(elem, $name); - check_no_attributes!(elem, $name); - Ok(match elem.name() { - $($enum_name => $elem::$enum,)+ - _ => return Err(crate::util::error::Error::ParseError(concat!("This is not a ", $name, " element."))), - }) - } - } - impl From<$elem> for crate::Element { - fn from(elem: $elem) -> crate::Element { - crate::Element::builder( - match elem { - $($elem::$enum => $enum_name,)+ - }, - crate::ns::$ns, - ) - .build() - } - } - ); -} - -macro_rules! generate_attribute_enum { - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => ( - generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+}); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub enum $elem { - $( - $(#[$enum_meta])* - $enum - ),+ - } - impl ::std::convert::TryFrom for $elem { - type Error = crate::util::error::Error; - fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> { - check_ns_only!(elem, $name, $ns); - check_no_children!(elem, $name); - check_no_unknown_attributes!(elem, $name, [$attr]); - Ok(match get_attr!(elem, $attr, Required) { - $($enum_name => $elem::$enum,)+ - _ => return Err(crate::util::error::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))), - }) - } - } - impl From<$elem> for crate::Element { - fn from(elem: $elem) -> crate::Element { - crate::Element::builder($name, crate::ns::$ns) - .attr($attr, match elem { - $($elem::$enum => $enum_name,)+ - }) - .build() - } - } - ); -} - -macro_rules! check_self { - ($elem:ident, $name:tt, $ns:ident) => { - check_self!($elem, $name, $ns, $name); - }; - ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => { - if !$elem.is($name, crate::ns::$ns) { - return Err(crate::util::error::Error::ParseError(concat!( - "This is not a ", - $pretty_name, - " element." - ))); - } - }; -} - -macro_rules! check_ns_only { - ($elem:ident, $name:tt, $ns:ident) => { - if !$elem.has_ns(crate::ns::$ns) { - return Err(crate::util::error::Error::ParseError(concat!( - "This is not a ", - $name, - " element." - ))); - } - }; -} - -macro_rules! check_no_children { - ($elem:ident, $name:tt) => { - #[cfg(not(feature = "disable-validation"))] - for _ in $elem.children() { - return Err(crate::util::error::Error::ParseError(concat!( - "Unknown child in ", - $name, - " element." - ))); - } - }; -} - -macro_rules! check_no_attributes { - ($elem:ident, $name:tt) => { - #[cfg(not(feature = "disable-validation"))] - for _ in $elem.attrs() { - return Err(crate::util::error::Error::ParseError(concat!( - "Unknown attribute in ", - $name, - " element." - ))); - } - }; -} - -macro_rules! check_no_unknown_attributes { - ($elem:ident, $name:tt, [$($attr:tt),*]) => ( - #[cfg(not(feature = "disable-validation"))] - for (_attr, _) in $elem.attrs() { - $( - if _attr == $attr { - continue; - } - )* - return Err(crate::util::error::Error::ParseError(concat!("Unknown attribute in ", $name, " element."))); - } - ); -} - -macro_rules! generate_empty_element { - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub struct $elem; - - impl ::std::convert::TryFrom for $elem { - type Error = crate::util::error::Error; - - fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> { - check_self!(elem, $name, $ns); - check_no_children!(elem, $name); - check_no_attributes!(elem, $name); - Ok($elem) - } - } - - impl From<$elem> for crate::Element { - fn from(_: $elem) -> crate::Element { - crate::Element::builder($name, crate::ns::$ns) - .build() - } - } - ); -} - -macro_rules! generate_id { - ($(#[$meta:meta])* $elem:ident) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct $elem(pub String); - impl ::std::str::FromStr for $elem { - type Err = crate::util::error::Error; - fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> { - // TODO: add a way to parse that differently when needed. - Ok($elem(String::from(s))) - } - } - impl ::minidom::IntoAttributeValue for $elem { - fn into_attribute_value(self) -> Option { - Some(self.0) - } - } - ); -} - -macro_rules! generate_elem_id { - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => ( - generate_elem_id!($(#[$meta])* $elem, $name, $ns, String); - impl ::std::str::FromStr for $elem { - type Err = crate::util::error::Error; - fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> { - // TODO: add a way to parse that differently when needed. - Ok($elem(String::from(s))) - } - } - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $type:ty) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - pub struct $elem(pub $type); - impl ::std::convert::TryFrom for $elem { - type Error = crate::util::error::Error; - fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> { - check_self!(elem, $name, $ns); - check_no_children!(elem, $name); - check_no_attributes!(elem, $name); - // TODO: add a way to parse that differently when needed. - Ok($elem(elem.text().parse()?)) - } - } - impl From<$elem> for crate::Element { - fn from(elem: $elem) -> crate::Element { - crate::Element::builder($name, crate::ns::$ns) - .append(elem.0.to_string()) - .build() - } - } - ); -} - -macro_rules! decl_attr { - (OptionEmpty, $type:ty) => ( - Option<$type> - ); - (Option, $type:ty) => ( - Option<$type> - ); - (Required, $type:ty) => ( - $type - ); - (RequiredNonEmpty, $type:ty) => ( - $type - ); - (Default, $type:ty) => ( - $type - ); -} - -macro_rules! start_decl { - (Vec, $type:ty) => ( - Vec<$type> - ); - (Option, $type:ty) => ( - Option<$type> - ); - (Required, $type:ty) => ( - $type - ); - (Present, $type:ty) => ( - bool - ); -} - -macro_rules! start_parse_elem { - ($temp:ident: Vec) => { - let mut $temp = Vec::new(); - }; - ($temp:ident: Option) => { - let mut $temp = None; - }; - ($temp:ident: Required) => { - let mut $temp = None; - }; - ($temp:ident: Present) => { - let mut $temp = false; - }; -} - -macro_rules! do_parse { - ($elem:ident, Element) => { - $elem.clone() - }; - ($elem:ident, String) => { - $elem.text() - }; - ($elem:ident, $constructor:ident) => { - $constructor::try_from($elem.clone())? - }; -} - -macro_rules! do_parse_elem { - ($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { - $temp.push(do_parse!($elem, $constructor)); - }; - ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { - if $temp.is_some() { - return Err(crate::util::error::Error::ParseError(concat!( - "Element ", - $parent_name, - " must not have more than one ", - $name, - " child." - ))); - } - $temp = Some(do_parse!($elem, $constructor)); - }; - ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { - if $temp.is_some() { - return Err(crate::util::error::Error::ParseError(concat!( - "Element ", - $parent_name, - " must not have more than one ", - $name, - " child." - ))); - } - $temp = Some(do_parse!($elem, $constructor)); - }; - ($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { - if $temp { - return Err(crate::util::error::Error::ParseError(concat!( - "Element ", - $parent_name, - " must not have more than one ", - $name, - " child." - ))); - } - $temp = true; - }; -} - -macro_rules! finish_parse_elem { - ($temp:ident: Vec = $name:tt, $parent_name:tt) => { - $temp - }; - ($temp:ident: Option = $name:tt, $parent_name:tt) => { - $temp - }; - ($temp:ident: Required = $name:tt, $parent_name:tt) => { - $temp.ok_or(crate::util::error::Error::ParseError(concat!( - "Missing child ", - $name, - " in ", - $parent_name, - " element." - )))? - }; - ($temp:ident: Present = $name:tt, $parent_name:tt) => { - $temp - }; -} - -macro_rules! generate_serialiser { - ($builder:ident, $parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => { - $builder.append( - crate::Element::builder($name, crate::ns::$ns) - .append(::minidom::Node::Text($parent.$elem)), - ) - }; - ($builder:ident, $parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => { - $builder.append_all($parent.$elem.map(|elem| { - crate::Element::builder($name, crate::ns::$ns).append(::minidom::Node::Text(elem)) - })) - }; - ($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, *)) => { - $builder.append_all( - $parent - .$elem - .map(|elem| ::minidom::Node::Element(crate::Element::from(elem))), - ) - }; - ($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, $ns:ident)) => { - $builder.append_all( - $parent - .$elem - .map(|elem| ::minidom::Node::Element(crate::Element::from(elem))), - ) - }; - ($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => { - $builder.append_all($parent.$elem.into_iter()) - }; - ($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => { - $builder.append(::minidom::Node::Element( - crate::Element::builder($name, crate::ns::$ns).build(), - )) - }; - ($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => { - $builder.append(::minidom::Node::Element(crate::Element::from( - $parent.$elem, - ))) - }; -} - -macro_rules! generate_child_test { - ($child:ident, $name:tt, *) => { - $child.is($name, ::minidom::NSChoice::Any) - }; - ($child:ident, $name:tt, $ns:tt) => { - $child.is($name, crate::ns::$ns) - }; -} - -macro_rules! generate_element { - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+,]) => ( - generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: []); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+]) => ( - generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: []); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*]) => ( - generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),*,], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*]) => ( - generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => ( - generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>)); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),+], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => ( - generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_action<$attr_type> = $attr_name),*], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>)); - ); - ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_action:tt<$attr_type:ty> = $attr_name:tt),*], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:tt) => $child_constructor:ident),*] $(, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >))*) => ( - $(#[$meta])* - #[derive(Debug, Clone, PartialEq)] - pub struct $elem { - $( - $(#[$attr_meta])* - pub $attr: decl_attr!($attr_action, $attr_type), - )* - $( - $(#[$child_meta])* - pub $child_ident: start_decl!($coucou, $child_type), - )* - $( - $(#[$text_meta])* - pub $text_ident: $text_type, - )* - } - - impl ::std::convert::TryFrom for $elem { - type Error = crate::util::error::Error; - - fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> { - check_self!(elem, $name, $ns); - check_no_unknown_attributes!(elem, $name, [$($attr_name),*]); - $( - start_parse_elem!($child_ident: $coucou); - )* - for _child in elem.children() { - $( - if generate_child_test!(_child, $child_name, $child_ns) { - do_parse_elem!($child_ident: $coucou = $child_constructor => _child, $child_name, $name); - continue; - } - )* - return Err(crate::util::error::Error::ParseError(concat!("Unknown child in ", $name, " element."))); - } - Ok($elem { - $( - $attr: get_attr!(elem, $attr_name, $attr_action), - )* - $( - $child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name), - )* - $( - $text_ident: $codec::decode(&elem.text())?, - )* - }) - } - } - - impl From<$elem> for crate::Element { - fn from(elem: $elem) -> crate::Element { - let mut builder = crate::Element::builder($name, crate::ns::$ns); - $( - builder = builder.attr($attr_name, elem.$attr); - )* - $( - builder = generate_serialiser!(builder, elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns)); - )* - $( - builder = builder.append_all($codec::encode(&elem.$text_ident).map(::minidom::Node::Text).into_iter()); - )* - - builder.build() - } - } - ); -} - -#[cfg(test)] -macro_rules! assert_size ( - ($t:ty, $sz:expr) => ( - assert_eq!(::std::mem::size_of::<$t>(), $sz); - ); -); - -// TODO: move that to src/pubsub/mod.rs, once we figure out how to use macros from there. -macro_rules! impl_pubsub_item { - ($item:ident, $ns:ident) => { - impl ::std::convert::TryFrom for $item { - type Error = Error; - - fn try_from(elem: crate::Element) -> Result<$item, Error> { - check_self!(elem, "item", $ns); - check_no_unknown_attributes!(elem, "item", ["id", "publisher"]); - let mut payloads = elem.children().cloned().collect::>(); - let payload = payloads.pop(); - if !payloads.is_empty() { - return Err(Error::ParseError( - "More than a single payload in item element.", - )); - } - Ok($item(crate::pubsub::Item { - id: get_attr!(elem, "id", Option), - publisher: get_attr!(elem, "publisher", Option), - payload, - })) - } - } - - impl From<$item> for crate::Element { - fn from(item: $item) -> crate::Element { - crate::Element::builder("item", ns::$ns) - .attr("id", item.0.id) - .attr("publisher", item.0.publisher) - .append_all(item.0.payload) - .build() - } - } - - impl ::std::ops::Deref for $item { - type Target = crate::pubsub::Item; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl ::std::ops::DerefMut for $item { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - }; -} diff --git a/xmpp-parsers/src/util/mod.rs b/xmpp-parsers/src/util/mod.rs deleted file mode 100644 index 1d3ee64..0000000 --- a/xmpp-parsers/src/util/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/// Error type returned by every parser on failure. -pub mod error; - -/// Various helpers. -pub(crate) mod helpers; - -/// Helper macros to parse and serialise more easily. -#[macro_use] -mod macros; diff --git a/xmpp-parsers/src/version.rs b/xmpp-parsers/src/version.rs deleted file mode 100644 index 2bd894b..0000000 --- a/xmpp-parsers/src/version.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2017 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::iq::{IqGetPayload, IqResultPayload}; - -generate_empty_element!( - /// Represents a query for the software version a remote entity is using. - /// - /// It should only be used in an ``, as it can only - /// represent the request, and not a result. - VersionQuery, - "query", - VERSION -); - -impl IqGetPayload for VersionQuery {} - -generate_element!( - /// Represents the answer about the software version we are using. - /// - /// It should only be used in an ``, as it can only - /// represent the result, and not a request. - VersionResult, "query", VERSION, - children: [ - /// The name of this client. - name: Required = ("name", VERSION) => String, - - /// The version of this client. - version: Required = ("version", VERSION) => String, - - /// The OS this client is running on. - os: Option = ("os", VERSION) => String - ] -); - -impl IqResultPayload for VersionResult {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(VersionQuery, 0); - assert_size!(VersionResult, 36); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(VersionQuery, 0); - assert_size!(VersionResult, 72); - } - - #[test] - fn simple() { - let elem: Element = - "xmpp-rs0.3.0" - .parse() - .unwrap(); - let version = VersionResult::try_from(elem).unwrap(); - assert_eq!(version.name, String::from("xmpp-rs")); - assert_eq!(version.version, String::from("0.3.0")); - assert_eq!(version.os, None); - } - - #[test] - fn serialisation() { - let version = VersionResult { - name: String::from("xmpp-rs"), - version: String::from("0.3.0"), - os: None, - }; - let elem1 = Element::from(version); - let elem2: Element = - "xmpp-rs0.3.0" - .parse() - .unwrap(); - println!("{:?}", elem1); - assert_eq!(elem1, elem2); - } -} diff --git a/xmpp-parsers/src/websocket.rs b/xmpp-parsers/src/websocket.rs deleted file mode 100644 index fbc1fb2..0000000 --- a/xmpp-parsers/src/websocket.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2018 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use jid::BareJid; - -generate_element!( - /// The stream opening for WebSocket. - Open, "open", WEBSOCKET, - attributes: [ - /// The JID of the entity opening this stream. - from: Option = "from", - - /// The JID of the entity receiving this stream opening. - to: Option = "to", - - /// The id of the stream, used for authentication challenges. - id: Option = "id", - - /// The XMPP version used during this stream. - version: Option = "version", - - /// The default human language for all subsequent stanzas, which will - /// be transmitted to other entities for better localisation. - xml_lang: Option = "xml:lang", - ] -); - -impl Open { - /// Creates a simple client→server `` element. - pub fn new(to: BareJid) -> Open { - Open { - from: None, - to: Some(to), - id: None, - version: Some(String::from("1.0")), - xml_lang: None, - } - } - - /// Sets the [@from](#structfield.from) attribute on this `` - /// element. - pub fn with_from(mut self, from: BareJid) -> Open { - self.from = Some(from); - self - } - - /// Sets the [@id](#structfield.id) attribute on this `` element. - pub fn with_id(mut self, id: String) -> Open { - self.id = Some(id); - self - } - - /// Sets the [@xml:lang](#structfield.xml_lang) attribute on this `` - /// element. - pub fn with_lang(mut self, xml_lang: String) -> Open { - self.xml_lang = Some(xml_lang); - self - } - - /// Checks whether the version matches the expected one. - pub fn is_version(&self, version: &str) -> bool { - match self.version { - None => false, - Some(ref self_version) => self_version == &String::from(version), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Element; - use std::convert::TryFrom; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(Open, 84); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(Open, 168); - } - - #[test] - fn test_simple() { - let elem: Element = "" - .parse() - .unwrap(); - let open = Open::try_from(elem).unwrap(); - assert_eq!(open.from, None); - assert_eq!(open.to, None); - assert_eq!(open.id, None); - assert_eq!(open.version, None); - assert_eq!(open.xml_lang, None); - } -} diff --git a/xmpp-parsers/src/xhtml.rs b/xmpp-parsers/src/xhtml.rs deleted file mode 100644 index fd0312f..0000000 --- a/xmpp-parsers/src/xhtml.rs +++ /dev/null @@ -1,642 +0,0 @@ -// Copyright (c) 2019 Emmanuel Gil Peyrot -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::message::MessagePayload; -use crate::ns; -use crate::util::error::Error; -use minidom::{Element, Node}; -use std::collections::HashMap; -use std::convert::TryFrom; - -// TODO: Use a proper lang type. -type Lang = String; - -/// Container for formatted text. -#[derive(Debug, Clone)] -pub struct XhtmlIm { - /// Map of language to body element. - bodies: HashMap, -} - -impl XhtmlIm { - /// Serialise formatted text to HTML. - pub fn to_html(self) -> String { - let mut html = Vec::new(); - // TODO: use the best language instead. - for (lang, body) in self.bodies { - if lang.is_empty() { - assert!(body.xml_lang.is_none()); - } else { - assert_eq!(Some(lang), body.xml_lang); - } - for tag in body.children { - html.push(tag.to_html()); - } - break; - } - html.concat() - } - - /// Removes all unknown elements. - fn flatten(self) -> XhtmlIm { - let mut bodies = HashMap::new(); - for (lang, body) in self.bodies { - let children = body.children.into_iter().fold(vec![], |mut acc, child| { - match child { - Child::Tag(Tag::Unknown(children)) => acc.extend(children), - any => acc.push(any), - } - acc - }); - let body = Body { children, ..body }; - bodies.insert(lang, body); - } - XhtmlIm { bodies } - } -} - -impl MessagePayload for XhtmlIm {} - -impl TryFrom for XhtmlIm { - type Error = Error; - - fn try_from(elem: Element) -> Result { - check_self!(elem, "html", XHTML_IM); - check_no_attributes!(elem, "html"); - - let mut bodies = HashMap::new(); - for child in elem.children() { - if child.is("body", ns::XHTML) { - let child = child.clone(); - let lang = match child.attr("xml:lang") { - Some(lang) => lang, - None => "", - } - .to_string(); - let body = Body::try_from(child)?; - match bodies.insert(lang, body) { - None => (), - Some(_) => { - return Err(Error::ParseError( - "Two identical language bodies found in XHTML-IM.", - )) - } - } - } else { - return Err(Error::ParseError("Unknown element in XHTML-IM.")); - } - } - - Ok(XhtmlIm { bodies }.flatten()) - } -} - -impl From for Element { - fn from(wrapper: XhtmlIm) -> Element { - Element::builder("html", ns::XHTML_IM) - .append_all(wrapper.bodies.into_iter().map(|(lang, body)| { - if lang.is_empty() { - assert!(body.xml_lang.is_none()); - } else { - assert_eq!(Some(lang), body.xml_lang); - } - Element::from(body) - })) - .build() - } -} - -#[derive(Debug, Clone)] -enum Child { - Tag(Tag), - Text(String), -} - -impl Child { - fn to_html(self) -> String { - match self { - Child::Tag(tag) => tag.to_html(), - Child::Text(text) => text, - } - } -} - -#[derive(Debug, Clone)] -struct Property { - key: String, - value: String, -} - -type Css = Vec; - -fn get_style_string(style: Css) -> Option { - let mut result = vec![]; - for Property { key, value } in style { - result.push(format!("{}: {}", key, value)); - } - if result.is_empty() { - return None; - } - Some(result.join("; ")) -} - -#[derive(Debug, Clone)] -struct Body { - style: Css, - xml_lang: Option, - children: Vec, -} - -impl TryFrom for Body { - type Error = Error; - - fn try_from(elem: Element) -> Result { - let mut children = vec![]; - for child in elem.nodes() { - match child { - Node::Element(child) => children.push(Child::Tag(Tag::try_from(child.clone())?)), - Node::Text(text) => children.push(Child::Text(text.clone())), - } - } - - Ok(Body { - style: parse_css(elem.attr("style")), - xml_lang: elem.attr("xml:lang").map(|xml_lang| xml_lang.to_string()), - children, - }) - } -} - -impl From for Element { - fn from(body: Body) -> Element { - Element::builder("body", ns::XHTML) - .attr("style", get_style_string(body.style)) - .attr("xml:lang", body.xml_lang) - .append_all(children_to_nodes(body.children)) - .build() - } -} - -#[derive(Debug, Clone)] -enum Tag { - A { - href: Option, - style: Css, - type_: Option, - children: Vec, - }, - Blockquote { - style: Css, - children: Vec, - }, - Br, - Cite { - style: Css, - children: Vec, - }, - Em { - children: Vec, - }, - Img { - src: Option, - alt: Option, - }, // TODO: height, width, style - Li { - style: Css, - children: Vec, - }, - Ol { - style: Css, - children: Vec, - }, - P { - style: Css, - children: Vec, - }, - Span { - style: Css, - children: Vec, - }, - Strong { - children: Vec, - }, - Ul { - style: Css, - children: Vec, - }, - Unknown(Vec), -} - -impl Tag { - fn to_html(self) -> String { - match self { - Tag::A { - href, - style, - type_, - children, - } => { - let href = write_attr(href, "href"); - let style = write_attr(get_style_string(style), "style"); - let type_ = write_attr(type_, "type"); - format!( - "{}", - href, - style, - type_, - children_to_html(children) - ) - } - Tag::Blockquote { style, children } => { - let style = write_attr(get_style_string(style), "style"); - format!( - "{}", - style, - children_to_html(children) - ) - } - Tag::Br => String::from("
"), - Tag::Cite { style, children } => { - let style = write_attr(get_style_string(style), "style"); - format!("{}", style, children_to_html(children)) - } - Tag::Em { children } => format!("{}", children_to_html(children)), - Tag::Img { src, alt } => { - let src = write_attr(src, "src"); - let alt = write_attr(alt, "alt"); - format!("", src, alt) - } - Tag::Li { style, children } => { - let style = write_attr(get_style_string(style), "style"); - format!("{}", style, children_to_html(children)) - } - Tag::Ol { style, children } => { - let style = write_attr(get_style_string(style), "style"); - format!("{}", style, children_to_html(children)) - } - Tag::P { style, children } => { - let style = write_attr(get_style_string(style), "style"); - format!("{}

", style, children_to_html(children)) - } - Tag::Span { style, children } => { - let style = write_attr(get_style_string(style), "style"); - format!("{}", style, children_to_html(children)) - } - Tag::Strong { children } => format!("{}", children_to_html(children)), - Tag::Ul { style, children } => { - let style = write_attr(get_style_string(style), "style"); - format!("{}", style, children_to_html(children)) - } - Tag::Unknown(_) => { - panic!("No unknown element should be present in XHTML-IM after parsing.") - } - } - } -} - -impl TryFrom for Tag { - type Error = Error; - - fn try_from(elem: Element) -> Result { - let mut children = vec![]; - for child in elem.nodes() { - match child { - Node::Element(child) => children.push(Child::Tag(Tag::try_from(child.clone())?)), - Node::Text(text) => children.push(Child::Text(text.clone())), - } - } - - Ok(match elem.name() { - "a" => Tag::A { - href: elem.attr("href").map(|href| href.to_string()), - style: parse_css(elem.attr("style")), - type_: elem.attr("type").map(|type_| type_.to_string()), - children, - }, - "blockquote" => Tag::Blockquote { - style: parse_css(elem.attr("style")), - children, - }, - "br" => Tag::Br, - "cite" => Tag::Cite { - style: parse_css(elem.attr("style")), - children, - }, - "em" => Tag::Em { children }, - "img" => Tag::Img { - src: elem.attr("src").map(|src| src.to_string()), - alt: elem.attr("alt").map(|alt| alt.to_string()), - }, - "li" => Tag::Li { - style: parse_css(elem.attr("style")), - children, - }, - "ol" => Tag::Ol { - style: parse_css(elem.attr("style")), - children, - }, - "p" => Tag::P { - style: parse_css(elem.attr("style")), - children, - }, - "span" => Tag::Span { - style: parse_css(elem.attr("style")), - children, - }, - "strong" => Tag::Strong { children }, - "ul" => Tag::Ul { - style: parse_css(elem.attr("style")), - children, - }, - _ => Tag::Unknown(children), - }) - } -} - -impl From for Element { - fn from(tag: Tag) -> Element { - let (name, attrs, children) = match tag { - Tag::A { - href, - style, - type_, - children, - } => ( - "a", - { - let mut attrs = vec![]; - if let Some(href) = href { - attrs.push(("href", href)); - } - if let Some(style) = get_style_string(style) { - attrs.push(("style", style)); - } - if let Some(type_) = type_ { - attrs.push(("type", type_)); - } - attrs - }, - children, - ), - Tag::Blockquote { style, children } => ( - "blockquote", - match get_style_string(style) { - Some(style) => vec![("style", style)], - None => vec![], - }, - children, - ), - Tag::Br => ("br", vec![], vec![]), - Tag::Cite { style, children } => ( - "cite", - match get_style_string(style) { - Some(style) => vec![("style", style)], - None => vec![], - }, - children, - ), - Tag::Em { children } => ("em", vec![], children), - Tag::Img { src, alt } => { - let mut attrs = vec![]; - if let Some(src) = src { - attrs.push(("src", src)); - } - if let Some(alt) = alt { - attrs.push(("alt", alt)); - } - ("img", attrs, vec![]) - } - Tag::Li { style, children } => ( - "li", - match get_style_string(style) { - Some(style) => vec![("style", style)], - None => vec![], - }, - children, - ), - Tag::Ol { style, children } => ( - "ol", - match get_style_string(style) { - Some(style) => vec![("style", style)], - None => vec![], - }, - children, - ), - Tag::P { style, children } => ( - "p", - match get_style_string(style) { - Some(style) => vec![("style", style)], - None => vec![], - }, - children, - ), - Tag::Span { style, children } => ( - "span", - match get_style_string(style) { - Some(style) => vec![("style", style)], - None => vec![], - }, - children, - ), - Tag::Strong { children } => ("strong", vec![], children), - Tag::Ul { style, children } => ( - "ul", - match get_style_string(style) { - Some(style) => vec![("style", style)], - None => vec![], - }, - children, - ), - Tag::Unknown(_) => { - panic!("No unknown element should be present in XHTML-IM after parsing.") - } - }; - let mut builder = Element::builder(name, ns::XHTML).append_all(children_to_nodes(children)); - for (key, value) in attrs { - builder = builder.attr(key, value); - } - builder.build() - } -} - -fn children_to_nodes(children: Vec) -> impl IntoIterator { - children.into_iter().map(|child| match child { - Child::Tag(tag) => Node::Element(Element::from(tag)), - Child::Text(text) => Node::Text(text), - }) -} - -fn children_to_html(children: Vec) -> String { - children - .into_iter() - .map(|child| child.to_html()) - .collect::>() - .concat() -} - -fn write_attr(attr: Option, name: &str) -> String { - match attr { - Some(attr) => format!(" {}='{}'", name, attr), - None => String::new(), - } -} - -fn parse_css(style: Option<&str>) -> Css { - let mut properties = vec![]; - if let Some(style) = style { - // TODO: make that parser a bit more resilient to things. - for part in style.split(";") { - let mut part = part - .splitn(2, ":") - .map(|a| a.to_string()) - .collect::>(); - let key = part.pop().unwrap(); - let value = part.pop().unwrap(); - properties.push(Property { key, value }); - } - } - properties -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "32")] - #[test] - fn test_size() { - assert_size!(XhtmlIm, 32); - assert_size!(Child, 56); - assert_size!(Tag, 52); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn test_size() { - assert_size!(XhtmlIm, 48); - assert_size!(Child, 112); - assert_size!(Tag, 104); - } - - #[test] - fn test_empty() { - let elem: Element = "" - .parse() - .unwrap(); - let xhtml = XhtmlIm::try_from(elem).unwrap(); - assert_eq!(xhtml.bodies.len(), 0); - - let elem: Element = "" - .parse() - .unwrap(); - let xhtml = XhtmlIm::try_from(elem).unwrap(); - assert_eq!(xhtml.bodies.len(), 1); - - let elem: Element = "" - .parse() - .unwrap(); - let xhtml = XhtmlIm::try_from(elem).unwrap(); - assert_eq!(xhtml.bodies.len(), 2); - } - - #[test] - fn invalid_two_same_langs() { - let elem: Element = "" - .parse() - .unwrap(); - let error = XhtmlIm::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, - _ => panic!(), - }; - assert_eq!(message, "Two identical language bodies found in XHTML-IM."); - } - - #[test] - fn test_tag() { - let elem: Element = "" - .parse() - .unwrap(); - let body = Body::try_from(elem).unwrap(); - assert_eq!(body.children.len(), 0); - - let elem: Element = "

Hello world!

" - .parse() - .unwrap(); - let mut body = Body::try_from(elem).unwrap(); - assert_eq!(body.style.len(), 0); - assert_eq!(body.xml_lang, None); - assert_eq!(body.children.len(), 1); - let p = match body.children.pop() { - Some(Child::Tag(tag)) => tag, - _ => panic!(), - }; - let mut children = match p { - Tag::P { style, children } => { - assert_eq!(style.len(), 0); - assert_eq!(children.len(), 1); - children - } - _ => panic!(), - }; - let text = match children.pop() { - Some(Child::Text(text)) => text, - _ => panic!(), - }; - assert_eq!(text, "Hello world!"); - } - - #[test] - fn test_unknown_element() { - let elem: Element = "Hello world!" - .parse() - .unwrap(); - let parsed = XhtmlIm::try_from(elem).unwrap(); - let parsed2 = parsed.clone(); - let html = parsed.to_html(); - assert_eq!(html, "Hello world!"); - - let elem = Element::from(parsed2); - assert_eq!(String::from(&elem), "Hello world!"); - } - - #[test] - fn test_generate_html() { - let elem: Element = "

Hello world!

" - .parse() - .unwrap(); - let xhtml_im = XhtmlIm::try_from(elem).unwrap(); - let html = xhtml_im.to_html(); - assert_eq!(html, "

Hello world!

"); - - let elem: Element = "

Hello world!

" - .parse() - .unwrap(); - let xhtml_im = XhtmlIm::try_from(elem).unwrap(); - let html = xhtml_im.to_html(); - assert_eq!(html, "

Hello world!

"); - } - - #[test] - fn generate_tree() { - let world = "world".to_string(); - - Body { - style: vec![], - xml_lang: Some("en".to_string()), - children: vec![Child::Tag(Tag::P { - style: vec![], - children: vec![ - Child::Text("Hello ".to_string()), - Child::Tag(Tag::Strong { - children: vec![Child::Text(world)], - }), - Child::Text("!".to_string()), - ], - })], - }; - } -}