refactor(netxlite/errors): improve docs and format code (#481)
No real functional change. A few are needed and they will come next. With this diff I just wanted to do cosmetic changes and documentation changes, to ensure this package is okay. See https://github.com/ooni/probe/issues/1591
This commit is contained in:
		
							parent
							
								
									323266da83
								
							
						
					
					
						commit
						a56b284b0e
					
				@ -18,6 +18,26 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ClassifyGenericError is the generic classifier mapping an error
 | 
					// ClassifyGenericError is the generic classifier mapping an error
 | 
				
			||||||
// occurred during an operation to an OONI failure string.
 | 
					// occurred during an operation to an OONI failure string.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If the input error is already an ErrWrapper we don't perform
 | 
				
			||||||
 | 
					// the classification again and we return its Failure to the caller.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Classification rules
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// We put inside this classifier:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// - system call errors
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// - generic errors that can occur in multiple places
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// - all the errors that depend on strings
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The more specific classifiers will call this classifier if
 | 
				
			||||||
 | 
					// they fail to find a mapping for the input error.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If everything else fails, this classifier returns a string
 | 
				
			||||||
 | 
					// like "unknown_failure: XXX" where XXX has been scrubbed
 | 
				
			||||||
 | 
					// so to remove any network endpoints from its value.
 | 
				
			||||||
func ClassifyGenericError(err error) string {
 | 
					func ClassifyGenericError(err error) string {
 | 
				
			||||||
	// The list returned here matches the values used by MK unless
 | 
						// The list returned here matches the values used by MK unless
 | 
				
			||||||
	// explicitly noted otherwise with a comment.
 | 
						// explicitly noted otherwise with a comment.
 | 
				
			||||||
@ -27,6 +47,9 @@ func ClassifyGenericError(err error) string {
 | 
				
			|||||||
		return errwrapper.Error() // we've already wrapped it
 | 
							return errwrapper.Error() // we've already wrapped it
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Classify system errors first. We could use strings for many
 | 
				
			||||||
 | 
						// of them on Unix, but this would fail on Windows as described
 | 
				
			||||||
 | 
						// by https://github.com/ooni/probe/issues/1526.
 | 
				
			||||||
	if failure := classifySyscallError(err); failure != "" {
 | 
						if failure := classifySyscallError(err); failure != "" {
 | 
				
			||||||
		return failure
 | 
							return failure
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -34,6 +57,7 @@ func ClassifyGenericError(err error) string {
 | 
				
			|||||||
	if errors.Is(err, context.Canceled) {
 | 
						if errors.Is(err, context.Canceled) {
 | 
				
			||||||
		return FailureInterrupted
 | 
							return FailureInterrupted
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s := err.Error()
 | 
						s := err.Error()
 | 
				
			||||||
	if strings.HasSuffix(s, "operation was canceled") {
 | 
						if strings.HasSuffix(s, "operation was canceled") {
 | 
				
			||||||
		return FailureInterrupted
 | 
							return FailureInterrupted
 | 
				
			||||||
@ -50,7 +74,6 @@ func ClassifyGenericError(err error) string {
 | 
				
			|||||||
	if strings.HasSuffix(s, "i/o timeout") {
 | 
						if strings.HasSuffix(s, "i/o timeout") {
 | 
				
			||||||
		return FailureGenericTimeoutError
 | 
							return FailureGenericTimeoutError
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// TODO(kelmenhorst,bassosimone): this can probably be moved since it's TLS specific
 | 
					 | 
				
			||||||
	if strings.HasSuffix(s, "TLS handshake timeout") {
 | 
						if strings.HasSuffix(s, "TLS handshake timeout") {
 | 
				
			||||||
		return FailureGenericTimeoutError
 | 
							return FailureGenericTimeoutError
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -60,11 +83,13 @@ func ClassifyGenericError(err error) string {
 | 
				
			|||||||
		// that we return here is significantly more specific.
 | 
							// that we return here is significantly more specific.
 | 
				
			||||||
		return FailureDNSNXDOMAINError
 | 
							return FailureDNSNXDOMAINError
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	formatted := fmt.Sprintf("unknown_failure: %s", s)
 | 
						formatted := fmt.Sprintf("unknown_failure: %s", s)
 | 
				
			||||||
	return scrubber.Scrub(formatted) // scrub IP addresses in the error
 | 
						return scrubber.Scrub(formatted) // scrub IP addresses in the error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TLS alert protocol as defined in RFC8446
 | 
					// TLS alert protocol as defined in RFC8446. We need these definitions
 | 
				
			||||||
 | 
					// to figure out which error occurred during a QUIC handshake.
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// Sender was unable to negotiate an acceptable set of security parameters given the options available.
 | 
						// Sender was unable to negotiate an acceptable set of security parameters given the options available.
 | 
				
			||||||
	quicTLSAlertHandshakeFailure = 40
 | 
						quicTLSAlertHandshakeFailure = 40
 | 
				
			||||||
@ -94,6 +119,11 @@ const (
 | 
				
			|||||||
	quicTLSUnrecognizedName = 112
 | 
						quicTLSUnrecognizedName = 112
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// quicIsCertificateError tells us whether a specific TLS alert error
 | 
				
			||||||
 | 
					// we received is actually an error depending on the certificate.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The set of checks we implement here is a set of heuristics based
 | 
				
			||||||
 | 
					// on our understanding of the TLS spec and may need tweaks.
 | 
				
			||||||
func quicIsCertificateError(alert uint8) bool {
 | 
					func quicIsCertificateError(alert uint8) bool {
 | 
				
			||||||
	return (alert == quicTLSAlertBadCertificate ||
 | 
						return (alert == quicTLSAlertBadCertificate ||
 | 
				
			||||||
		alert == quicTLSAlertUnsupportedCertificate ||
 | 
							alert == quicTLSAlertUnsupportedCertificate ||
 | 
				
			||||||
@ -104,17 +134,25 @@ func quicIsCertificateError(alert uint8) bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ClassifyQUICHandshakeError maps an error occurred during the QUIC
 | 
					// ClassifyQUICHandshakeError maps an error occurred during the QUIC
 | 
				
			||||||
// handshake to an OONI failure string.
 | 
					// handshake to an OONI failure string.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If the input error is already an ErrWrapper we don't perform
 | 
				
			||||||
 | 
					// the classification again and we return its Failure to the caller.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If this classifier fails, it calls ClassifyGenericError and
 | 
				
			||||||
 | 
					// returns to the caller its return value.
 | 
				
			||||||
func ClassifyQUICHandshakeError(err error) string {
 | 
					func ClassifyQUICHandshakeError(err error) string {
 | 
				
			||||||
	var errwrapper *ErrWrapper
 | 
						var errwrapper *ErrWrapper
 | 
				
			||||||
	if errors.As(err, &errwrapper) {
 | 
						if errors.As(err, &errwrapper) {
 | 
				
			||||||
		return errwrapper.Error() // we've already wrapped it
 | 
							return errwrapper.Error() // we've already wrapped it
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var versionNegotiation *quic.VersionNegotiationError
 | 
						var (
 | 
				
			||||||
	var statelessReset *quic.StatelessResetError
 | 
							versionNegotiation *quic.VersionNegotiationError
 | 
				
			||||||
	var handshakeTimeout *quic.HandshakeTimeoutError
 | 
							statelessReset     *quic.StatelessResetError
 | 
				
			||||||
	var idleTimeout *quic.IdleTimeoutError
 | 
							handshakeTimeout   *quic.HandshakeTimeoutError
 | 
				
			||||||
	var transportError *quic.TransportError
 | 
							idleTimeout        *quic.IdleTimeoutError
 | 
				
			||||||
 | 
							transportError     *quic.TransportError
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if errors.As(err, &versionNegotiation) {
 | 
						if errors.As(err, &versionNegotiation) {
 | 
				
			||||||
		return FailureQUICIncompatibleVersion
 | 
							return FailureQUICIncompatibleVersion
 | 
				
			||||||
@ -137,8 +175,9 @@ func ClassifyQUICHandshakeError(err error) string {
 | 
				
			|||||||
		if quicIsCertificateError(errCode) {
 | 
							if quicIsCertificateError(errCode) {
 | 
				
			||||||
			return FailureSSLInvalidCertificate
 | 
								return FailureSSLInvalidCertificate
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// TLSAlertDecryptError and TLSAlertHandshakeFailure are summarized to a FailureSSLHandshake error because both
 | 
							// TLSAlertDecryptError and TLSAlertHandshakeFailure are summarized to a
 | 
				
			||||||
		// alerts are caused by a failed or corrupted parameter negotiation during the TLS handshake.
 | 
							// FailureSSLHandshake error because both alerts are caused by a failed or
 | 
				
			||||||
 | 
							// corrupted parameter negotiation during the TLS handshake.
 | 
				
			||||||
		if errCode == quicTLSAlertDecryptError || errCode == quicTLSAlertHandshakeFailure {
 | 
							if errCode == quicTLSAlertDecryptError || errCode == quicTLSAlertHandshakeFailure {
 | 
				
			||||||
			return FailureSSLFailedHandshake
 | 
								return FailureSSLFailedHandshake
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -152,13 +191,18 @@ func ClassifyQUICHandshakeError(err error) string {
 | 
				
			|||||||
	return ClassifyGenericError(err)
 | 
						return ClassifyGenericError(err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ErrDNSBogon indicates that we found a bogon address. This is the
 | 
					// ErrDNSBogon indicates that we found a bogon address. Code that
 | 
				
			||||||
// correct value with which to initialize MeasurementRoot.ErrDNSBogon
 | 
					// filters for DNS bogons MUST use this error.
 | 
				
			||||||
// to tell this library to return an error when a bogon is found.
 | 
					 | 
				
			||||||
var ErrDNSBogon = errors.New("dns: detected bogon address")
 | 
					var ErrDNSBogon = errors.New("dns: detected bogon address")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ClassifyResolverError maps an error occurred during a domain name
 | 
					// ClassifyResolverError maps an error occurred during a domain name
 | 
				
			||||||
// resolution to the corresponding OONI failure string.
 | 
					// resolution to the corresponding OONI failure string.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If the input error is already an ErrWrapper we don't perform
 | 
				
			||||||
 | 
					// the classification again and we return its Failure to the caller.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If this classifier fails, it calls ClassifyGenericError and
 | 
				
			||||||
 | 
					// returns to the caller its return value.
 | 
				
			||||||
func ClassifyResolverError(err error) string {
 | 
					func ClassifyResolverError(err error) string {
 | 
				
			||||||
	var errwrapper *ErrWrapper
 | 
						var errwrapper *ErrWrapper
 | 
				
			||||||
	if errors.As(err, &errwrapper) {
 | 
						if errors.As(err, &errwrapper) {
 | 
				
			||||||
@ -172,6 +216,12 @@ func ClassifyResolverError(err error) string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ClassifyTLSHandshakeError maps an error occurred during the TLS
 | 
					// ClassifyTLSHandshakeError maps an error occurred during the TLS
 | 
				
			||||||
// handshake to an OONI failure string.
 | 
					// handshake to an OONI failure string.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If the input error is already an ErrWrapper we don't perform
 | 
				
			||||||
 | 
					// the classification again and we return its Failure to the caller.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If this classifier fails, it calls ClassifyGenericError and
 | 
				
			||||||
 | 
					// returns to the caller its return value.
 | 
				
			||||||
func ClassifyTLSHandshakeError(err error) string {
 | 
					func ClassifyTLSHandshakeError(err error) string {
 | 
				
			||||||
	var errwrapper *ErrWrapper
 | 
						var errwrapper *ErrWrapper
 | 
				
			||||||
	if errors.As(err, &errwrapper) {
 | 
						if errors.As(err, &errwrapper) {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,52 +22,62 @@ func TestClassifyGenericError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal("did not classify existing ErrWrapper correctly")
 | 
								t.Fatal("did not classify existing ErrWrapper correctly")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for already wrapped error", func(t *testing.T) {
 | 
						t.Run("for already wrapped error", func(t *testing.T) {
 | 
				
			||||||
		err := io.EOF
 | 
							err := io.EOF
 | 
				
			||||||
		if ClassifyGenericError(err) != FailureEOFError {
 | 
							if ClassifyGenericError(err) != FailureEOFError {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for context.Canceled", func(t *testing.T) {
 | 
						t.Run("for context.Canceled", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(context.Canceled) != FailureInterrupted {
 | 
							if ClassifyGenericError(context.Canceled) != FailureInterrupted {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for operation was canceled error", func(t *testing.T) {
 | 
						t.Run("for operation was canceled error", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(errors.New("operation was canceled")) != FailureInterrupted {
 | 
							if ClassifyGenericError(errors.New("operation was canceled")) != FailureInterrupted {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for EOF", func(t *testing.T) {
 | 
						t.Run("for EOF", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(io.EOF) != FailureEOFError {
 | 
							if ClassifyGenericError(io.EOF) != FailureEOFError {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for canceled", func(t *testing.T) {
 | 
						t.Run("for canceled", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(syscall.ECANCELED) != FailureOperationCanceled {
 | 
							if ClassifyGenericError(syscall.ECANCELED) != FailureOperationCanceled {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for connection_refused", func(t *testing.T) {
 | 
						t.Run("for connection_refused", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(syscall.ECONNREFUSED) != FailureConnectionRefused {
 | 
							if ClassifyGenericError(syscall.ECONNREFUSED) != FailureConnectionRefused {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for connection_reset", func(t *testing.T) {
 | 
						t.Run("for connection_reset", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(syscall.ECONNRESET) != FailureConnectionReset {
 | 
							if ClassifyGenericError(syscall.ECONNRESET) != FailureConnectionReset {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for host_unreachable", func(t *testing.T) {
 | 
						t.Run("for host_unreachable", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(syscall.EHOSTUNREACH) != FailureHostUnreachable {
 | 
							if ClassifyGenericError(syscall.EHOSTUNREACH) != FailureHostUnreachable {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for system timeout", func(t *testing.T) {
 | 
						t.Run("for system timeout", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(syscall.ETIMEDOUT) != FailureTimedOut {
 | 
							if ClassifyGenericError(syscall.ETIMEDOUT) != FailureTimedOut {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for context deadline exceeded", func(t *testing.T) {
 | 
						t.Run("for context deadline exceeded", func(t *testing.T) {
 | 
				
			||||||
		ctx, cancel := context.WithTimeout(context.Background(), 1)
 | 
							ctx, cancel := context.WithTimeout(context.Background(), 1)
 | 
				
			||||||
		defer cancel()
 | 
							defer cancel()
 | 
				
			||||||
@ -76,11 +86,13 @@ func TestClassifyGenericError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for stun's transaction is timed out", func(t *testing.T) {
 | 
						t.Run("for stun's transaction is timed out", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(stun.ErrTransactionTimeOut) != FailureGenericTimeoutError {
 | 
							if ClassifyGenericError(stun.ErrTransactionTimeOut) != FailureGenericTimeoutError {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for i/o error", func(t *testing.T) {
 | 
						t.Run("for i/o error", func(t *testing.T) {
 | 
				
			||||||
		ctx, cancel := context.WithTimeout(context.Background(), 1)
 | 
							ctx, cancel := context.WithTimeout(context.Background(), 1)
 | 
				
			||||||
		defer cancel() // fail immediately
 | 
							defer cancel() // fail immediately
 | 
				
			||||||
@ -95,12 +107,14 @@ func TestClassifyGenericError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for TLS handshake timeout error", func(t *testing.T) {
 | 
						t.Run("for TLS handshake timeout error", func(t *testing.T) {
 | 
				
			||||||
		err := errors.New("net/http: TLS handshake timeout")
 | 
							err := errors.New("net/http: TLS handshake timeout")
 | 
				
			||||||
		if ClassifyGenericError(err) != FailureGenericTimeoutError {
 | 
							if ClassifyGenericError(err) != FailureGenericTimeoutError {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for no such host", func(t *testing.T) {
 | 
						t.Run("for no such host", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyGenericError(&net.DNSError{
 | 
							if ClassifyGenericError(&net.DNSError{
 | 
				
			||||||
			Err: "no such host",
 | 
								Err: "no such host",
 | 
				
			||||||
@ -108,6 +122,7 @@ func TestClassifyGenericError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for errors including IPv4 address", func(t *testing.T) {
 | 
						t.Run("for errors including IPv4 address", func(t *testing.T) {
 | 
				
			||||||
		input := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: use of closed network connection")
 | 
							input := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: use of closed network connection")
 | 
				
			||||||
		expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
 | 
							expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
 | 
				
			||||||
@ -116,6 +131,7 @@ func TestClassifyGenericError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal(cmp.Diff(expected, out))
 | 
								t.Fatal(cmp.Diff(expected, out))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for errors including IPv6 address", func(t *testing.T) {
 | 
						t.Run("for errors including IPv6 address", func(t *testing.T) {
 | 
				
			||||||
		input := errors.New("read tcp [::1]:56948->[::1]:443: use of closed network connection")
 | 
							input := errors.New("read tcp [::1]:56948->[::1]:443: use of closed network connection")
 | 
				
			||||||
		expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
 | 
							expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
 | 
				
			||||||
@ -124,6 +140,7 @@ func TestClassifyGenericError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal(cmp.Diff(expected, out))
 | 
								t.Fatal(cmp.Diff(expected, out))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for i/o error", func(t *testing.T) {
 | 
						t.Run("for i/o error", func(t *testing.T) {
 | 
				
			||||||
		ctx, cancel := context.WithTimeout(context.Background(), 1)
 | 
							ctx, cancel := context.WithTimeout(context.Background(), 1)
 | 
				
			||||||
		defer cancel() // fail immediately
 | 
							defer cancel() // fail immediately
 | 
				
			||||||
@ -152,55 +169,65 @@ func TestClassifyQUICHandshakeError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal("did not classify existing ErrWrapper correctly")
 | 
								t.Fatal("did not classify existing ErrWrapper correctly")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for connection_reset", func(t *testing.T) {
 | 
						t.Run("for connection_reset", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
 | 
							if ClassifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for incompatible quic version", func(t *testing.T) {
 | 
						t.Run("for incompatible quic version", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.VersionNegotiationError{}) != FailureQUICIncompatibleVersion {
 | 
							if ClassifyQUICHandshakeError(&quic.VersionNegotiationError{}) != FailureQUICIncompatibleVersion {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for quic connection refused", func(t *testing.T) {
 | 
						t.Run("for quic connection refused", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
 | 
							if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for quic handshake timeout", func(t *testing.T) {
 | 
						t.Run("for quic handshake timeout", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.HandshakeTimeoutError{}) != FailureGenericTimeoutError {
 | 
							if ClassifyQUICHandshakeError(&quic.HandshakeTimeoutError{}) != FailureGenericTimeoutError {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for QUIC idle connection timeout", func(t *testing.T) {
 | 
						t.Run("for QUIC idle connection timeout", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.IdleTimeoutError{}) != FailureGenericTimeoutError {
 | 
							if ClassifyQUICHandshakeError(&quic.IdleTimeoutError{}) != FailureGenericTimeoutError {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for QUIC CRYPTO Handshake", func(t *testing.T) {
 | 
						t.Run("for QUIC CRYPTO Handshake", func(t *testing.T) {
 | 
				
			||||||
		var err quic.TransportErrorCode = quicTLSAlertHandshakeFailure
 | 
							var err quic.TransportErrorCode = quicTLSAlertHandshakeFailure
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
 | 
							if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for QUIC CRYPTO Invalid Certificate", func(t *testing.T) {
 | 
						t.Run("for QUIC CRYPTO Invalid Certificate", func(t *testing.T) {
 | 
				
			||||||
		var err quic.TransportErrorCode = quicTLSAlertBadCertificate
 | 
							var err quic.TransportErrorCode = quicTLSAlertBadCertificate
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
 | 
							if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for QUIC CRYPTO Unknown CA", func(t *testing.T) {
 | 
						t.Run("for QUIC CRYPTO Unknown CA", func(t *testing.T) {
 | 
				
			||||||
		var err quic.TransportErrorCode = quicTLSAlertUnknownCA
 | 
							var err quic.TransportErrorCode = quicTLSAlertUnknownCA
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLUnknownAuthority {
 | 
							if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLUnknownAuthority {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for QUIC CRYPTO Bad Hostname", func(t *testing.T) {
 | 
						t.Run("for QUIC CRYPTO Bad Hostname", func(t *testing.T) {
 | 
				
			||||||
		var err quic.TransportErrorCode = quicTLSUnrecognizedName
 | 
							var err quic.TransportErrorCode = quicTLSUnrecognizedName
 | 
				
			||||||
		if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidHostname {
 | 
							if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidHostname {
 | 
				
			||||||
			t.Fatal("unexpected results")
 | 
								t.Fatal("unexpected results")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for another kind of error", func(t *testing.T) {
 | 
						t.Run("for another kind of error", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyQUICHandshakeError(io.EOF) != FailureEOFError {
 | 
							if ClassifyQUICHandshakeError(io.EOF) != FailureEOFError {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
@ -215,11 +242,13 @@ func TestClassifyResolverError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal("did not classify existing ErrWrapper correctly")
 | 
								t.Fatal("did not classify existing ErrWrapper correctly")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for ErrDNSBogon", func(t *testing.T) {
 | 
						t.Run("for ErrDNSBogon", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyResolverError(ErrDNSBogon) != FailureDNSBogonError {
 | 
							if ClassifyResolverError(ErrDNSBogon) != FailureDNSBogonError {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for another kind of error", func(t *testing.T) {
 | 
						t.Run("for another kind of error", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyResolverError(io.EOF) != FailureEOFError {
 | 
							if ClassifyResolverError(io.EOF) != FailureEOFError {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
@ -234,24 +263,28 @@ func TestClassifyTLSHandshakeError(t *testing.T) {
 | 
				
			|||||||
			t.Fatal("did not classify existing ErrWrapper correctly")
 | 
								t.Fatal("did not classify existing ErrWrapper correctly")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for x509.HostnameError", func(t *testing.T) {
 | 
						t.Run("for x509.HostnameError", func(t *testing.T) {
 | 
				
			||||||
		var err x509.HostnameError
 | 
							var err x509.HostnameError
 | 
				
			||||||
		if ClassifyTLSHandshakeError(err) != FailureSSLInvalidHostname {
 | 
							if ClassifyTLSHandshakeError(err) != FailureSSLInvalidHostname {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for x509.UnknownAuthorityError", func(t *testing.T) {
 | 
						t.Run("for x509.UnknownAuthorityError", func(t *testing.T) {
 | 
				
			||||||
		var err x509.UnknownAuthorityError
 | 
							var err x509.UnknownAuthorityError
 | 
				
			||||||
		if ClassifyTLSHandshakeError(err) != FailureSSLUnknownAuthority {
 | 
							if ClassifyTLSHandshakeError(err) != FailureSSLUnknownAuthority {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for x509.CertificateInvalidError", func(t *testing.T) {
 | 
						t.Run("for x509.CertificateInvalidError", func(t *testing.T) {
 | 
				
			||||||
		var err x509.CertificateInvalidError
 | 
							var err x509.CertificateInvalidError
 | 
				
			||||||
		if ClassifyTLSHandshakeError(err) != FailureSSLInvalidCertificate {
 | 
							if ClassifyTLSHandshakeError(err) != FailureSSLInvalidCertificate {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("for another kind of error", func(t *testing.T) {
 | 
						t.Run("for another kind of error", func(t *testing.T) {
 | 
				
			||||||
		if ClassifyTLSHandshakeError(io.EOF) != FailureEOFError {
 | 
							if ClassifyTLSHandshakeError(io.EOF) != FailureEOFError {
 | 
				
			||||||
			t.Fatal("unexpected result")
 | 
								t.Fatal("unexpected result")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,20 @@
 | 
				
			|||||||
// Package errorsx contains code to classify errors.
 | 
					// Package errorsx contains code to classify errors.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// We define the ErrWrapper type, that should wrap any error
 | 
				
			||||||
 | 
					// and map it to the corresponding OONI failure.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// See https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md
 | 
				
			||||||
 | 
					// for a list of OONI failure strings.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// We define ClassifyXXX functions that map an `error` type to
 | 
				
			||||||
 | 
					// the corresponding OONI failure.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// When we cannot map an error to an OONI failure we return
 | 
				
			||||||
 | 
					// an "unknown_failure: XXX" string where the XXX part has
 | 
				
			||||||
 | 
					// been scrubbed so to remove any network endpoints.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The general approach we have been following for this
 | 
				
			||||||
 | 
					// package has been to return the same strings that we used
 | 
				
			||||||
 | 
					// with the previous measurement engine, Measurement Kit
 | 
				
			||||||
 | 
					// available at https://github.com/measurement-kit/measurement-kit.
 | 
				
			||||||
package errorsx
 | 
					package errorsx
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
// Code generated by go generate; DO NOT EDIT.
 | 
					// Code generated by go generate; DO NOT EDIT.
 | 
				
			||||||
// Generated: 2021-09-07 16:43:08.462721 +0200 CEST m=+0.105415376
 | 
					// Generated: 2021-09-07 20:26:06.502417 +0200 CEST m=+0.133209876
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package errorsx
 | 
					package errorsx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
// Code generated by go generate; DO NOT EDIT.
 | 
					// Code generated by go generate; DO NOT EDIT.
 | 
				
			||||||
// Generated: 2021-09-07 16:43:08.510432 +0200 CEST m=+0.153127376
 | 
					// Generated: 2021-09-07 20:26:06.547786 +0200 CEST m=+0.178579584
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package errorsx
 | 
					package errorsx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,95 +9,184 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestToSyscallErr(t *testing.T) {
 | 
					func TestClassifySyscallError(t *testing.T) {
 | 
				
			||||||
	if v := classifySyscallError(io.EOF); v != "" {
 | 
						t.Run("for a non-syscall error", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected empty string, got '%s'", v)
 | 
							if v := classifySyscallError(io.EOF); v != "" {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected empty string, got '%s'", v)
 | 
				
			||||||
	if v := classifySyscallError(ECANCELED); v != FailureOperationCanceled {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureOperationCanceled, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(ECONNREFUSED); v != FailureConnectionRefused {
 | 
						t.Run("for ECANCELED", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v)
 | 
							if v := classifySyscallError(ECANCELED); v != FailureOperationCanceled {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureOperationCanceled, v)
 | 
				
			||||||
	if v := classifySyscallError(ECONNRESET); v != FailureConnectionReset {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EHOSTUNREACH); v != FailureHostUnreachable {
 | 
						t.Run("for ECONNREFUSED", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v)
 | 
							if v := classifySyscallError(ECONNREFUSED); v != FailureConnectionRefused {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v)
 | 
				
			||||||
	if v := classifySyscallError(ETIMEDOUT); v != FailureTimedOut {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EAFNOSUPPORT); v != FailureAddressFamilyNotSupported {
 | 
						t.Run("for ECONNRESET", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v)
 | 
							if v := classifySyscallError(ECONNRESET); v != FailureConnectionReset {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v)
 | 
				
			||||||
	if v := classifySyscallError(EADDRINUSE); v != FailureAddressInUse {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EADDRNOTAVAIL); v != FailureAddressNotAvailable {
 | 
						t.Run("for EHOSTUNREACH", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v)
 | 
							if v := classifySyscallError(EHOSTUNREACH); v != FailureHostUnreachable {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v)
 | 
				
			||||||
	if v := classifySyscallError(EISCONN); v != FailureAlreadyConnected {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EFAULT); v != FailureBadAddress {
 | 
						t.Run("for ETIMEDOUT", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v)
 | 
							if v := classifySyscallError(ETIMEDOUT); v != FailureTimedOut {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v)
 | 
				
			||||||
	if v := classifySyscallError(EBADF); v != FailureBadFileDescriptor {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(ECONNABORTED); v != FailureConnectionAborted {
 | 
						t.Run("for EAFNOSUPPORT", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v)
 | 
							if v := classifySyscallError(EAFNOSUPPORT); v != FailureAddressFamilyNotSupported {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v)
 | 
				
			||||||
	if v := classifySyscallError(EALREADY); v != FailureConnectionAlreadyInProgress {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EDESTADDRREQ); v != FailureDestinationAddressRequired {
 | 
						t.Run("for EADDRINUSE", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v)
 | 
							if v := classifySyscallError(EADDRINUSE); v != FailureAddressInUse {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v)
 | 
				
			||||||
	if v := classifySyscallError(EINTR); v != FailureInterrupted {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EINVAL); v != FailureInvalidArgument {
 | 
						t.Run("for EADDRNOTAVAIL", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v)
 | 
							if v := classifySyscallError(EADDRNOTAVAIL); v != FailureAddressNotAvailable {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v)
 | 
				
			||||||
	if v := classifySyscallError(EMSGSIZE); v != FailureMessageSize {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(ENETDOWN); v != FailureNetworkDown {
 | 
						t.Run("for EISCONN", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v)
 | 
							if v := classifySyscallError(EISCONN); v != FailureAlreadyConnected {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v)
 | 
				
			||||||
	if v := classifySyscallError(ENETRESET); v != FailureNetworkReset {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(ENETUNREACH); v != FailureNetworkUnreachable {
 | 
						t.Run("for EFAULT", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v)
 | 
							if v := classifySyscallError(EFAULT); v != FailureBadAddress {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v)
 | 
				
			||||||
	if v := classifySyscallError(ENOBUFS); v != FailureNoBufferSpace {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(ENOPROTOOPT); v != FailureNoProtocolOption {
 | 
						t.Run("for EBADF", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v)
 | 
							if v := classifySyscallError(EBADF); v != FailureBadFileDescriptor {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v)
 | 
				
			||||||
	if v := classifySyscallError(ENOTSOCK); v != FailureNotASocket {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(ENOTCONN); v != FailureNotConnected {
 | 
						t.Run("for ECONNABORTED", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v)
 | 
							if v := classifySyscallError(ECONNABORTED); v != FailureConnectionAborted {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v)
 | 
				
			||||||
	if v := classifySyscallError(EWOULDBLOCK); v != FailureOperationWouldBlock {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EACCES); v != FailurePermissionDenied {
 | 
						t.Run("for EALREADY", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v)
 | 
							if v := classifySyscallError(EALREADY); v != FailureConnectionAlreadyInProgress {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v)
 | 
				
			||||||
	if v := classifySyscallError(EPROTONOSUPPORT); v != FailureProtocolNotSupported {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	if v := classifySyscallError(EPROTOTYPE); v != FailureWrongProtocolType {
 | 
						t.Run("for EDESTADDRREQ", func(t *testing.T) {
 | 
				
			||||||
		t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v)
 | 
							if v := classifySyscallError(EDESTADDRREQ); v != FailureDestinationAddressRequired {
 | 
				
			||||||
	}
 | 
								t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v)
 | 
				
			||||||
	if v := classifySyscallError(syscall.Errno(0)); v != "" {
 | 
							}
 | 
				
			||||||
		t.Fatalf("expected empty string, got '%s'", v)
 | 
						})
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
 | 
						t.Run("for EINTR", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(EINTR); v != FailureInterrupted {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for EINVAL", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(EINVAL); v != FailureInvalidArgument {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for EMSGSIZE", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(EMSGSIZE); v != FailureMessageSize {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for ENETDOWN", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(ENETDOWN); v != FailureNetworkDown {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for ENETRESET", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(ENETRESET); v != FailureNetworkReset {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for ENETUNREACH", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(ENETUNREACH); v != FailureNetworkUnreachable {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for ENOBUFS", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(ENOBUFS); v != FailureNoBufferSpace {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for ENOPROTOOPT", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(ENOPROTOOPT); v != FailureNoProtocolOption {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for ENOTSOCK", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(ENOTSOCK); v != FailureNotASocket {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for ENOTCONN", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(ENOTCONN); v != FailureNotConnected {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for EWOULDBLOCK", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(EWOULDBLOCK); v != FailureOperationWouldBlock {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for EACCES", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(EACCES); v != FailurePermissionDenied {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for EPROTONOSUPPORT", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(EPROTONOSUPPORT); v != FailureProtocolNotSupported {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for EPROTOTYPE", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(EPROTOTYPE); v != FailureWrongProtocolType {
 | 
				
			||||||
 | 
								t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("for the zero errno value", func(t *testing.T) {
 | 
				
			||||||
 | 
							if v := classifySyscallError(syscall.Errno(0)); v != "" {
 | 
				
			||||||
 | 
								t.Fatalf("expected empty string, got '%s'", v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
// Code generated by go generate; DO NOT EDIT.
 | 
					// Code generated by go generate; DO NOT EDIT.
 | 
				
			||||||
// Generated: 2021-09-07 16:43:08.35751 +0200 CEST m=+0.000202959
 | 
					// Generated: 2021-09-07 20:26:06.370246 +0200 CEST m=+0.001036417
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package errorsx
 | 
					package errorsx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
// Code generated by go generate; DO NOT EDIT.
 | 
					// Code generated by go generate; DO NOT EDIT.
 | 
				
			||||||
// Generated: 2021-09-07 16:43:08.436584 +0200 CEST m=+0.079277834
 | 
					// Generated: 2021-09-07 20:26:06.478577 +0200 CEST m=+0.109369751
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package errorsx
 | 
					package errorsx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,17 +2,28 @@ package errorsx
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ErrWrapper is our error wrapper for Go errors. The key objective of
 | 
					// ErrWrapper is our error wrapper for Go errors. The key objective of
 | 
				
			||||||
// this structure is to properly set Failure, which is also returned by
 | 
					// this structure is to properly set Failure, which is also returned by
 | 
				
			||||||
// the Error() method, so be one of the OONI defined strings.
 | 
					// the Error() method, to be one of the OONI failure strings.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// OONI failure strings are defined in the github.com/ooni/spec repo
 | 
				
			||||||
 | 
					// at https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md.
 | 
				
			||||||
type ErrWrapper struct {
 | 
					type ErrWrapper struct {
 | 
				
			||||||
	// Failure is the OONI failure string. The failure strings are
 | 
						// Failure is the OONI failure string. The failure strings are
 | 
				
			||||||
	// loosely backward compatible with Measurement Kit.
 | 
						// loosely backward compatible with Measurement Kit.
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
	// This is either one of the FailureXXX strings or any other
 | 
						// This is either one of the FailureXXX strings or any other
 | 
				
			||||||
	// string like `unknown_failure ...`. The latter represents an
 | 
						// string like `unknown_failure: ...`. The latter represents an
 | 
				
			||||||
	// error that we have not yet mapped to a failure.
 | 
						// error that we have not yet mapped to a failure.
 | 
				
			||||||
	Failure string
 | 
						Failure string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Operation is the operation that failed. If possible, it
 | 
						// Operation is the operation that failed.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// New code will always nest ErrWrapper and you need to
 | 
				
			||||||
 | 
						// walk the chain to find what happened.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// The following comment describes the DEPRECATED
 | 
				
			||||||
 | 
						// legacy behavior implements by internal/engine/legacy/errorsx:
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// If possible, the Operation string
 | 
				
			||||||
	// SHOULD be a _major_ operation. Major operations are:
 | 
						// SHOULD be a _major_ operation. Major operations are:
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
	// - ResolveOperation: resolving a domain name failed
 | 
						// - ResolveOperation: resolving a domain name failed
 | 
				
			||||||
 | 
				
			|||||||
@ -6,19 +6,21 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestErrWrapperError(t *testing.T) {
 | 
					func TestErrWrapper(t *testing.T) {
 | 
				
			||||||
	err := &ErrWrapper{Failure: FailureDNSNXDOMAINError}
 | 
						t.Run("Error", func(t *testing.T) {
 | 
				
			||||||
	if err.Error() != FailureDNSNXDOMAINError {
 | 
							err := &ErrWrapper{Failure: FailureDNSNXDOMAINError}
 | 
				
			||||||
		t.Fatal("invalid return value")
 | 
							if err.Error() != FailureDNSNXDOMAINError {
 | 
				
			||||||
	}
 | 
								t.Fatal("invalid return value")
 | 
				
			||||||
}
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestErrWrapperUnwrap(t *testing.T) {
 | 
						t.Run("Unwrap", func(t *testing.T) {
 | 
				
			||||||
	err := &ErrWrapper{
 | 
							err := &ErrWrapper{
 | 
				
			||||||
		Failure:    FailureEOFError,
 | 
								Failure:    FailureEOFError,
 | 
				
			||||||
		WrappedErr: io.EOF,
 | 
								WrappedErr: io.EOF,
 | 
				
			||||||
	}
 | 
							}
 | 
				
			||||||
	if !errors.Is(err, io.EOF) {
 | 
							if !errors.Is(err, io.EOF) {
 | 
				
			||||||
		t.Fatal("cannot unwrap error")
 | 
								t.Fatal("cannot unwrap error")
 | 
				
			||||||
	}
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -234,25 +234,32 @@ func writeGenericTestFile() {
 | 
				
			|||||||
	fileWrite(filep, "\t\"testing\"\n")
 | 
						fileWrite(filep, "\t\"testing\"\n")
 | 
				
			||||||
	fileWrite(filep, ")\n\n")
 | 
						fileWrite(filep, ")\n\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fileWrite(filep, "func TestToSyscallErr(t *testing.T) {\n")
 | 
						fileWrite(filep, "func TestClassifySyscallError(t *testing.T) {\n")
 | 
				
			||||||
	fileWrite(filep, "\tif v := classifySyscallError(io.EOF); v != \"\" {\n")
 | 
						fileWrite(filep, "\tt.Run(\"for a non-syscall error\", func (t *testing.T) {\n")
 | 
				
			||||||
	fileWrite(filep, "\t\tt.Fatalf(\"expected empty string, got '%s'\", v)\n")
 | 
						fileWrite(filep, "\t\tif v := classifySyscallError(io.EOF); v != \"\" {\n")
 | 
				
			||||||
	fileWrite(filep, "\t}\n")
 | 
						fileWrite(filep, "\t\t\tt.Fatalf(\"expected empty string, got '%s'\", v)\n")
 | 
				
			||||||
 | 
						fileWrite(filep, "\t\t}\n")
 | 
				
			||||||
 | 
						fileWrite(filep, "\t})\n\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, spec := range Specs {
 | 
						for _, spec := range Specs {
 | 
				
			||||||
		if !spec.IsSystemError() {
 | 
							if !spec.IsSystemError() {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		filePrintf(filep, "\tif v := classifySyscallError(%s); v != %s {\n",
 | 
							filePrintf(filep, "\tt.Run(\"for %s\", func (t *testing.T) {\n",
 | 
				
			||||||
 | 
								spec.AsErrnoName())
 | 
				
			||||||
 | 
							filePrintf(filep, "\t\tif v := classifySyscallError(%s); v != %s {\n",
 | 
				
			||||||
			spec.AsErrnoName(), spec.AsFailureVar())
 | 
								spec.AsErrnoName(), spec.AsFailureVar())
 | 
				
			||||||
		filePrintf(filep, "\t\tt.Fatalf(\"expected '%%s', got '%%s'\", %s, v)\n",
 | 
							filePrintf(filep, "\t\t\tt.Fatalf(\"expected '%%s', got '%%s'\", %s, v)\n",
 | 
				
			||||||
			spec.AsFailureVar())
 | 
								spec.AsFailureVar())
 | 
				
			||||||
		fileWrite(filep, "\t}\n")
 | 
							fileWrite(filep, "\t\t}\n")
 | 
				
			||||||
 | 
							fileWrite(filep, "\t})\n\n")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fileWrite(filep, "\tif v := classifySyscallError(syscall.Errno(0)); v != \"\" {\n")
 | 
						fileWrite(filep, "\tt.Run(\"for the zero errno value\", func (t *testing.T) {\n")
 | 
				
			||||||
	fileWrite(filep, "\t\tt.Fatalf(\"expected empty string, got '%s'\", v)\n")
 | 
						fileWrite(filep, "\t\tif v := classifySyscallError(syscall.Errno(0)); v != \"\" {\n")
 | 
				
			||||||
	fileWrite(filep, "\t}\n")
 | 
						fileWrite(filep, "\t\t\tt.Fatalf(\"expected empty string, got '%s'\", v)\n")
 | 
				
			||||||
 | 
						fileWrite(filep, "\t\t}\n")
 | 
				
			||||||
 | 
						fileWrite(filep, "\t})\n")
 | 
				
			||||||
	fileWrite(filep, "}\n")
 | 
						fileWrite(filep, "}\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fileClose(filep)
 | 
						fileClose(filep)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
package errorsx
 | 
					package errorsx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Operations that we measure.
 | 
					// Operations that we measure. They are the possibly values of
 | 
				
			||||||
 | 
					// the ErrWrapper.Operation field.
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// ResolveOperation is the operation where we resolve a domain name.
 | 
						// ResolveOperation is the operation where we resolve a domain name.
 | 
				
			||||||
	ResolveOperation = "resolve"
 | 
						ResolveOperation = "resolve"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user