-
Notifications
You must be signed in to change notification settings - Fork 0
/
message.go
139 lines (118 loc) · 3.79 KB
/
message.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"fmt"
"log"
"strconv"
"strings"
)
// Message represents an iRTSP message. iRTSP (possibly standing for Interactive RTSP?) is a
// text-based protocol, essentially similar to RTSP but with some additions for user interaction
// with the server.
//
// A sequence header is present at the start of the message below the protocol version, which works
// the same way as the "CSeq" header on the standard RTSP protocol.
//
// Message lines are split with CRLF, and header fields are values are split with an equal sign (=).
// Messages always end with "Submit + CRLF"
//
// An example message would be:
// iRTSP/1.21 + CRLF
// Seq=0 + CRLF
// SET/START + CRLF
// sc + CRLF
// t=1429051 + CRLF
// Submit + CRLF
type Message struct {
// Version represents the iRTSP version. An example value would be "iRTSP/1.21"
Version string
// Sequence is the message sequence number
Sequence int
// Method is the message method
Method string
// Code is the response code, if the message is a response
Code int
// Headers are the message headers
Headers map[string]string
}
// ToBytes converts the message to a byte stream
func (m *Message) ToBytes() []byte {
builder := &strings.Builder{}
builder.WriteString(m.Version + "\r\n")
builder.WriteString(fmt.Sprintf("Seq=%d\r\n", m.Sequence))
// If the code isn't zero, then the message is a response and we can use the response line
if m.Code > 0 {
builder.WriteString(fmt.Sprintf("RSP/%s/%d\r\n", m.Method, m.Code))
} else {
builder.WriteString(fmt.Sprintf("SET/%s\r\n", m.Method))
}
for header, value := range m.Headers {
// If a header value is empty, we don't write the equal sign
if value == "" {
builder.WriteString(header + "\r\n")
} else {
builder.WriteString(fmt.Sprintf("%s=%s\r\n", header, value))
}
}
builder.WriteString("Submit\r\n")
return []byte(builder.String())
}
// NewMessage creates a new Message from a byte array
//
// TODO: Support multiple messages on the same stream
func NewMessage(message []byte) *Message {
messageString := string(message)
// Replace CRLF line endings with LF so that we can split the message lines
messageString = strings.ReplaceAll(messageString, "\r\n", "\n")
messageLines := strings.Split(messageString, "\n")
if len(messageLines) <= 0 {
return nil
}
// As there is a CRLF at the end, the last line will be empty
messageLines = messageLines[:len(messageLines)-1]
if messageLines[len(messageLines)-1] == "Submit" {
// Remove "Submit" line
messageLines = messageLines[:len(messageLines)-1]
}
msg := &Message{Headers: make(map[string]string)}
msg.Version = messageLines[0]
// Discard the vresion line
messageLines = messageLines[1:]
// Extract sequence value from message lines
seqField, seqValue, found := strings.Cut(messageLines[0], "=")
if found && seqField == "Seq" {
seq, err := strconv.Atoi(seqValue)
if err != nil {
log.Printf("[ERROR] %v\n", err)
return msg
}
msg.Sequence = seq
messageLines = messageLines[1:]
}
// Extract method from message lines
msgSource, msgMethod, found := strings.Cut(messageLines[0], "/")
// If the message is a request, we can set the method right away
if found && msgSource == "SET" {
msg.Method = msgMethod
messageLines = messageLines[1:]
}
// If the message is a response, we have to split the method and the response code
if found && msgSource == "RSP" {
method, codeString, found := strings.Cut(msgMethod, "/")
if found {
msg.Method = method
code, err := strconv.Atoi(codeString)
if err != nil {
log.Printf("[ERROR] %v\n", err)
return msg
}
msg.Code = code
}
messageLines = messageLines[1:]
}
// Extract headers from message lines
for _, msgHeaderField := range messageLines {
msgHeader, msgValue, _ := strings.Cut(msgHeaderField, "=")
msg.Headers[msgHeader] = msgValue
}
return msg
}