1package magna
2
3import (
4 "encoding/binary"
5 "strings"
6)
7
8// decode_domain decodes a domain name from a buffer starting at offset.
9// It returns the domain name along with the offset and error.
10func decode_domain(buf []byte, offset int) (string, int, error) {
11 var builder strings.Builder
12 firstLabel := true
13
14 seen_offsets := make(map[int]struct{})
15 finalOffsetAfterJump := -1
16
17 currentOffset := offset
18
19 for {
20 if _, found := seen_offsets[currentOffset]; found {
21 return "", len(buf), &DomainCompressionError{}
22 }
23 seen_offsets[currentOffset] = struct{}{}
24
25 length, nextOffsetAfterLen, err := getU8(buf, currentOffset)
26 if err != nil {
27 return "", len(buf), err
28 }
29
30 if length == 0 {
31 currentOffset = nextOffsetAfterLen
32 break
33 }
34
35 if (length & 0xC0) == 0xC0 {
36 sec, nextOffsetAfterPtr, err := getU8(buf, nextOffsetAfterLen)
37 if err != nil {
38 return "", len(buf), err
39 }
40
41 jumpTargetOffset := int(length&0x3F)<<8 | int(sec)
42
43 if jumpTargetOffset >= len(buf) {
44 return "", len(buf), &BufferOverflowError{Length: len(buf), Offset: jumpTargetOffset}
45 }
46 if _, found := seen_offsets[jumpTargetOffset]; found {
47 return "", len(buf), &DomainCompressionError{}
48 }
49
50 if finalOffsetAfterJump == -1 {
51 finalOffsetAfterJump = nextOffsetAfterPtr
52 }
53
54 currentOffset = jumpTargetOffset
55 continue
56 }
57
58 if length > 63 {
59 return "", len(buf), &InvalidLabelError{Length: int(length)}
60 }
61
62 labelBytes, nextOffsetAfterLabel, err := getSlice(buf, nextOffsetAfterLen, int(length))
63 if err != nil {
64 return "", len(buf), err
65 }
66
67 if !firstLabel {
68 builder.WriteByte('.')
69 }
70 builder.Write(labelBytes)
71 firstLabel = false
72
73 currentOffset = nextOffsetAfterLabel
74
75 }
76
77 finalReadOffset := currentOffset
78 if finalOffsetAfterJump != -1 {
79 finalReadOffset = finalOffsetAfterJump
80 }
81
82 return builder.String(), finalReadOffset, nil
83}
84
85// encode_domain returns the bytes of the input bytes appened with the encoded domain name.
86func encode_domain(bytes []byte, domain_name string, offsets *map[string]uint16) []byte {
87 if domain_name == "." || domain_name == "" {
88 return append(bytes, 0)
89 }
90
91 clean_domain := strings.TrimSuffix(domain_name, ".")
92 if clean_domain == "" {
93 return append(bytes, 0)
94 }
95
96 start := 0
97 for start < len(clean_domain) {
98 suffix := clean_domain[start:]
99
100 if offset, found := (*offsets)[suffix]; found {
101 if offset > 0x3FFF {
102 end := strings.IndexByte(suffix, '.')
103 if end == -1 {
104 end = len(suffix)
105 }
106 } else {
107 pointer := 0xC000 | offset
108 return binary.BigEndian.AppendUint16(bytes, pointer)
109 }
110 }
111
112 currentPos := uint16(len(bytes))
113 if currentPos <= 0x3FFF {
114 (*offsets)[suffix] = currentPos
115 }
116
117 end := strings.IndexByte(suffix, '.')
118 var label string
119 nextStart := len(clean_domain)
120
121 if end == -1 {
122 label = suffix
123 start = nextStart
124 } else {
125 label = suffix[:end]
126 nextStart = start + end + 1
127 start = nextStart
128 }
129
130 labelBytes := []byte(label)
131
132 if len(labelBytes) > 63 {
133 // XXX: maybe should return an error
134 labelBytes = labelBytes[:63]
135 }
136
137 bytes = append(bytes, byte(len(labelBytes)))
138 bytes = append(bytes, labelBytes...)
139 }
140
141 return append(bytes, 0)
142}