-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmessage.go
More file actions
182 lines (172 loc) · 5.37 KB
/
message.go
File metadata and controls
182 lines (172 loc) · 5.37 KB
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package stun
// Message represents a complete STUN message, including its header and attributes.
// A STUN message consists of a 20-byte header followed by zero or more attributes.
//
// The Message structure follows RFC 5389 specifications:
// - Header: Contains message type, length, magic cookie, and transaction ID
// - Attributes: Variable-length list of STUN attributes
//
// Example:
//
// msg := &stun.Message{
// Header: stun.Header{
// Type: stun.BindingRequest,
// },
// Attributes: []stun.Attribute{},
// }
type Message struct {
Header Header
Attributes Attributes
}
// NewMessage creates a new Message by parsing the provided byte buffer.
// The buffer should contain a complete STUN message starting with the header.
//
// The function performs the following operations:
// - Decodes the 20-byte header
// - Validates the magic cookie
// - Parses all attributes based on the message length
// - Returns a fully populated Message structure
//
// Returns:
// - *Message: The parsed STUN message
// - error: Any error that occurred during parsing
//
// Example:
//
// buff := []byte{...} // STUN message bytes
// msg, err := stun.NewMessage(buff)
// if err != nil {
// log.Fatal(err)
// }
func NewMessage(buff []byte) (*Message, error) {
header, err := decodeHeader(buff)
if err != nil {
return nil, err
}
attributes := decodeAttrs(buff[20:], int(header.Length))
return &Message{
Header: *header,
Attributes: attributes,
}, nil
}
// GetAttr searches for a specific attribute type in the message and returns it if found.
// This method iterates through all attributes in the message to find a match.
//
// Parameters:
// - t: The StunAttribute type to search for
//
// Returns:
// - *Attribute: The found attribute, or nil if not found
// - bool: True if the attribute was found, false otherwise
//
// Example:
//
// if attr, found := msg.GetAttr(stun.XORMappedAddress); found {
// // Process the XOR-MAPPED-ADDRESS attribute
// xorAddr := decodeAddr(attr.Value)
// fmt.Printf("XOR Address: %s:%d\n", xorAddr.IP, xorAddr.Port)
// }
func (m Message) GetAttr(t StunAttribute) (*Attribute, bool) {
for _, attr := range m.Attributes {
if attr.Type == t {
return &attr, true
}
}
return nil, false
}
// GetXorAddr extracts the XOR-MAPPED-ADDRESS attribute from the message.
// This method is specifically designed for handling binding responses and
// provides a convenient way to access the client's public IP address and port.
//
// The method checks if the message is a binding response and then looks for
// the XOR-MAPPED-ADDRESS attribute. If found, it decodes the address information.
//
// Returns:
// - *XorMappedAddr: The decoded XOR mapped address, or nil if not found
// - error: Any error that occurred during decoding
//
// Example:
//
// msg, err := client.Dial(&stun.Message{
// Header: stun.Header{Type: stun.BindingRequest},
// })
// if err != nil {
// log.Fatal(err)
// }
//
// xorAddr, err := msg.GetXorAddr()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Public IP: %s:%d\n", xorAddr.IP, xorAddr.Port)
func (m Message) GetXorAddr() (*XorMappedAddr, error) {
if m.Header.Type != BindingResponse {
return nil, nil
}
if attr, ok := m.GetAttr(XORMappedAddress); ok {
return decodeAddr(attr.Value), nil
}
return nil, ErrAttrNotFound
}
// decodeAttrs decodes multiple STUN attributes from the given byte buffer.
// It iterates through the buffer, decoding each attribute and adding it to a slice.
//
// The function processes attributes sequentially, using the length information
// in each attribute header to determine where the next attribute begins.
// Each attribute has a 4-byte header (type + length) followed by the attribute value.
//
// Parameters:
// - buff: The byte buffer containing attribute data
// - length: The total length of attribute data to process
//
// Returns:
// - []Attribute: A slice of decoded STUN attributes
func decodeAttrs(buff []byte, length int) []Attribute {
offset := 0
var attrs []Attribute
// Loop through the buffer until the entire length is processed
for offset < length {
// Decode the current STUN attribute starting at the current offset
attr := DecodeAttr(buff[offset:])
// Append the decoded attribute to the slice
attrs = append(attrs, attr)
// Move the offset to the start of the next attribute
// Each attribute has a 4-byte header (type + length) plus the padded value
offset += 4 + attr.PaddedLength
}
// Return the slice of decoded attributes
return attrs
}
// Encode converts the Message to its binary representation.
// This method serializes the complete STUN message including header and all attributes.
//
// The encoding process:
// - Encodes the 20-byte header
// - Encodes each attribute in sequence
// - Returns the complete binary message
//
// Returns:
// - []byte: The encoded STUN message as a byte slice
//
// Example:
//
// msg := &stun.Message{
// Header: stun.Header{
// Type: stun.BindingRequest,
// },
// Attributes: []stun.Attribute{},
// }
// encoded := msg.Encode()
// // Send encoded message over network
func (m *Message) Encode() []byte {
buff := make([]byte, m.Header.Length+20)
copy(buff[0:20], m.Header.Encode())
offset := 20
for _, attr := range m.Attributes {
encodedAttr := attr.Encode()
attrSize := attr.PaddedLength + 4 // 4 bytes header + padded value length
copy(buff[offset:offset+attrSize], encodedAttr)
offset += attrSize
}
return buff
}