75 lines
2.1 KiB
Go
75 lines
2.1 KiB
Go
package rtsp
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Credential struct {
|
|
Username string
|
|
Password string
|
|
}
|
|
type Result struct {
|
|
Status string `json:"status"`
|
|
LatencyMS int64 `json:"latency_ms"`
|
|
Detail string `json:"detail,omitempty"`
|
|
}
|
|
type Verifier interface {
|
|
Verify(context.Context, string, Credential) (Result, error)
|
|
}
|
|
type NetVerifier struct{ Timeout time.Duration }
|
|
|
|
func (v NetVerifier) Verify(ctx context.Context, raw string, credential Credential) (Result, error) {
|
|
parsed, err := url.Parse(raw)
|
|
if err != nil || parsed.Scheme != "rtsp" || parsed.Host == "" {
|
|
return Result{}, fmt.Errorf("invalid_rtsp_uri")
|
|
}
|
|
if parsed.User != nil {
|
|
return Result{}, fmt.Errorf("rtsp_uri_contains_credentials")
|
|
}
|
|
address := parsed.Host
|
|
if !strings.Contains(address, ":") {
|
|
address += ":554"
|
|
}
|
|
timeout := v.Timeout
|
|
if timeout <= 0 {
|
|
timeout = 5 * time.Second
|
|
}
|
|
dialer := net.Dialer{Timeout: timeout}
|
|
started := time.Now()
|
|
connection, err := dialer.DialContext(ctx, "tcp", address)
|
|
if err != nil {
|
|
return Result{Status: "unreachable", Detail: "无法连接视频端口"}, nil
|
|
}
|
|
defer connection.Close()
|
|
_ = connection.SetDeadline(time.Now().Add(timeout))
|
|
authorization := ""
|
|
if credential.Username != "" {
|
|
authorization = "Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(credential.Username+":"+credential.Password)) + "\r\n"
|
|
}
|
|
request := fmt.Sprintf("OPTIONS %s RTSP/1.0\r\nCSeq: 1\r\nUser-Agent: YoVision-Sense\r\n%s\r\n", parsed.String(), authorization)
|
|
if _, err := connection.Write([]byte(request)); err != nil {
|
|
return Result{}, err
|
|
}
|
|
line, err := bufio.NewReader(connection).ReadString('\n')
|
|
if err != nil {
|
|
return Result{Status: "timeout", Detail: "等待视频响应超时"}, nil
|
|
}
|
|
status := "ready"
|
|
detail := "码流可访问"
|
|
if strings.Contains(line, " 401 ") {
|
|
status = "authentication_failed"
|
|
detail = "设备拒绝了当前凭据"
|
|
} else if !strings.Contains(line, " 200 ") {
|
|
status = "failed"
|
|
detail = "设备返回非成功状态"
|
|
}
|
|
return Result{Status: status, LatencyMS: time.Since(started).Milliseconds(), Detail: detail}, nil
|
|
}
|