github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/media/serverRTSP.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package media
    20  
    21  import (
    22  	"bytes"
    23  	"errors"
    24  	"fmt"
    25  	"net"
    26  	"net/url"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  )
    31  
    32  var (
    33  	Version   = "RTSP/1.0"
    34  	UserAgent = "Lavf58.29.100"
    35  	Session   = "000a959d6816"
    36  )
    37  var (
    38  	OPTIONS  = "OPTIONS"
    39  	DESCRIBE = "DESCRIBE"
    40  	SETUP    = "SETUP"
    41  	PLAY     = "PLAY"
    42  	TEARDOWN = "TEARDOWN"
    43  )
    44  
    45  // RTSP response status codes
    46  const (
    47  	StatusContinue                      = 100
    48  	StatusOK                            = 200
    49  	StatusCreated                       = 201
    50  	StatusLowOnStorageSpace             = 250
    51  	StatusMultipleChoices               = 300
    52  	StatusMovedPermanently              = 301
    53  	StatusMovedTemporarily              = 302
    54  	StatusSeeOther                      = 303
    55  	StatusNotModified                   = 304
    56  	StatusUseProxy                      = 305
    57  	StatusBadRequest                    = 400
    58  	StatusUnauthorized                  = 401
    59  	StatusPaymentRequired               = 402
    60  	StatusForbidden                     = 403
    61  	StatusNotFound                      = 404
    62  	StatusMethodNotAllowed              = 405
    63  	StatusNotAcceptable                 = 406
    64  	StatusProxyAuthenticationRequired   = 407
    65  	StatusRequestTimeout                = 408
    66  	StatusGone                          = 410
    67  	StatusLengthRequired                = 411
    68  	StatusPreconditionFailed            = 412
    69  	StatusRequestEntityTooLarge         = 413
    70  	StatusRequestURITooLong             = 414
    71  	StatusUnsupportedMediaType          = 415
    72  	StatusInvalidparameter              = 451
    73  	StatusIllegalConferenceIdentifier   = 452
    74  	StatusNotEnoughBandwidth            = 453
    75  	StatusSessionNotFound               = 454
    76  	StatusMethodNotValidInThisState     = 455
    77  	StatusHeaderFieldNotValid           = 456
    78  	StatusInvalidRange                  = 457
    79  	StatusParameterIsReadOnly           = 458
    80  	StatusAggregateOperationNotAllowed  = 459
    81  	StatusOnlyAggregateOperationAllowed = 460
    82  	StatusUnsupportedTransport          = 461
    83  	StatusDestinationUnreachable        = 462
    84  	StatusInternalServerError           = 500
    85  	StatusNotImplemented                = 501
    86  	StatusBadGateway                    = 502
    87  	StatusServiceUnavailable            = 503
    88  	StatusGatewayTimeout                = 504
    89  	StatusRTSPVersionNotSupported       = 505
    90  	StatusOptionNotsupport              = 551
    91  )
    92  
    93  func StatusText(code int) string {
    94  	return statusText[code]
    95  }
    96  
    97  var statusText = map[int]string{
    98  	StatusContinue:                      "Continue",
    99  	StatusOK:                            "OK",
   100  	StatusCreated:                       "Created",
   101  	StatusLowOnStorageSpace:             "Low on Storage Space",
   102  	StatusMultipleChoices:               "Multiple Choices",
   103  	StatusMovedPermanently:              "Moved Permanently",
   104  	StatusMovedTemporarily:              "Moved Temporarily",
   105  	StatusSeeOther:                      "See Other",
   106  	StatusNotModified:                   "Not Modified",
   107  	StatusUseProxy:                      "Use Proxy",
   108  	StatusBadRequest:                    "Bad Request",
   109  	StatusUnauthorized:                  "Unauthorized",
   110  	StatusPaymentRequired:               "Payment Required",
   111  	StatusForbidden:                     "Forbidden",
   112  	StatusNotFound:                      "Not Found",
   113  	StatusMethodNotAllowed:              "Method Not Allowed",
   114  	StatusNotAcceptable:                 "Not Acceptable",
   115  	StatusProxyAuthenticationRequired:   "Proxy Authentication Required",
   116  	StatusRequestTimeout:                "Request Time-out",
   117  	StatusGone:                          "Gone",
   118  	StatusLengthRequired:                "Length Required",
   119  	StatusPreconditionFailed:            "Precondition Failed",
   120  	StatusRequestEntityTooLarge:         "Request Entity Too Large",
   121  	StatusRequestURITooLong:             "Request-URI Too Large",
   122  	StatusUnsupportedMediaType:          "Unsupported Media Type",
   123  	StatusInvalidparameter:              "Parameter Not Understood",
   124  	StatusIllegalConferenceIdentifier:   "Conference Not Found",
   125  	StatusNotEnoughBandwidth:            "Not Enough Bandwidth",
   126  	StatusSessionNotFound:               "Session Not Found",
   127  	StatusMethodNotValidInThisState:     "Method Not Valid in This State",
   128  	StatusHeaderFieldNotValid:           "Header Field Not Valid for Resource",
   129  	StatusInvalidRange:                  "Invalid Range",
   130  	StatusParameterIsReadOnly:           "Parameter Is Read-Only",
   131  	StatusAggregateOperationNotAllowed:  "Aggregate operation not allowed",
   132  	StatusOnlyAggregateOperationAllowed: "Only aggregate operation allowed",
   133  	StatusUnsupportedTransport:          "Unsupported transport",
   134  	StatusDestinationUnreachable:        "Destination unreachable",
   135  	StatusInternalServerError:           "Internal Server Error",
   136  	StatusNotImplemented:                "Not Implemented",
   137  	StatusBadGateway:                    "Bad Gateway",
   138  	StatusServiceUnavailable:            "Service Unavailable",
   139  	StatusGatewayTimeout:                "Gateway Time-out",
   140  	StatusRTSPVersionNotSupported:       "RTSP Version not supported",
   141  	StatusOptionNotsupport:              "Option not supported",
   142  }
   143  
   144  // RTSPServer func
   145  func RTSPServer() {
   146  	log.Info("Server RTSP start")
   147  	l, err := net.Listen("tcp", Storage.ServerRTSPPort())
   148  	if err != nil {
   149  		log.Error(err.Error())
   150  		return
   151  	}
   152  	defer func() {
   153  		err := l.Close()
   154  		if err != nil {
   155  			log.Error(err.Error())
   156  		}
   157  	}()
   158  	for {
   159  		conn, err := l.Accept()
   160  		if err != nil {
   161  			log.Error(err.Error())
   162  			return
   163  		}
   164  		go RTSPServerClientHandle(conn)
   165  	}
   166  }
   167  
   168  // RTSPServerClientHandle func
   169  func RTSPServerClientHandle(conn net.Conn) {
   170  	buf := make([]byte, 4096)
   171  	token, uuid, channel, in, cSEQ := "", "", "0", 0, 0
   172  	var playStarted bool
   173  	defer func() {
   174  		err := conn.Close()
   175  		if err != nil {
   176  			log.Error(err.Error())
   177  		}
   178  
   179  	}()
   180  	err := conn.SetDeadline(time.Now().Add(10 * time.Second))
   181  	if err != nil {
   182  		log.Error(err.Error())
   183  		return
   184  	}
   185  	for {
   186  		n, err := conn.Read(buf)
   187  		if err != nil {
   188  			log.Error(err.Error())
   189  			return
   190  		}
   191  		cSEQ = parsecSEQ(buf[:n])
   192  		stage, err := parseStage(buf[:n])
   193  		if err != nil {
   194  			log.Error(err.Error())
   195  		}
   196  		err = conn.SetDeadline(time.Now().Add(60 * time.Second))
   197  		log.Debug(string(buf[:n]))
   198  
   199  		if err != nil {
   200  			log.Error(err.Error())
   201  			return
   202  		}
   203  
   204  		switch stage {
   205  		case OPTIONS:
   206  			if playStarted {
   207  				err = RTSPServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "Public": "DESCRIBE, SETUP, TEARDOWN, PLAY"})
   208  				if err != nil {
   209  					return
   210  				}
   211  				continue
   212  			}
   213  			uuid, channel, token, err = parseStreamChannel(buf[:n])
   214  			if err != nil {
   215  				log.Error(err.Error())
   216  				return
   217  			}
   218  			if !Storage.StreamChannelExist(uuid, channel) {
   219  				log.Error(ErrorStreamNotFound.Error())
   220  				err = RTSPServerClientResponse(uuid, channel, conn, 404, map[string]string{"CSeq": strconv.Itoa(cSEQ)})
   221  				if err != nil {
   222  					return
   223  				}
   224  				return
   225  			}
   226  
   227  			if !RemoteAuthorization("RTSP", uuid, channel, token, conn.RemoteAddr().String()) {
   228  				log.Error(ErrorStreamUnauthorized.Error())
   229  				err = RTSPServerClientResponse(uuid, channel, conn, 401, map[string]string{"CSeq": strconv.Itoa(cSEQ)})
   230  				if err != nil {
   231  					return
   232  				}
   233  				return
   234  			}
   235  
   236  			Storage.StreamChannelRun(uuid, channel)
   237  			err = RTSPServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "Public": "DESCRIBE, SETUP, TEARDOWN, PLAY"})
   238  			if err != nil {
   239  				return
   240  			}
   241  		case SETUP:
   242  			if !strings.Contains(string(buf[:n]), "interleaved") {
   243  				err = RTSPServerClientResponse(uuid, channel, conn, 461, map[string]string{"CSeq": strconv.Itoa(cSEQ)})
   244  				if err != nil {
   245  					return
   246  				}
   247  				continue
   248  			}
   249  			err = RTSPServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session, "Transport": "RTP/AVP/TCP;unicast;interleaved=" + strconv.Itoa(in) + "-" + strconv.Itoa(in+1)})
   250  			if err != nil {
   251  				return
   252  			}
   253  			in = in + 2
   254  		case DESCRIBE:
   255  			sdp, err := Storage.StreamChannelSDP(uuid, channel)
   256  			if err != nil {
   257  				log.Error(err.Error())
   258  				return
   259  			}
   260  			err = RTSPServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session, "Content-Type": "application/sdp\r\nContent-Length: " + strconv.Itoa(len(sdp)), "sdp": string(sdp)})
   261  			if err != nil {
   262  				return
   263  			}
   264  		case PLAY:
   265  			err = RTSPServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session})
   266  			if err != nil {
   267  				return
   268  			}
   269  			playStarted = true
   270  			go RTSPServerClientPlay(uuid, channel, conn)
   271  		case TEARDOWN:
   272  			err = RTSPServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session})
   273  			if err != nil {
   274  				return
   275  			}
   276  			return
   277  		default:
   278  			log.Debugf("stage bad %v", stage)
   279  		}
   280  	}
   281  }
   282  
   283  // handleRTSPServerPlay func
   284  func RTSPServerClientPlay(uuid string, channel string, conn net.Conn) {
   285  	cid, _, ch, err := Storage.ClientAdd(uuid, channel, RTSP)
   286  	if err != nil {
   287  		log.Error(err.Error())
   288  		return
   289  	}
   290  	defer func() {
   291  		Storage.ClientDelete(uuid, cid, channel)
   292  		log.Info("Client offline")
   293  		err := conn.Close()
   294  		if err != nil {
   295  			log.Error(err.Error())
   296  		}
   297  	}()
   298  
   299  	noVideo := time.NewTimer(10 * time.Second)
   300  
   301  	for {
   302  		select {
   303  		case <-noVideo.C:
   304  			return
   305  		case pck := <-ch:
   306  			noVideo.Reset(10 * time.Second)
   307  			_, err := conn.Write(*pck)
   308  			if err != nil {
   309  				log.Error(err.Error())
   310  				return
   311  			}
   312  		}
   313  	}
   314  }
   315  
   316  // handleRTSPServerPlay func
   317  func RTSPServerClientResponse(uuid string, channel string, conn net.Conn, status int, headers map[string]string) error {
   318  	var sdp string
   319  	builder := bytes.Buffer{}
   320  	builder.WriteString(fmt.Sprintf(Version+" %d %s\r\n", status, StatusText(status)))
   321  	for k, v := range headers {
   322  		if k == "sdp" {
   323  			sdp = v
   324  			continue
   325  		}
   326  		builder.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
   327  	}
   328  	builder.WriteString("\r\n")
   329  	builder.WriteString(sdp)
   330  	log.Debug(builder.String())
   331  	if _, err := conn.Write(builder.Bytes()); err != nil {
   332  		log.Error(err.Error())
   333  		return err
   334  	}
   335  	return nil
   336  }
   337  
   338  // parsecSEQ func
   339  func parsecSEQ(buf []byte) int {
   340  	return stringToInt(stringInBetween(string(buf), "CSeq: ", "\r\n"))
   341  }
   342  
   343  // parseStage func
   344  func parseStage(buf []byte) (string, error) {
   345  	st := strings.Split(string(buf), " ")
   346  	if len(st) > 0 {
   347  		return st[0], nil
   348  	}
   349  	return "", errors.New("parse stage error " + string(buf))
   350  }
   351  
   352  // parseStreamChannel func
   353  func parseStreamChannel(buf []byte) (string, string, string, error) {
   354  
   355  	var token string
   356  
   357  	uri := stringInBetween(string(buf), " ", " ")
   358  	u, err := url.Parse(uri)
   359  	if err == nil {
   360  		token = u.Query().Get("token")
   361  		uri = u.Path
   362  	}
   363  
   364  	st := strings.Split(uri, "/")
   365  
   366  	if len(st) >= 3 {
   367  		return st[1], st[2], token, nil
   368  	}
   369  
   370  	return "", "0", token, errors.New("parse stream error " + string(buf))
   371  }