From f5a6ce44c747841517a10cc0b1fceac176ac5e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 4 Jul 2019 23:54:43 +0200 Subject: [PATCH] wrap some rr fields in flags/enum types with some derived help, add [c]dnskey tag calculation --- lib/dnsbox-base/src/common_types/binary.rs | 40 ++++++ lib/dnsbox-base/src/common_types/caa.rs | 9 ++ lib/dnsbox-base/src/common_types/dnssec.rs | 125 +++++++++++++++++++ lib/dnsbox-base/src/common_types/mod.rs | 6 + lib/dnsbox-base/src/common_types/sshfp.rs | 25 ++++ lib/dnsbox-base/src/records/structs.rs | 105 ++++++++++++---- lib/dnsbox-derive/src/lib.rs | 5 +- lib/dnsbox-derive/src/native_enum.rs | 88 +++++++++++++ lib/dnsbox-derive/src/native_flags.rs | 136 +++++++++++++++++++++ 9 files changed, 513 insertions(+), 26 deletions(-) create mode 100644 lib/dnsbox-base/src/common_types/caa.rs create mode 100644 lib/dnsbox-base/src/common_types/dnssec.rs create mode 100644 lib/dnsbox-base/src/common_types/sshfp.rs create mode 100644 lib/dnsbox-derive/src/native_enum.rs create mode 100644 lib/dnsbox-derive/src/native_flags.rs diff --git a/lib/dnsbox-base/src/common_types/binary.rs b/lib/dnsbox-base/src/common_types/binary.rs index 84a62e3..2d60073 100644 --- a/lib/dnsbox-base/src/common_types/binary.rs +++ b/lib/dnsbox-base/src/common_types/binary.rs @@ -65,6 +65,14 @@ impl DnsTextData for HexShortBlob { } } +impl std::ops::Deref for HexShortBlob { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + // 16-bit length, uses decimal length + base64 encoding (if length > 0) // for text representation. // @@ -116,6 +124,14 @@ impl DnsTextData for Base64LongBlob { } } +impl std::ops::Deref for Base64LongBlob { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// No length byte (or restriction), just all data to end of record. uses /// base64 encoding for text representation, whitespace allowed, padding /// required. @@ -154,6 +170,14 @@ impl DnsTextData for Base64RemainingBlob { } } +impl std::ops::Deref for Base64RemainingBlob { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// No length byte (or restriction), just all data to end of record. uses /// hex encoding for text representation, whitespace allowed. /// @@ -187,6 +211,14 @@ impl DnsTextData for HexRemainingBlob { } } +impl std::ops::Deref for HexRemainingBlob { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// No length byte (or restriction), just all data to end of record. uses /// hex encoding for text representation, whitespace allowed. /// @@ -226,3 +258,11 @@ impl DnsTextData for HexRemainingBlobNotEmpty { write!(f, "{}", HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.0)) } } + +impl std::ops::Deref for HexRemainingBlobNotEmpty { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/lib/dnsbox-base/src/common_types/caa.rs b/lib/dnsbox-base/src/common_types/caa.rs new file mode 100644 index 0000000..f15be53 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/caa.rs @@ -0,0 +1,9 @@ +use crate::ser::{packet::DnsPacketData, text::DnsTextData}; + +#[dnsbox_derive::native_flags(u8)] +#[derive(DnsPacketData, DnsTextData)] +/// Flags for the CAA RR +pub enum CaaFlags { + /// Issuer Critical Flag + CRITICAL = 0x01, // bit "7" +} diff --git a/lib/dnsbox-base/src/common_types/dnssec.rs b/lib/dnsbox-base/src/common_types/dnssec.rs new file mode 100644 index 0000000..e00857f --- /dev/null +++ b/lib/dnsbox-base/src/common_types/dnssec.rs @@ -0,0 +1,125 @@ +use crate::ser::{packet::DnsPacketData, text::DnsTextData}; + +// https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml +#[dnsbox_derive::native_enum(u8)] +#[derive(DnsPacketData, DnsTextData)] +pub enum DnsSecAlgorithm { + /// Delete DS + // [RFC4034][RFC4398][RFC8078] + DELETE = 0, + /// RSA/MD5 (deprecated, see 5) + // [RFC3110][RFC4034] + RSAMD5 = 1, + /// Diffie-Hellman + // [RFC2539][proposed standard] + DH = 2, + /// DSA/SHA1 + // [RFC3755][proposed standard][RFC2536][proposed standard][Federal Information Processing Standards Publication (FIPS PUB) 186, Digital Signature Standard, 18 May 1994.][Federal Information Processing Standards Publication (FIPS PUB) 180-1, Secure Hash Standard, 17 April 1995. (Supersedes FIPS PUB 180 dated 11 May 1993.)] + DSA = 3, + + // Reserved: 4 [RFC6725] + + /// RSA/SHA-1 + // [RFC3110][RFC4034] + RSASHA1 = 5, + /// DSA-NSEC3-SHA1 + // [RFC5155][proposed standard] + DSA_NSEC3_SHA1 = 6, + /// RSASHA1-NSEC3-SHA1 + // [RFC5155][proposed standard] + RSASHA1_NSEC3_SHA1 = 7, + /// RSA/SHA-256 + // [RFC5702][proposed standard] + RSASHA256 = 8, + + // Reserved: 9 [RFC6725] + + /// RSA/SHA-512 + // [RFC5702][proposed standard] + RSASHA512 = 10, + + // Reserved: 11 [RFC6725] + + /// GOST R 34.10-2001 + // [RFC5933][standards track] + ECC_GOST = 12, + /// ECDSA Curve P-256 with SHA-256 + // [RFC6605][standards track] + ECDSAP256SHA256 = 13, + /// ECDSA Curve P-384 with SHA-384 + // [RFC6605][standards track] + ECDSAP384SHA384 = 14, + /// Ed25519 + // [RFC8080][standards track] + ED25519 = 15, + /// Ed448 + // [RFC8080][standards track] + ED448 = 16, + /// Reserved for Indirect Keys + // [RFC4034][proposed standard] + INDIRECT = 252, + /// private algorithm + // [RFC4034] + PRIVATEDNS = 253, + /// private algorithm OID + // [RFC4034] + PRIVATEOID = 254, + + // Reserved: 255 [RFC4034][proposed standard] +} + +#[dnsbox_derive::native_flags(u16)] +#[derive(DnsPacketData, DnsTextData)] +/// Flags for the DNSKEY RR +pub enum DnskeyFlags { + ZONE_KEY = 0x0100, // bit "7" + /// secure entry point, SEP + SECURE_ENTRY_POINT = 0x0001, // bit "15" +} + +#[dnsbox_derive::native_enum(u8)] +#[derive(DnsPacketData, DnsTextData)] +/// Protocol for the DNSKEY RR (only DNSSEC(3) is valid for DNSKEY) +pub enum DnskeyProtocol { + // reserved: 0x00 + TLS = 0x01, + EMAIL = 0x02, + DNSSEC = 0x03, + IPSEC = 0x04, + ALL = 0xff, +} + +// https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml +#[dnsbox_derive::native_enum(u8)] +#[derive(DnsPacketData, DnsTextData)] +pub enum DnsSecDigestAlgorithm { + // Reserved: 0 [RFC3658] + SHA1 = 0x01, // [RFC3658] + SHA256 = 0x02, // [RFC4509] + GOST_R_34_11_94 = 0x03, // [RFC5933] + SHA384 = 0x04, // [RFC6605] +} + +// https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml +#[dnsbox_derive::native_flags(u8)] +#[derive(DnsPacketData, DnsTextData)] +/// Flags for the NSEC3 RR +pub enum Nsec3Flags { + OPT_OUT = 0x01, // bit "7" +} + +// https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml +#[dnsbox_derive::native_flags(u8)] +#[derive(DnsPacketData, DnsTextData)] +/// Flags for the NSEC3PARAM RR +pub enum Nsec3ParamFlags { + // reserved: Nsec3Flags::OPT_OUT bit "7" (0x01) +} + +// https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml +#[dnsbox_derive::native_enum(u8)] +#[derive(DnsPacketData, DnsTextData)] +pub enum Nsec3Algorithm { + // Reserved: 0 [RFC5155] + SHA1 = 0x01, // [RFC5155] +} diff --git a/lib/dnsbox-base/src/common_types/mod.rs b/lib/dnsbox-base/src/common_types/mod.rs index d83aa5a..f732287 100644 --- a/lib/dnsbox-base/src/common_types/mod.rs +++ b/lib/dnsbox-base/src/common_types/mod.rs @@ -3,20 +3,26 @@ pub mod classes; pub mod name; pub mod text; pub mod types; +mod caa; +mod dnssec; mod eui; mod nsec; mod nxt; mod sig; +mod sshfp; mod time; mod uri; pub use self::binary::{HexShortBlob, Base64LongBlob, Base64RemainingBlob, HexRemainingBlob, HexRemainingBlobNotEmpty}; pub use self::classes::Class; +pub use self::caa::{CaaFlags}; +pub use self::dnssec::{DnsSecAlgorithm, DnskeyFlags, DnskeyProtocol, DnsSecDigestAlgorithm, Nsec3Flags, Nsec3ParamFlags, Nsec3Algorithm}; pub use self::eui::{EUI48Addr, EUI64Addr}; pub use self::name::{DnsName, DnsCanonicalName, DnsCompressedName}; pub use self::nsec::{NsecTypeBitmap, NextHashedOwnerName}; pub use self::nxt::NxtTypeBitmap; pub use self::sig::OptionalTTL; +pub use self::sshfp::{SshFpAlgorithm, SshFpType}; pub use self::text::{ShortText, LongText, UnquotedShortText, RemainingText}; pub use self::time::{Time, TimeStrict, Time48}; pub use self::types::Type; diff --git a/lib/dnsbox-base/src/common_types/sshfp.rs b/lib/dnsbox-base/src/common_types/sshfp.rs new file mode 100644 index 0000000..85e6ba8 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/sshfp.rs @@ -0,0 +1,25 @@ +use crate::ser::{packet::DnsPacketData, text::DnsTextData}; + +// https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xml + +#[dnsbox_derive::native_enum(u8)] +#[derive(DnsPacketData, DnsTextData)] +pub enum SshFpAlgorithm { + // Reserved: 0 + + // [RFC4255] + RSA = 1, + // [RFC4255] + DSA = 2, + // [RFC6594] + ECDSA = 3, + // [RFC7479] + ED25519 = 4, +} + +#[dnsbox_derive::native_enum(u8)] +#[derive(DnsPacketData, DnsTextData)] +pub enum SshFpType { + SHA1 = 1, + SHA256 = 2, +} diff --git a/lib/dnsbox-base/src/records/structs.rs b/lib/dnsbox-base/src/records/structs.rs index 5147cea..5259c28 100644 --- a/lib/dnsbox-base/src/records/structs.rs +++ b/lib/dnsbox-base/src/records/structs.rs @@ -179,7 +179,7 @@ pub struct NSAP_PTR { #[RRClass(ANY)] pub struct SIG { pub rr_type: Type, - pub algorithm: u8, + pub algorithm: DnsSecAlgorithm, pub labels: u8, // RFC says this can be omitted in text form if it is the same as // the TTL on the SIG record. not supported to be omitted here @@ -196,8 +196,8 @@ pub struct SIG { #[RRClass(ANY)] pub struct KEY { pub flags: u16, - pub protocol: u8, - pub algorithm: u8, + pub protocol: DnskeyProtocol, + pub algorithm: DnsSecAlgorithm, pub public_key: Base64RemainingBlob, } @@ -274,9 +274,10 @@ pub struct KX { #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] // #[RRClass(?)] pub struct CERT { + // https://www.iana.org/assignments/cert-rr-types/cert-rr-types.xhtml pub cert_type: u16, pub key_tag: u16, - pub algorithm: u8, + pub algorithm: DnsSecAlgorithm, pub certificate: Base64RemainingBlob, } @@ -304,16 +305,16 @@ pub struct DNAME { #[RRClass(ANY)] pub struct DS { pub key_tag: u16, - pub algorithm: u8, - pub digest_type: u8, + pub algorithm: DnsSecAlgorithm, + pub digest_type: DnsSecDigestAlgorithm, pub digest: HexRemainingBlob, } #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] // #[RRClass(?)] pub struct SSHFP { - pub algorithm: u8, - pub fingerprint_type: u8, + pub algorithm: SshFpAlgorithm, + pub fingerprint_type: SshFpType, // RFC 4255 doesn't specify whether whitespace is allowed. // `HexRemainingBlob` allows whitespace. pub fingerprint: HexRemainingBlob, @@ -325,7 +326,7 @@ pub use super::weird_structs::IPSECKEY; #[RRClass(ANY)] pub struct RRSIG { pub rr_type: Type, - pub algorithm: u8, + pub algorithm: DnsSecAlgorithm, pub labels: u8, pub original_ttl: u32, pub signature_expiration: Time, @@ -345,12 +346,55 @@ pub struct NSEC { #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] pub struct DNSKEY { - pub flags: u16, - pub protocol: u8, - pub algorithm: u8, + pub flags: DnskeyFlags, + pub protocol: DnskeyProtocol, // should always be DNSSEC (3) according to RFC 4034 + pub algorithm: DnsSecAlgorithm, pub public_key: Base64RemainingBlob, } +impl DNSKEY { + fn alg1_tag(&self) -> u16 { + let key: &[u8] = &self.public_key; + if key.is_empty() { return 0; } // not enough data + let pkey; + if 0 == key[0] { + // two-byte length encoding of exponent + if key.len() < 3 { return 0; } // not enough data + let explen = ((key[1] as u16) << 8) + (key[2] as u16); + if explen < 256 { return 0; } // should have used shorter length encoding + if key.len() < 3 + (explen as usize) { return 0; } // not enough data + pkey = &key[3 + (explen as usize)..]; + } else { + // one-byte length encoding of exponent + let explen = key[0]; + if key.len() < 1 + (explen as usize) { return 0; } // not enough data + pkey = &key[1 + (explen as usize)..]; + } + if pkey.len() < 3 { return 0; } // not enough data + ((pkey[pkey.len() - 3] as u16) << 8) + (pkey[pkey.len() - 3] as u16) + } + + /// calculate key tag + pub fn tag(&self) -> u16 { + if self.algorithm == DnsSecAlgorithm::RSAMD5 { return self.alg1_tag(); } + + let mut sum = 0u32; + + sum += self.flags.0 as u32; + sum += (self.protocol.0 as u32) << 8; + sum += self.algorithm.0 as u32; + + let key: &[u8] = &self.public_key; + for i in 0..key.len() { + let v = key[i] as u32; + let v = if 0 == i & 1 { v << 8 } else { v }; + sum = sum.wrapping_add(v); + } + + sum.wrapping_add(sum >> 16) as u16 + } +} + #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(IN)] pub struct DHCID { @@ -360,8 +404,8 @@ pub struct DHCID { #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] pub struct NSEC3 { - pub hash_algorithm: u8, - pub flags: u8, + pub hash_algorithm: Nsec3Algorithm, + pub flags: Nsec3Flags, pub iterations: u16, pub salt: HexShortBlob, pub next_hashed: NextHashedOwnerName, @@ -371,8 +415,8 @@ pub struct NSEC3 { #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] pub struct NSEC3PARAM { - pub hash_algorithm: u8, - pub flags: u8, + pub hash_algorithm: Nsec3Algorithm, + pub flags: Nsec3ParamFlags, pub iterations: u16, pub salt: HexShortBlob, } @@ -413,7 +457,7 @@ pub struct NINFO { pub struct RKEY { pub flags: u16, pub protocol: u8, - pub algorithm: u8, + pub algorithm: DnsSecAlgorithm, pub public_key: Base64RemainingBlob, } @@ -425,20 +469,31 @@ pub struct RKEY { #[RRClass(ANY)] pub struct CDS { pub key_tag: u16, - pub algorithm: u8, - pub digest_type: u8, + pub algorithm: DnsSecAlgorithm, + pub digest_type: DnsSecDigestAlgorithm, pub digest: HexRemainingBlob, } #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] pub struct CDNSKEY { - pub flags: u16, - pub protocol: u8, - pub algorithm: u8, + pub flags: DnskeyFlags, + pub protocol: DnskeyProtocol, + pub algorithm: DnsSecAlgorithm, pub public_key: Base64RemainingBlob, } +impl CDNSKEY { + pub fn tag(&self) -> u16 { + DNSKEY { + flags: self.flags, + protocol: self.protocol, + algorithm: self.algorithm, + public_key: self.public_key.clone(), + }.tag() + } +} + #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] pub struct OPENPGPKEY { @@ -545,7 +600,7 @@ pub struct URI { #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] // #[RRClass(?)] pub struct CAA { - pub flags: u8, + pub flags: CaaFlags, pub tag: UnquotedShortText, pub value: RemainingText, } @@ -559,8 +614,8 @@ pub struct CAA { #[RRClass(ANY)] pub struct DLV { pub key_tag: u16, - pub algorithm: u8, - pub digest_type: u8, + pub algorithm: DnsSecAlgorithm, + pub digest_type: DnsSecDigestAlgorithm, pub digest: HexRemainingBlob, } diff --git a/lib/dnsbox-derive/src/lib.rs b/lib/dnsbox-derive/src/lib.rs index 8d674f5..c834247 100644 --- a/lib/dnsbox-derive/src/lib.rs +++ b/lib/dnsbox-derive/src/lib.rs @@ -9,11 +9,14 @@ extern crate proc_macro2; mod rrdata; mod dns_packet_data; mod dns_text_data; +mod native_enum; +mod native_flags; decl_derive!([DnsPacketData] => dns_packet_data::derive); decl_derive!([DnsTextData] => dns_text_data::derive); decl_derive!([RRData, attributes(RRTypeName, RRClass)] => rrdata::rrdata_derive); - +decl_attribute!([native_enum] => native_enum::attribute_native_enum); +decl_attribute!([native_flags] => native_flags::attribute_native_flags); fn attr_get_single_list_arg(attr_meta: &syn::Meta) -> proc_macro2::TokenStream { match attr_meta { diff --git a/lib/dnsbox-derive/src/native_enum.rs b/lib/dnsbox-derive/src/native_enum.rs new file mode 100644 index 0000000..15d3fd4 --- /dev/null +++ b/lib/dnsbox-derive/src/native_enum.rs @@ -0,0 +1,88 @@ +use proc_macro2::TokenStream; + +pub fn attribute_native_enum( + native_type: TokenStream, + structure: synstructure::Structure, +) -> TokenStream { + let ast = structure.ast(); + let in_attrs = ast.attrs.iter().map(|a| quote!{#a}).collect::(); + let in_vis = &ast.vis; + let name = &ast.ident; + + let known_name = syn::Ident::new(&format!("{}Known", name), proc_macro2::Span::call_site()); + + let enumdata = match &ast.data { + syn::Data::Enum(de) => de, + _ => panic!("not an enum"), + }; + let known_enum; + + { + let variants = &enumdata.variants; + let doc_str = format!("Known enum variants of [`{}`]\n\n[`{}`]: struct.{}.html\n", name, name, name); + known_enum = quote! { + #[doc = #doc_str] + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + #[repr(#native_type)] + #[allow(non_camel_case_types)] + #in_vis enum #known_name { + #variants + } + } + } + + let mut consts = TokenStream::new(); + let mut convert = TokenStream::new(); + for variant in &enumdata.variants { + let variant_attrs = variant.attrs.iter().map(|a| quote!{#a}).collect::(); + let disc_name = &variant.ident; + let disc = variant.discriminant.as_ref().map(|(_, d)| quote!{#d}).unwrap_or_else(|| quote!{ + #known_name::#disc_name as #native_type + }); + consts.extend(quote! { + #variant_attrs + pub const #disc_name: Self = #name(#disc); + }); + convert.extend(quote! { + Self::#disc_name => #known_name::#disc_name, + }); + } + + quote! { + #known_enum + + #in_attrs + #[derive(Clone, Copy, PartialEq, Eq)] + #in_vis struct #name(pub #native_type); + + impl #name { + #consts + + /// Try converting to known enum values + pub fn into_known(self) -> Option<#known_name> { + Some( + #[allow(unreachable_patterns)] + match self { + #convert + _ => return None, + } + ) + } + } + + impl core::fmt::Debug for #name { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self.into_known() { + Some(v) => v.fmt(f), + None => f.debug_tuple(stringify!(#name)).field(&self.0).finish(), + } + } + } + + impl From<#known_name> for #name { + fn from(v: #known_name) -> Self { + #name(v as #native_type) + } + } + } +} diff --git a/lib/dnsbox-derive/src/native_flags.rs b/lib/dnsbox-derive/src/native_flags.rs new file mode 100644 index 0000000..f8be9bf --- /dev/null +++ b/lib/dnsbox-derive/src/native_flags.rs @@ -0,0 +1,136 @@ +use proc_macro2::TokenStream; + +pub fn attribute_native_flags( + native_type: TokenStream, + structure: synstructure::Structure, +) -> TokenStream { + let ast = structure.ast(); + let in_attrs = ast.attrs.iter().map(|a| quote!{#a}).collect::(); + let in_vis = &ast.vis; + let name = &ast.ident; + + let hidden_impl_mod = syn::Ident::new(&format!("_{}_hidden_native_flags", name), proc_macro2::Span::call_site()); + + let enumdata = match &ast.data { + syn::Data::Enum(de) => de, + _ => panic!("not an enum"), + }; + + let mut consts = TokenStream::new(); + let mut known_mask = TokenStream::new(); + let mut dbg = TokenStream::new(); + for variant in &enumdata.variants { + let variant_attrs = variant.attrs.iter().map(|a| quote!{#a}).collect::(); + let disc_name = &variant.ident; + let disc = variant.discriminant.as_ref().map(|(_, d)| d).expect("all variants need explicit bitmask discriminants"); + consts.extend(quote! { + #variant_attrs + pub const #disc_name: Flag = Flag { mask: #disc }; + }); + known_mask.extend(quote!{ + | #disc + }); + dbg.extend(quote! { + if self & Self::#disc_name { + if !first { f.write_str(" | ")?; } + first = false; + f.write_str(stringify!(#disc_name))?; + } + }); + } + + let bitops = quote! { + impl core::ops::BitAnd for super::#name { + type Output = bool; + + fn bitand(self, rhs: Flag) -> Self::Output { + rhs.mask == self.0 & rhs.mask + } + } + + impl core::ops::BitAnd for &super::#name { + type Output = bool; + + fn bitand(self, rhs: Flag) -> Self::Output { + rhs.mask == self.0 & rhs.mask + } + } + + impl core::ops::BitOr for Flag { + type Output = super::#name; + + fn bitor(self, rhs: Flag) -> Self::Output { + super::#name(self.mask | rhs.mask) + } + } + + impl core::ops::BitOr for super::#name { + type Output = super::#name; + + fn bitor(self, rhs: Flag) -> Self::Output { + super::#name(self.0 | rhs.mask) + } + } + + impl core::ops::BitOr for Flag { + type Output = super::#name; + + fn bitor(self, rhs: super::#name) -> Self::Output { + super::#name(self.mask | rhs.0) + } + } + }; + + quote! { + #in_attrs + /// + /// The `Flag` constants can be used to check if a flag is set + /// by using bitwise and: `flags & flag`. + /// + /// They can be converted to and combined with bitwise or to set + /// flags. Their bitmask is available as public struct member + /// `mask`. + #[derive(Clone, Copy, PartialEq, Eq)] + #in_vis struct #name(pub #native_type); + + mod #hidden_impl_mod { + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub struct Flag { + pub mask: #native_type, + } + + const known_mask: #native_type = 0 #known_mask; + + impl super::#name { + pub fn new_flag(mask: #native_type) -> Flag { + Flag { mask } + } + + #consts + } + + #bitops + + impl From for super::#name { + fn from(v: Flag) -> Self { + Self(v.mask) + } + } + + impl core::fmt::Debug for super::#name { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let mut first = true; + let unknown_bits = self.0 & !known_mask; + #dbg + if unknown_bits != 0 { + if !first { f.write_str(" | ")?; } + write!(f, "unknown(0x{:x})", unknown_bits)?; + } else if first { + f.write_str("(empty)")?; + } + Ok(()) + } + } + } + } +}