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
use std::error::Error;
use std::net::SocketAddr;

/// An error that can be returned from the different browser operations
pub enum BrowserError<
    #[cfg(any(feature = "tokio", feature = "async-std"))]
    SFError: Error = <super::socket::DefaultSocketFactory as super::socket::UdpSocketFactory>::Error,
    #[cfg(any(feature = "tokio", feature = "async-std"))]
    SError: Error = <<super::socket::DefaultSocketFactory as super::socket::UdpSocketFactory>::Socket as super::socket::UdpSocket>::Error,
    #[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]
    SFError: Error,
    #[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]
    SError: Error
> {
    /// The underlying `tokio::net::UdpSocket` failed to bind.
    BindFailed(SFError),

    /// Enabling the broadcast option on the `tokio::net::UdpSocket` failed.
    SetBroadcastFailed(SError),

    /// Sending the request datagram failed.
    SendFailed(SocketAddr, SError),

    /// Locking the `tokio::net::UdpSocket` to a specific endpoint via `tokio::net::UdpSocket::connect` failed.
    ConnectFailed(SocketAddr, SError),

    /// Receiving a datagram failed.
    ReceiveFailed(SError),

    /// The given instance name is too long.
    InstanceNameTooLong,

    /// The server send back an invalid response.
    ProtocolError(BrowserProtocolError),
}

// Can't automatically derive Debug because it uses conditional type parameters
impl<SFError: std::error::Error, SError: Error> std::fmt::Debug 
    for BrowserError<SFError, SError> 
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use BrowserError::*;

        match self {
            BindFailed(e) => write!(f, "BindFailed({:?})", e),
            SetBroadcastFailed(e) => write!(f, "SetBroadcastFailed({:?})", e),
            SendFailed(addr, e) => write!(f, "SendFailed({:?}, {:?})", addr, e),
            ConnectFailed(addr, e) => write!(f, "ConnectFailed({:?}, {:?})", addr, e),
            ReceiveFailed(e) => write!(f, "ReceiveFailed({:?})", e),
            InstanceNameTooLong => write!(f, "InstanceNameTooLong"),
            ProtocolError(e) => write!(f, "ProtocolError({:?})", e),
        }
    }
}

impl<SFError: std::error::Error, SError: Error> std::fmt::Display
    for BrowserError<SFError, SError>
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use BrowserError::*;

        match self {
            BindFailed(err) => write!(f, "bind failed: {}", err),
            SetBroadcastFailed(err) => write!(f, "enabling broadcast option failed: {}", err),
            SendFailed(addr, err) => write!(f, "sending of datagram to '{}' failed: {}", addr, err),
            ConnectFailed(addr, err) => write!(f, "connect to '{}' failed: {}", addr, err),
            ReceiveFailed(err) => write!(f, "receiving of datagram failed: {}", err),
            InstanceNameTooLong => write!(
                f,
                "specified instance name is longer than {} bytes",
                super::MAX_INSTANCE_NAME_LEN
            ),
            ProtocolError(e) => write!(f, "protocol error: {}", e),
        }
    }
}

impl<SFError: Error, SError: Error> Error for BrowserError<SFError, SError> {
    fn cause(&self) -> Option<&dyn Error> {
        use BrowserError::*;

        match self {
            BindFailed(err) => Some(err),
            SetBroadcastFailed(err) => Some(err),
            SendFailed(_, err) => Some(err),
            ConnectFailed(_, err) => Some(err),
            ReceiveFailed(err) => Some(err),
            InstanceNameTooLong => None,
            ProtocolError(err) => Some(err),
        }
    }
}

/// Received an unexpected response from the server
#[derive(Debug)]
pub enum BrowserProtocolError {
    /// An unexpected token was received from the server
    UnexpectedToken {
        /// The token that was expected at this location
        expected: BrowserProtocolToken,

        /// The token that was found
        found: BrowserProtocolToken,
    },

    /// The length of the datagram does not match the length
    /// specified in the packet header.
    LengthMismatch {
        /// The size, in bytes, of the datagram
        datagram: usize,

        /// The size, in bytes, specified in the packet header
        header: usize,
    },

    /// Unexpected MBCS string encoding found in the received message
    InvalidUtf8(std::str::Utf8Error),

    /// There was extraneous data after the parsed message
    ExtraneousData(Vec<u8>),
}

impl std::fmt::Display for BrowserProtocolError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use BrowserProtocolError::*;

        match self {
            UnexpectedToken { expected, found } => {
                write!(f, "expected {}, but found {}", expected, found)
            }
            LengthMismatch { datagram, header } => write!(
                f,
                "mismatch between datagram size {} bytes and size specified in header {} bytes",
                datagram, header
            ),
            InvalidUtf8(err) => err.fmt(f),
            ExtraneousData(data) => write!(f, "{} unexpected trailing bytes", data.len()),
        }
    }
}

impl Error for BrowserProtocolError {}

/// The value that was expected.
#[derive(Debug)]
pub enum BrowserProtocolToken {
    /// End of the datagram
    EndOfMessage,

    /// A literal string
    Literal(String),

    /// The message identifier specified in the header
    MessageIdentifier(u8),

    /// The message length specified in the header
    MessageLength,

    DacVersion(u8),
    DacPort,
    Identifier(BrowserProtocolField),
    ValueOf(BrowserProtocolField),
    TcpPort,
    ViaParameters,
    EndpointIdentifierOrSemicolon,
}

impl std::fmt::Display for BrowserProtocolToken {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use BrowserProtocolToken::*;

        match self {
            EndOfMessage => write!(f, "end of message"),
            Literal(s) => write!(f, "'{}'", s),
            MessageIdentifier(v) => write!(f, "message identifier {:#X}", v),
            MessageLength => write!(f, "message length"),
            DacVersion(v) => write!(f, "dac version {}", v),
            DacPort => write!(f, "dac port"),
            Identifier(field) => write!(f, "identifier for field {:?}", field),
            ValueOf(field) => write!(f, "value for field {:?}", field),
            TcpPort => write!(f, "tcp port"),
            ViaParameters => write!(f, "via parameters"),
            EndpointIdentifierOrSemicolon => write!(f, "endpoint identifier or semicolon"),
        }
    }
}

/// Different fields found in a browser response
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum BrowserProtocolField {
    ServerName,
    InstanceName,
    IsClustered,
    Version,

    NamedPipeName,
    TcpPort,
    ViaMachineName,
    RpcComputerName,
    SpxServiceName,
    AppleTalkObjectName,
    BvItemName,
    BvGroupName,
    BvOrgName,
}