1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
use std::ffi::NulError;
use std::fmt;

use crate::Service;
use bonjour_sys::DNSServiceErrorType;
use std::error::Error;
use std::str::Utf8Error;
use std::sync::PoisonError;

/// Custom error holding any potential errors from publishing or browsing for
/// services.
#[derive(Debug)]
pub enum ZeroconfError {
    /// An error from the Bonjour API ([BonjourError])
    Bonjour(BonjourError),
    /// An IO error on the internal socket used to poll for events
    Io(std::io::Error),
    /// A timeout occurred before the operation was complete
    ///
    /// Contains the service that could not be resolved. This is only valid
    /// for resolve operations as browsing does not have a specific endpoint
    /// and so timing out is not an error.
    Timeout(Service),
    /// The service type specified is invalid
    InvalidServiceType(String),
    /// The TXT record specified is invalid
    InvalidTxtRecord(String),
    /// A service was passed to resolve that was not from a
    /// [ServiceBrowser][crate::ServiceBrowser].
    ///
    /// This is an error as the resolve operation requires the information
    /// about domain, interface and so on to be in the format provided from
    /// the browse operation.
    NotFromBrowser(Service),
    /// Null byte in a string conversion
    NullString(NulError),
    /// Poisoned Mutex
    Poison(),
    /// Failed to convert to a UTF-8 string
    Utf8(Utf8Error),
    /// Interface not found
    InterfaceNotFound(String),
}

impl From<PoisonError<std::sync::MutexGuard<'_, ()>>> for ZeroconfError {
    fn from(_: PoisonError<std::sync::MutexGuard<'_, ()>>) -> Self {
        ZeroconfError::Poison()
    }
}

impl From<NulError> for ZeroconfError {
    fn from(s: NulError) -> Self {
        ZeroconfError::NullString(s)
    }
}

impl From<Utf8Error> for ZeroconfError {
    fn from(s: Utf8Error) -> Self {
        ZeroconfError::Utf8(s)
    }
}

impl From<std::io::Error> for ZeroconfError {
    fn from(s: std::io::Error) -> Self {
        ZeroconfError::Io(s)
    }
}

impl From<i32> for ZeroconfError {
    fn from(s: i32) -> Self {
        ZeroconfError::Bonjour(s.into())
    }
}

impl From<BonjourError> for ZeroconfError {
    fn from(s: BonjourError) -> Self {
        ZeroconfError::Bonjour(s)
    }
}

impl fmt::Display for ZeroconfError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            ZeroconfError::Bonjour(e) => format!("error from bonjour - {}", e),
            ZeroconfError::Io(e) => e.to_string(),
            ZeroconfError::Timeout(s) => format!("timeout on {}", s.service_type()),
            ZeroconfError::InvalidServiceType(s) => format!("invalid service type '{}'", s),
            ZeroconfError::InvalidTxtRecord(s) => format!("invalid txt record '{}'", s),
            ZeroconfError::NotFromBrowser(s) => {
                format!("'{}' service not from browser", s.service_type())
            }
            ZeroconfError::NullString(s) => s.to_string(),
            ZeroconfError::Poison() => "mutex was poisoned".to_string(),
            ZeroconfError::Utf8(e) => e.to_string(),
            ZeroconfError::InterfaceNotFound(s) => format!("interface not found '{}'", s),
        };
        write!(f, "{}", s)
    }
}

impl Error for ZeroconfError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            ZeroconfError::Bonjour(e) => Some(e),
            ZeroconfError::Io(e) => Some(e),
            ZeroconfError::NullString(e) => Some(e),
            ZeroconfError::Utf8(e) => Some(e),
            _ => None,
        }
    }
}

/// An error from the Bonjour API
///
/// Further information about the requirements for service parameters can be
/// found in the [Bonjour API][b] documentation.
///
/// [b]: https://developer.apple.com/documentation/dnssd/dns_service_discovery_c
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum BonjourError {
    Unknown,
    NoSuchName,
    NoMemory,
    BadParam,
    BadReference,
    BadState,
    BadFlags,
    Unsupported,
    NotInitialized,
    AlreadyRegistered,
    NameConflict,
    Invalid,
    Firewall,
    Incompatible,
    BadInterfaceIndex,
    Refused,
    NoSuchRecord,
    NoAuth,
    NoSuchKey,
    NATTraversal,
    DoubleNAT,
    BadTime,
    Undefined,
}

impl fmt::Display for BonjourError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            BonjourError::Unknown => "unknown error",
            BonjourError::NoSuchName => "no such name",
            BonjourError::NoMemory => "no memory",
            BonjourError::BadParam => "bad parameter",
            BonjourError::BadReference => "bad reference",
            BonjourError::BadState => "bad state",
            BonjourError::BadFlags => "bad flags",
            BonjourError::Unsupported => "unsupported",
            BonjourError::NotInitialized => "not initialized",
            BonjourError::AlreadyRegistered => "already registered",
            BonjourError::NameConflict => "name conflict",
            BonjourError::Invalid => "invalid",
            BonjourError::Firewall => "firewall",
            BonjourError::Incompatible => "incompatible",
            BonjourError::BadInterfaceIndex => "bad interface index",
            BonjourError::Refused => "refused",
            BonjourError::NoSuchRecord => "no such record",
            BonjourError::NoAuth => "no auth",
            BonjourError::NoSuchKey => "no such key",
            BonjourError::NATTraversal => "NAT traversal",
            BonjourError::DoubleNAT => "double NAT",
            BonjourError::BadTime => "bad time",
            BonjourError::Undefined => "undefined error",
        };
        write!(f, "{}", s)
    }
}

impl std::error::Error for BonjourError {}

impl From<DNSServiceErrorType> for BonjourError {
    fn from(err: DNSServiceErrorType) -> Self {
        match err {
            -65537 => BonjourError::Unknown,
            -65538 => BonjourError::NoSuchName,
            -65539 => BonjourError::NoMemory,
            -65540 => BonjourError::BadParam,
            -65541 => BonjourError::BadReference,
            -65542 => BonjourError::BadState,
            -65543 => BonjourError::BadFlags,
            -65544 => BonjourError::Unsupported,
            -65545 => BonjourError::NotInitialized,
            -65547 => BonjourError::AlreadyRegistered,
            -65548 => BonjourError::NameConflict,
            -65549 => BonjourError::Invalid,
            -65550 => BonjourError::Firewall,
            -65551 => BonjourError::Incompatible,
            -65552 => BonjourError::BadInterfaceIndex,
            -65553 => BonjourError::Refused,
            -65554 => BonjourError::NoSuchRecord,
            -65555 => BonjourError::NoAuth,
            -65556 => BonjourError::NoSuchKey,
            -65557 => BonjourError::NATTraversal,
            -65558 => BonjourError::DoubleNAT,
            -65559 => BonjourError::BadTime,
            _ => BonjourError::Undefined,
        }
    }
}