package magna import ( "encoding/binary" "fmt" "net" "strings" ) func (a *A) Decode(buf []byte, offset int, rdlength int) (int, error) { bytes, offset, err := getSlice(buf, offset, rdlength) if err != nil { return offset, err } a.Address = net.IP(bytes) return offset, err } func (a *A) Encode(bytes []byte, offsets *map[string]uint16) []byte { return append(bytes, a.Address.To4()...) } func (a A) String() string { return a.Address.String() } func (ns *NS) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error ns.NSDName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (ns *NS) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, ns.NSDName, offsets) } func (ns NS) String() string { return ns.NSDName } func (md *MD) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error md.MADName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (md *MD) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, md.MADName, offsets) } func (md MD) String() string { return md.MADName } func (mf *MF) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error mf.MADName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (mf *MF) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, mf.MADName, offsets) } func (mf MF) String() string { return mf.MADName } func (c *CNAME) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error c.CName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (c *CNAME) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, c.CName, offsets) } func (c CNAME) String() string { return c.CName } func (soa *SOA) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error soa.MName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } soa.RName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } soa.Serial, offset, err = getU32(buf, offset) if err != nil { return offset, err } soa.Refresh, offset, err = getU32(buf, offset) if err != nil { return offset, err } soa.Retry, offset, err = getU32(buf, offset) if err != nil { return offset, err } soa.Expire, offset, err = getU32(buf, offset) if err != nil { return offset, err } soa.Minimum, offset, err = getU32(buf, offset) if err != nil { return offset, err } return offset, err } func (soa *SOA) Encode(bytes []byte, offsets *map[string]uint16) []byte { bytes = encode_domain(bytes, soa.MName, offsets) bytes = encode_domain(bytes, soa.RName, offsets) bytes = binary.BigEndian.AppendUint32(bytes, soa.Serial) bytes = binary.BigEndian.AppendUint32(bytes, soa.Refresh) bytes = binary.BigEndian.AppendUint32(bytes, soa.Retry) bytes = binary.BigEndian.AppendUint32(bytes, soa.Expire) bytes = binary.BigEndian.AppendUint32(bytes, soa.Minimum) return bytes } func (soa SOA) String() string { return fmt.Sprintf("%s %s %d %d %d %d %d", soa.MName, soa.RName, soa.Serial, soa.Refresh, soa.Retry, soa.Expire, soa.Minimum) } func (mb *MB) Decode(buf []byte, offset int, rdlength int) (int, error) { madname, offset, err := decode_domain(buf, offset) if err != nil { return offset, err } mb.MADName = string(madname) return offset, err } func (mb *MB) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, mb.MADName, offsets) } func (mb MB) String() string { return mb.MADName } func (mg *MG) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error mg.MGMName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (mg *MG) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, mg.MGMName, offsets) } func (mg MG) String() string { return mg.MGMName } func (mr *MR) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error mr.NEWName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (mr *MR) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, mr.NEWName, offsets) } func (mr MR) String() string { return mr.NEWName } func (null *NULL) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error null.Anything, offset, err = getSlice(buf, offset, int(rdlength)) if err != nil { return offset, err } return offset, err } func (null *NULL) Encode(bytes []byte, offsets *map[string]uint16) []byte { return append(bytes, null.Anything...) } func (null NULL) String() string { return string(null.Anything) } func (wks *WKS) Decode(buf []byte, offset int, rdlength int) (int, error) { if rdlength < 5 { return len(buf), &MagnaError{Message: fmt.Sprintf("magna: WKS RDLENGTH too short: %d", rdlength)} } addressBytes, nextOffset, err := getSlice(buf, offset, 4) if err != nil { return len(buf), fmt.Errorf("magna: WKS error reading address: %w", err) } offset = nextOffset wks.Address = net.IP(addressBytes) protocol, nextOffset, err := getU8(buf, offset) if err != nil { return len(buf), fmt.Errorf("magna: WKS error reading protocol: %w", err) } offset = nextOffset wks.Protocol = protocol bitmapLength := rdlength - 5 wks.BitMap, nextOffset, err = getSlice(buf, offset, bitmapLength) if err != nil { return len(buf), fmt.Errorf("magna: WKS error reading bitmap: %w", err) } offset = nextOffset return offset, nil } func (wks *WKS) Encode(bytes []byte, offsets *map[string]uint16) []byte { bytes = append(bytes, wks.Address.To4()...) bytes = append(bytes, wks.Protocol) bytes = append(bytes, wks.BitMap...) return bytes } func (wks WKS) String() string { return fmt.Sprintf("%s %d %s", wks.Address.String(), wks.Protocol, wks.BitMap) } func (ptr *PTR) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error ptr.PTRDName, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (ptr *PTR) Encode(bytes []byte, offsets *map[string]uint16) []byte { return encode_domain(bytes, ptr.PTRDName, offsets) } func (ptr PTR) String() string { return ptr.PTRDName } func (hinfo *HINFO) Decode(buf []byte, offset int, rdlength int) (int, error) { endOffset := offset + rdlength if endOffset > len(buf) { return len(buf), &BufferOverflowError{Length: len(buf), Offset: endOffset} } currentOffset := offset var err error cpuLen, nextOffset, err := getU8(buf, currentOffset) if err != nil { return len(buf), fmt.Errorf("magna: HINFO error reading CPU length: %w", err) } currentOffset = nextOffset if currentOffset+int(cpuLen) > endOffset { return len(buf), &BufferOverflowError{Length: len(buf), Offset: currentOffset + int(cpuLen)} } cpuBytes, nextOffset, err := getSlice(buf, currentOffset, int(cpuLen)) if err != nil { return len(buf), fmt.Errorf("magna: HINFO error reading CPU data: %w", err) } currentOffset = nextOffset hinfo.CPU = string(cpuBytes) osLen, nextOffset, err := getU8(buf, currentOffset) if err != nil { if currentOffset == endOffset { return len(buf), &MagnaError{Message: "magna: HINFO missing OS string"} } return len(buf), fmt.Errorf("magna: HINFO error reading OS length: %w", err) } currentOffset = nextOffset if currentOffset+int(osLen) > endOffset { return len(buf), &BufferOverflowError{Length: len(buf), Offset: currentOffset + int(osLen)} } osBytes, nextOffset, err := getSlice(buf, currentOffset, int(osLen)) if err != nil { return len(buf), fmt.Errorf("magna: HINFO error reading OS data: %w", err) } currentOffset = nextOffset hinfo.OS = string(osBytes) if currentOffset != endOffset { return len(buf), &MagnaError{Message: fmt.Sprintf("magna: HINFO RDATA length mismatch, expected end at %d, ended at %d", endOffset, currentOffset)} } return currentOffset, nil } func (hinfo *HINFO) Encode(bytes []byte, offsets *map[string]uint16) []byte { // XXX: should probally return an error if len(hinfo.CPU) > 255 { hinfo.CPU = hinfo.CPU[:255] } if len(hinfo.OS) > 255 { hinfo.OS = hinfo.OS[:255] } bytes = append(bytes, byte(len(hinfo.CPU))) bytes = append(bytes, []byte(hinfo.CPU)...) bytes = append(bytes, byte(len(hinfo.OS))) bytes = append(bytes, []byte(hinfo.OS)...) return bytes } func (hinfo HINFO) String() string { return fmt.Sprintf("%q %q", hinfo.CPU, hinfo.OS) } func (minfo *MINFO) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error minfo.RMailBx, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } minfo.EMailBx, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (minfo *MINFO) Encode(bytes []byte, offsets *map[string]uint16) []byte { bytes = encode_domain(bytes, minfo.RMailBx, offsets) bytes = encode_domain(bytes, minfo.EMailBx, offsets) return bytes } func (minfo MINFO) String() string { return minfo.RMailBx + " " + minfo.EMailBx } func (mx *MX) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error mx.Preference, offset, err = getU16(buf, offset) if err != nil { return offset, err } mx.Exchange, offset, err = decode_domain(buf, offset) if err != nil { return offset, err } return offset, err } func (mx *MX) Encode(bytes []byte, offsets *map[string]uint16) []byte { bytes = binary.BigEndian.AppendUint16(bytes, mx.Preference) bytes = encode_domain(bytes, mx.Exchange, offsets) return bytes } func (mx MX) String() string { return fmt.Sprintf("%d %s", mx.Preference, mx.Exchange) } func (txt *TXT) Decode(buf []byte, offset int, rdlength int) (int, error) { txt.TxtData = make([]string, 0, 1) endOffset := offset + rdlength if endOffset > len(buf) { return len(buf), &BufferOverflowError{Length: len(buf), Offset: endOffset} } currentOffset := offset for currentOffset < endOffset { strLen, nextOffsetAfterLen, err := getU8(buf, currentOffset) if err != nil { return len(buf), fmt.Errorf("magna: error reading TXT string length byte: %w", err) } nextOffsetAfterData := nextOffsetAfterLen + int(strLen) if nextOffsetAfterData > endOffset { return len(buf), &MagnaError{ Message: fmt.Sprintf("magna: TXT string segment length %d at offset %d exceeds RDLENGTH boundary %d", strLen, nextOffsetAfterLen, endOffset), } } strBytes, actualNextOffsetAfterData, err := getSlice(buf, nextOffsetAfterLen, int(strLen)) if err != nil { return len(buf), fmt.Errorf("magna: error reading TXT string data: %w", err) } txt.TxtData = append(txt.TxtData, string(strBytes)) currentOffset = actualNextOffsetAfterData } if currentOffset != endOffset { return len(buf), &MagnaError{ Message: fmt.Sprintf("magna: TXT RDATA parsing finished at offset %d, but expected end at %d based on RDLENGTH", currentOffset, endOffset), } } return currentOffset, nil } func (txt *TXT) Encode(bytes []byte, offsets *map[string]uint16) []byte { for _, s := range txt.TxtData { if len(s) > 255 { // XXX: should return probably an error s = s[:255] } bytes = append(bytes, byte(len(s))) bytes = append(bytes, []byte(s)...) } return bytes } func (txt TXT) String() string { quoted := make([]string, len(txt.TxtData)) for i, s := range txt.TxtData { quoted[i] = fmt.Sprintf("%q", s) } return strings.Join(quoted, " ") } func (r *Reserved) Decode(buf []byte, offset int, rdlength int) (int, error) { var err error r.Bytes, offset, err = getSlice(buf, offset, int(rdlength)) if err != nil { return offset, err } return offset, err } func (r *Reserved) Encode(bytes []byte, offsets *map[string]uint16) []byte { return append(bytes, r.Bytes...) } func (r Reserved) String() string { return string(r.Bytes) } // Decode decodes a resource record from buf at the offset. func (r *ResourceRecord) Decode(buf []byte, offset int) (int, error) { name, offset, err := decode_domain(buf, offset) if err != nil { return offset, err } r.Name = name rtype, offset, err := getU16(buf, offset) if err != nil { return offset, err } r.RType = DNSType(rtype) rclass, offset, err := getU16(buf, offset) if err != nil { return offset, err } r.RClass = DNSClass(rclass) r.TTL, offset, err = getU32(buf, offset) if err != nil { return offset, err } r.RDLength, offset, err = getU16(buf, offset) if err != nil { return offset, err } switch r.RType { case 1: r.RData = &A{} case 2: r.RData = &NS{} case 3: r.RData = &MD{} case 4: r.RData = &MF{} case 5: r.RData = &CNAME{} case 6: r.RData = &SOA{} case 7: r.RData = &MB{} case 8: r.RData = &MG{} case 9: r.RData = &MR{} case 10: r.RData = &NULL{} case 11: r.RData = &WKS{} case 12: r.RData = &PTR{} case 13: r.RData = &HINFO{} case 14: r.RData = &MINFO{} case 15: r.RData = &MX{} case 16: r.RData = &TXT{} default: r.RData = &Reserved{} } if r.RData != nil { offset, err = r.RData.Decode(buf, offset, int(r.RDLength)) if err != nil { return offset, err } } return offset, nil } // Encode encdoes a resource record and returns the input bytes appened. func (r *ResourceRecord) Encode(bytes []byte, offsets *map[string]uint16) []byte { bytes = encode_domain(bytes, r.Name, offsets) bytes = binary.BigEndian.AppendUint16(bytes, uint16(r.RType)) bytes = binary.BigEndian.AppendUint16(bytes, uint16(r.RClass)) bytes = binary.BigEndian.AppendUint32(bytes, r.TTL) rdata_start := len(bytes) bytes = binary.BigEndian.AppendUint16(bytes, 0) bytes = r.RData.Encode(bytes, offsets) rdata_length := uint16(len(bytes) - rdata_start - 2) binary.BigEndian.PutUint16(bytes[rdata_start:rdata_start+2], rdata_length) return bytes }