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