package lib import ( "crypto/md5" "encoding/hex" "fmt" "io" "log" "net" "regexp" "strings" "time" ) type Camera struct { host string port int alive time.Duration biosUri string fullUri string seq int sock net.Conn realm string nonce string auth string session string logger *log.Logger } func (cam *Camera) Init(host string, port int, alive int, logger *log.Logger) { cam.logger = logger cam.host, cam.port, cam.alive = host, port, time.Duration(alive) cam.biosUri = fmt.Sprintf("rtsp://%s:%d", host, port) cam.fullUri = fmt.Sprintf("rtsp://%s:%d/cam/realmonitor?channel=1&subtype=0", host, port) } func (cam *Camera) Run(stream chan []byte) { sock, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cam.host, cam.port)) if err != nil { cam.logger.Fatal("camera connect failed") } cam.seq, cam.sock = 1, sock cam.prepare(false) go cam.keepAliveThread() go cam.transportThread(stream) } func (cam *Camera) Stop() { if cam.sock == nil { return } // TEARDOWN request := fmt.Sprintf( "TEARDOWN %s RTSP/1.0\r\n"+ "CSeq: %d\r\n"+ `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"\r\n`+ "Session: %s\r\n"+ "User-Agent: Tinger.HM-HZ\r\n\r\n", cam.fullUri, cam.seq, User, cam.realm, cam.nonce, cam.fullUri, cam.auth, cam.session) cam.send(request) // response := cam.receive() // no receive, as dirty data // close connection _ = cam.sock.Close() cam.seq, cam.sock = 1, nil } func (cam *Camera) show(title string, body string) { fmt.Printf("==============< %s> START ==============\n", title) fmt.Println(body) fmt.Printf("===============< %s> END ===============\n", title) } func (cam *Camera) hash(raw string) string { hash := md5.New() hash.Write([]byte(raw)) hashBytes := hash.Sum(nil) return hex.EncodeToString(hashBytes) } func (cam *Camera) calcAuth(realm string, nonce string, method string) string { h1 := cam.hash(fmt.Sprintf("%s:%s:%s", User, realm, Pass)) h2 := cam.hash(fmt.Sprintf("%s:%s", method, cam.biosUri)) return cam.hash(fmt.Sprintf("%s:%s:%s", h1, nonce, h2)) } func (cam *Camera) send(msg string) { _, err := cam.sock.Write([]byte(msg)) if err != nil { // cam.logger.Print("message send failed") return } cam.seq++ } func (cam *Camera) receive() string { buff := make([]byte, BlockSize) n, err := cam.sock.Read(buff) if err != nil { cam.logger.Fatal("camera message receive failed") } return string(buff[:n]) } func (cam *Camera) prepare(verbose bool) { // OPTIONS 1: get realm, nonce request := fmt.Sprintf( "OPTIONS %s RTSP/1.0\r\n"+ "CSeq: %d\r\n"+ "User-Agent: Tinger.HM-HZ\r\n\r\n", cam.biosUri, cam.seq) cam.send(request) response := cam.receive() ptn := regexp.MustCompile(`Digest realm="(.+?)",nonce="(.+?)"\r\n`) search := ptn.FindStringSubmatch(response) if len(search) == 0 { cam.logger.Fatal("realm and nonce not matched") } cam.realm, cam.nonce = search[1], search[2] if verbose { cam.show("OPTIONS 1", request) cam.show("OPTIONS 1 RES", response) } // OPTIONS 2: auth cam.auth = cam.calcAuth(cam.realm, cam.nonce, "OPTIONS") request = fmt.Sprintf( "OPTIONS %s RTSP/1.0\r\n"+ "Accept: application/sdp\r\n"+ "CSeq: %d\r\n"+ `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"\r\n`+ "User-Agent: Tinger.HM-HZ\r\n\r\n", cam.biosUri, cam.seq, User, cam.realm, cam.nonce, cam.biosUri, cam.auth) cam.send(request) response = cam.receive() if verbose { cam.show("OPTIONS 2", request) cam.show("OPTIONS 2 RES", response) } // DESCRIBE request = fmt.Sprintf( "DESCRIBE %s RTSP/1.0\r\n"+ "CSeq: %d\r\n"+ "User-Agent: Tinger.HM-HZ\r\n\r\n", cam.fullUri, cam.seq) cam.send(request) response = cam.receive() if verbose { cam.show("DESCRIBE", request) cam.show("DESCRIBE RES", response) } // SETUP: get session id request = fmt.Sprintf( "SETUP %s/trackID=0 RTSP/1.0\r\n"+ "CSeq: %d\r\n"+ "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n"+ "User-Agent: Tinger.HM-HZ\r\n\r\n", cam.fullUri, cam.seq) cam.send(request) response = cam.receive() ptn = regexp.MustCompile(`Session: (\d+)`) search = ptn.FindStringSubmatch(response) if len(search) == 0 { cam.logger.Fatal("session not matched") } cam.session = search[1] if verbose { cam.show("SETUP", request) cam.show("SETUP RES", response) } // PLAY request = fmt.Sprintf( "PLAY %s RTSP/1.0\r\n"+ "CSeq: %d\r\n"+ "Session: %s\r\n"+ "Range: npt=0.000-\r\n"+ "User-Agent: Tigner,HN-HZ\r\n\r\n", cam.fullUri, cam.seq, cam.session) cam.send(request) response = cam.receive() if !strings.HasPrefix(response, "RTSP/1.0 200 OK") { cam.logger.Fatal("PLAY failed") } if verbose { cam.show("PLAY", request) cam.show("PLAY RES", response) } } func (cam *Camera) transportThread(stream chan []byte) { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * a complicated situation: * * The actual reason is that every communication message are put in the same socket, * * which leads to and (mostly keep alive response message) * * will be mixed in the only socket message pool. * * So header[0] maybe not b"$"(ASCII: 36), in this case, it means that there are another * * response message which is not image data, what should we do is just find one by one * * until b"$" appears, the bytes data which between the wrong header[0] and b"$" are recognized * * as dirty data(are dropped in current process) * * * * resolution in future: * * store all received data in a bytes buffer * * data start with b"$" is image steam data * * it is easy to read every block of this type of data * * data start with b"RTSP 200 OK" is response data * * problem: how to locate the end(\r\n\r\n not always the real end of a message) * * you can try it, believe yourself! another words: you can you up. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ defer cam.Stop() header := make([]byte, 4) buffer := make([]byte, BlockSize*2) syncer := make([]byte, 1) for { if count, err := io.ReadFull(cam.sock, header); err != nil || count != 4 { // cam.logger.Printf(fmt.Sprintf("header read error:\n%s\n", err)) return } if header[0] != 36 { rid := 0 for { if count, err := io.ReadFull(cam.sock, syncer); err != nil && count != 1 { // cam.logger.Printf("syncer read error:\n%s\n", err) return } else if syncer[0] == 36 { header[0] = 36 if count, err = io.ReadFull(cam.sock, header[1:]); err != nil && count == 3 { // cam.logger.Printf("header remain read error:\n%s\n", err) return } break } else { rid++ } } cam.logger.Printf("get rid of: %d\n", rid) } payloadLen := int(header[2])<<8 + int(header[3]) fullLength := payloadLen + 4 if fullLength > len(buffer) { buffer = make([]byte, fullLength) } copy(buffer, header) if count, err := io.ReadFull(cam.sock, buffer[4:fullLength]); err != nil || count != payloadLen { // cam.logger.Printf("payload read error:\n%s\n", err) return } else { stream <- buffer[:fullLength] } } } func (cam *Camera) keepAliveThread() { defer cam.Stop() for { time.Sleep(time.Second * cam.alive) if cam.sock == nil { continue } request := fmt.Sprintf( "OPTIONS %s RTSP/1.0\r\n"+ "Accept: application/sdp\r\n"+ "CSeq: %d\r\n"+ `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"\r\n`+ "User-Agent: Tinger.HM-HZ\r\n\r\n", cam.biosUri, cam.seq, User, cam.realm, cam.nonce, cam.biosUri, cam.auth) cam.send(request) // response := cam.receive() // no receive, as dirty data } }