diff --git a/clientconn.go b/clientconn.go index 93ebc772..4874ff4b 100644 --- a/clientconn.go +++ b/clientconn.go @@ -35,6 +35,142 @@ const ( clientConnUDPKeepalivePeriod = 30 * time.Second ) +// ErrClientWrongState is returned in case of a wrong client state. +type ErrClientWrongState struct { + allowedList []clientConnState + state clientConnState +} + +// Error implements the error interface. +func (e ErrClientWrongState) Error() string { + return fmt.Sprintf("must be in state %v, while is in state %v", + e.allowedList, e.state) +} + +// ErrClientSessionHeaderInvalid is returned in case of an invalid session header. +type ErrClientSessionHeaderInvalid struct { + err error +} + +// Error implements the error interface. +func (e ErrClientSessionHeaderInvalid) Error() string { + return fmt.Sprintf("invalid session header: %v", e.err) +} + +// ErrClientWrongStatusCode is returned in case of a wrong status code. +type ErrClientWrongStatusCode struct { + code base.StatusCode + message string +} + +// Error implements the error interface. +func (e ErrClientWrongStatusCode) Error() string { + return fmt.Sprintf("wrong status code: %d (%s)", e.code, e.message) +} + +// ErrClientContentTypeMissing is returned in case the Content-Type header is missing. +type ErrClientContentTypeMissing struct{} + +// Error implements the error interface. +func (e ErrClientContentTypeMissing) Error() string { + return "Content-Type header is missing" +} + +// ErrClientContentTypeUnsupported is returned in case the Content-Type header is unsupported. +type ErrClientContentTypeUnsupported struct { + ct base.HeaderValue +} + +// Error implements the error interface. +func (e ErrClientContentTypeUnsupported) Error() string { + return fmt.Sprintf("unsupported Content-Type header '%v'", e.ct) +} + +// ErrClientCannotReadPublishAtSameTime is returned when the client is trying to read and publish at the same time. +type ErrClientCannotReadPublishAtSameTime struct{} + +// Error implements the error interface. +func (e ErrClientCannotReadPublishAtSameTime) Error() string { + return "cannot read and publish at the same time" +} + +// ErrClientCannotSetupTracksDifferentURLs is returned when the client is trying to setup tracks with different base URLs. +type ErrClientCannotSetupTracksDifferentURLs struct{} + +// Error implements the error interface. +func (e ErrClientCannotSetupTracksDifferentURLs) Error() string { + return "cannot setup tracks with different base URLs" +} + +// ErrClientUDPPortsZero is returned when one of the UDP ports is zero. +type ErrClientUDPPortsZero struct{} + +// Error implements the error interface. +func (e ErrClientUDPPortsZero) Error() string { + return "rtpPort and rtcpPort must be both zero or non-zero" +} + +// ErrClientUDPPortsNotConsecutive is returned when the two UDP ports are not consecutive. +type ErrClientUDPPortsNotConsecutive struct{} + +// Error implements the error interface. +func (e ErrClientUDPPortsNotConsecutive) Error() string { + return "rtcpPort must be rtpPort + 1" +} + +// ErrClientServerPortsZero is returned when one of the server ports is zero. +type ErrClientServerPortsZero struct{} + +// Error implements the error interface. +func (e ErrClientServerPortsZero) Error() string { + return "server ports must be both zero or both not zero" +} + +// ErrClientServerPortsNotProvided is returned in case the server ports have not been provided. +type ErrClientServerPortsNotProvided struct{} + +// Error implements the error interface. +func (e ErrClientServerPortsNotProvided) Error() string { + return "server ports have not been provided. Use AnyPortEnable to communicate with this server" +} + +// ErrClientTransportHeaderNoInterleavedIDs is returned in case the transport header doesn't contain interleaved IDs. +type ErrClientTransportHeaderNoInterleavedIDs struct{} + +// Error implements the error interface. +func (e ErrClientTransportHeaderNoInterleavedIDs) Error() string { + return "transport header does not contain interleaved IDs" +} + +// ErrClientTransportHeaderWrongInterleavedIDs is returned in case the transport header contains wrong interleaved IDs. +type ErrClientTransportHeaderWrongInterleavedIDs struct { + expected [2]int + value [2]int +} + +// Error implements the error interface. +func (e ErrClientTransportHeaderWrongInterleavedIDs) Error() string { + return fmt.Sprintf("wrong interleaved IDs, expected %v, got %v", e.expected, e.value) +} + +// ErrClientNoUDPPacketsRecently is returned when no UDP packets have been received recently. +type ErrClientNoUDPPacketsRecently struct{} + +// Error implements the error interface. +func (e ErrClientNoUDPPacketsRecently) Error() string { + return "no UDP packets received recently (maybe there's a firewall/NAT in between)" +} + +// ErrClientRTPInfoInvalid is returned in case of an invalid RTP-Info. +type ErrClientRTPInfoInvalid struct { + err error +} + +// Error implements the error interface. +func (e ErrClientRTPInfoInvalid) Error() string { + return fmt.Sprintf("unable to parse RTP-Info: %v", e.err) +} + type clientConnState int const ( @@ -199,8 +335,7 @@ func (c *ClientConn) checkState(allowed map[clientConnState]struct{}) error { allowedList[i] = a i++ } - return fmt.Errorf("must be in state %v, while is in state %v", - allowedList, c.state) + return ErrClientWrongState{allowedList, c.state} } // NetConn returns the underlying net.Conn. @@ -271,7 +406,7 @@ func (c *ClientConn) Do(req *base.Request) (*base.Response, error) { var sx headers.Session err := sx.Read(v) if err != nil { - return nil, fmt.Errorf("unable to parse session header: %s", err) + return nil, ErrClientSessionHeaderInvalid{err} } c.session = sx.Session } @@ -319,7 +454,7 @@ func (c *ClientConn) Options(u *base.URL) (*base.Response, error) { if res.StatusCode == base.StatusNotFound { return res, nil } - return res, fmt.Errorf("bad status code: %d (%s)", res.StatusCode, res.StatusMessage) + return res, ErrClientWrongStatusCode{res.StatusCode, res.StatusMessage} } c.getParameterSupported = func() bool { @@ -389,16 +524,16 @@ func (c *ClientConn) Describe(u *base.URL) (Tracks, *base.Response, error) { return c.Describe(u) } - return nil, res, fmt.Errorf("bad status code: %d (%s)", res.StatusCode, res.StatusMessage) + return nil, res, ErrClientWrongStatusCode{res.StatusCode, res.StatusMessage} } - payloadType, ok := res.Header["Content-Type"] - if !ok || len(payloadType) != 1 { - return nil, nil, fmt.Errorf("Content-Type not provided") + ct, ok := res.Header["Content-Type"] + if !ok || len(ct) != 1 { + return nil, nil, ErrClientContentTypeMissing{} } - if payloadType[0] != "application/sdp" { - return nil, nil, fmt.Errorf("wrong Content-Type, expected application/sdp") + if ct[0] != "application/sdp" { + return nil, nil, ErrClientContentTypeUnsupported{ct} } tracks, err := ReadTracks(res.Body, u) @@ -423,17 +558,14 @@ func (c *ClientConn) Setup(mode headers.TransportMode, track *Track, return nil, err } - if mode == headers.TransportModeRecord && c.state != clientConnStatePreRecord { - return nil, fmt.Errorf("cannot read and publish at the same time") - } - - if mode == headers.TransportModePlay && c.state != clientConnStatePrePlay && - c.state != clientConnStateInitial { - return nil, fmt.Errorf("cannot read and publish at the same time") + if (mode == headers.TransportModeRecord && c.state != clientConnStatePreRecord) || + (mode == headers.TransportModePlay && c.state != clientConnStatePrePlay && + c.state != clientConnStateInitial) { + return nil, ErrClientCannotReadPublishAtSameTime{} } if c.streamURL != nil && *track.BaseURL != *c.streamURL { - return nil, fmt.Errorf("cannot setup tracks with different base urls") + return nil, ErrClientCannotSetupTracksDifferentURLs{} } var rtpListener *clientConnUDPListener @@ -472,11 +604,11 @@ func (c *ClientConn) Setup(mode headers.TransportMode, track *Track, if proto == base.StreamProtocolUDP { if (rtpPort == 0 && rtcpPort != 0) || (rtpPort != 0 && rtcpPort == 0) { - return nil, fmt.Errorf("rtpPort and rtcpPort must be both zero or non-zero") + return nil, ErrClientUDPPortsZero{} } if rtpPort != 0 && rtcpPort != (rtpPort+1) { - return nil, fmt.Errorf("rtcpPort must be rtpPort + 1") + return nil, ErrClientUDPPortsNotConsecutive{} } var err error @@ -568,7 +700,7 @@ func (c *ClientConn) Setup(mode headers.TransportMode, track *Track, return c.Setup(headers.TransportModePlay, track, 0, 0) } - return res, fmt.Errorf("bad status code: %d (%s)", res.StatusCode, res.StatusMessage) + return res, ErrClientWrongStatusCode{res.StatusCode, res.StatusMessage} } var thRes headers.Transport @@ -587,29 +719,27 @@ func (c *ClientConn) Setup(mode headers.TransportMode, track *Track, (thRes.ServerPorts[0] != 0 && thRes.ServerPorts[1] == 0) { rtpListener.close() rtcpListener.close() - return nil, fmt.Errorf("server ports must be both zero or both not zero") + return nil, ErrClientServerPortsZero{} } } if !c.conf.AnyPortEnable { - if thRes.ServerPorts == nil { + if thRes.ServerPorts == nil || (thRes.ServerPorts[0] == 0 && thRes.ServerPorts[1] == 0) { rtpListener.close() rtcpListener.close() - return nil, fmt.Errorf("server ports have not been provided. Use AnyPortEnable to communicate with this server") - } - - if thRes.ServerPorts[0] == 0 && thRes.ServerPorts[1] == 0 { - rtpListener.close() - rtcpListener.close() - return nil, fmt.Errorf("server ports have not been provided. Use AnyPortEnable to communicate with this server") + return nil, ErrClientServerPortsNotProvided{} } } - } else if thRes.InterleavedIDs == nil || - thRes.InterleavedIDs[0] != th.InterleavedIDs[0] || - thRes.InterleavedIDs[1] != th.InterleavedIDs[1] { - return nil, fmt.Errorf("transport header does not have interleaved ids %v (%s)", - *th.InterleavedIDs, res.Header["Transport"]) + } else { + if thRes.InterleavedIDs == nil { + return nil, ErrClientTransportHeaderNoInterleavedIDs{} + } + + if thRes.InterleavedIDs[0] != th.InterleavedIDs[0] || + thRes.InterleavedIDs[1] != th.InterleavedIDs[1] { + return nil, ErrClientTransportHeaderWrongInterleavedIDs{*th.InterleavedIDs, *thRes.InterleavedIDs} + } } clockRate, _ := track.ClockRate() @@ -681,7 +811,7 @@ func (c *ClientConn) Pause() (*base.Response, error) { } if res.StatusCode != base.StatusOK { - return res, fmt.Errorf("bad status code: %d (%s)", res.StatusCode, res.StatusMessage) + return res, ErrClientWrongStatusCode{res.StatusCode, res.StatusMessage} } switch c.state { diff --git a/clientconnpublish.go b/clientconnpublish.go index 2b32c372..dca84c3d 100644 --- a/clientconnpublish.go +++ b/clientconnpublish.go @@ -43,7 +43,7 @@ func (c *ClientConn) Announce(u *base.URL, tracks Tracks) (*base.Response, error } if res.StatusCode != base.StatusOK { - return nil, fmt.Errorf("bad status code: %d (%s)", res.StatusCode, res.StatusMessage) + return nil, ErrClientWrongStatusCode{res.StatusCode, res.StatusMessage} } c.streamURL = u @@ -71,7 +71,7 @@ func (c *ClientConn) Record() (*base.Response, error) { } if res.StatusCode != base.StatusOK { - return nil, fmt.Errorf("bad status code: %d (%s)", res.StatusCode, res.StatusMessage) + return nil, ErrClientWrongStatusCode{res.StatusCode, res.StatusMessage} } c.state = clientConnStateRecord diff --git a/clientconnread.go b/clientconnread.go index 12025835..d81a04a5 100644 --- a/clientconnread.go +++ b/clientconnread.go @@ -28,14 +28,14 @@ func (c *ClientConn) Play() (*base.Response, error) { } if res.StatusCode != base.StatusOK { - return nil, fmt.Errorf("bad status code: %d (%s)", res.StatusCode, res.StatusMessage) + return nil, ErrClientWrongStatusCode{res.StatusCode, res.StatusMessage} } if v, ok := res.Header["RTP-Info"]; ok { var ri headers.RTPInfo err := ri.Read(v) if err != nil { - return nil, fmt.Errorf("unable to parse RTP-Info: %v", err) + return nil, ErrClientRTPInfoInvalid{err} } c.rtpInfo = &ri } @@ -144,7 +144,7 @@ func (c *ClientConn) backgroundPlayUDP(done chan error) { if now.Sub(last) >= c.conf.ReadTimeout { c.nconn.SetReadDeadline(time.Now()) <-readerDone - returnError = fmt.Errorf("no UDP packets received recently (maybe there's a firewall/NAT in between)") + returnError = ErrClientNoUDPPacketsRecently{} return } } diff --git a/serverconn.go b/serverconn.go index dbb5fac1..b501e130 100644 --- a/serverconn.go +++ b/serverconn.go @@ -75,7 +75,7 @@ type ErrServerContentTypeUnsupported struct { // Error implements the error interface. func (e ErrServerContentTypeUnsupported) Error() string { - return fmt.Sprintf("unsupported Content-Type header '%s'", e.ct) + return fmt.Sprintf("unsupported Content-Type header '%v'", e.ct) } // ErrServerSDPInvalid is returned in case the SDP is invalid. @@ -139,7 +139,7 @@ type ErrServerTransportHeaderNoInterleavedIDs struct{} // Error implements the error interface. func (e ErrServerTransportHeaderNoInterleavedIDs) Error() string { - return "transport header does not contain interleaved ids" + return "transport header does not contain interleaved IDs" } // ErrServerTransportHeaderWrongInterleavedIDs is returned in case the transport header contains wrong interleaved IDs. @@ -150,7 +150,7 @@ type ErrServerTransportHeaderWrongInterleavedIDs struct { // Error implements the error interface. func (e ErrServerTransportHeaderWrongInterleavedIDs) Error() string { - return fmt.Sprintf("wrong interleaved ids, expected %v, got %v", e.expected, e.value) + return fmt.Sprintf("wrong interleaved IDs, expected %v, got %v", e.expected, e.value) } // ErrServerTracksDifferentProtocols is returned in case the client is trying to setup tracks with different protocols.