| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package mdns |
| |
| import ( |
| "encoding/base32" |
| "errors" |
| "regexp" |
| "sort" |
| "strings" |
| |
| "v.io/v23/discovery" |
| ) |
| |
| const ( |
| // Limit the maximum large txt records to the maximum total txt records size. |
| maxLargeTxtRecordLen = maxTotalTxtRecordsLen |
| |
| // The index of large txt records ranges from '0' to '~' as defined in |
| // reLargeTxtRecord. |
| maxNumLargeTxtRecords = '~' - '0' + 1 |
| ) |
| |
| var ( |
| // The key of encoded large txt records is "_x<i><j>", where 'i' and 'j' are |
| // one byte indices. This should be enough with the current limit on the |
| // advertisement size and the number of attributes or attachments. |
| reLargeTxtRecord = regexp.MustCompile("^" + attrLargeTxtPrefix + "[0-~][0-~]=") |
| ) |
| |
| // encodeAdId encodes the given advertisement id to a valid host name by using |
| // "Extended Hex Alphabet" defined in RFC 4648. This removes any padding characters. |
| func encodeAdId(id *discovery.AdId) string { |
| return strings.TrimRight(base32.HexEncoding.EncodeToString(id[:]), "=") |
| } |
| |
| // decodeAdId decodes the given host name to the advertisement id. |
| func decodeAdId(hostname string, id *discovery.AdId) error { |
| // Add padding characters if needed. |
| if p := len(hostname) % 8; p > 0 { |
| hostname += strings.Repeat("=", 8-p) |
| } |
| |
| decoded, err := base32.HexEncoding.DecodeString(hostname) |
| if err != nil { |
| return err |
| } |
| if len(decoded) != len(id) { |
| return errors.New("invalid hostname") |
| } |
| copy(id[:], decoded) |
| return nil |
| } |
| |
| // maybeSplitTxtRecord slices a txt record into multiple records if it is larger than 255 bytes. |
| func maybeSplitTxtRecord(txt string, xseq int) ([]string, int) { |
| n := len(txt) |
| if n <= maxTxtRecordLen { |
| return []string{txt}, xseq |
| } |
| |
| // The overhead of a large txt record is 5 bytes - |
| // "_x<i><j>=", where 'i' and 'j' are one byte. |
| const overhead = 5 |
| splitted := make([]string, 0, (n+maxTxtRecordLen-overhead-1)/(maxTxtRecordLen-overhead)) |
| var buf [maxTxtRecordLen]byte |
| copy(buf[:], attrLargeTxtPrefix) |
| for i, off := 0, 0; off < n; i++ { |
| buf[2] = byte(xseq + '0') |
| buf[3] = byte(i + '0') |
| buf[4] = '=' |
| c := copy(buf[5:], txt[off:]) |
| splitted = append(splitted, string(buf[:5+c])) |
| off += c |
| } |
| return splitted, xseq + 1 |
| } |
| |
| // maybeJoinTxtRecords joins the splitted large txt records. |
| func maybeJoinTxtRecords(txts []string) ([]string, error) { |
| joined, splitted := make([]string, 0, len(txts)), make([]string, 0, len(txts)) |
| for _, txt := range txts { |
| switch { |
| case strings.HasPrefix(txt, attrLargeTxtPrefix): |
| if !reLargeTxtRecord.MatchString(txt) { |
| return nil, errors.New("invalid large txt record") |
| } |
| splitted = append(splitted, txt) |
| default: |
| joined = append(joined, txt) |
| } |
| } |
| if len(splitted) == 0 { |
| return joined, nil |
| } |
| |
| sort.Strings(splitted) |
| |
| var buf [maxLargeTxtRecordLen]byte |
| xseq, off := -1, 0 |
| for _, txt := range splitted { |
| i := int(txt[2] - '0') |
| if i > xseq { |
| if off > 0 { |
| // A new large txt record started. |
| joined = append(joined, string(buf[:off])) |
| } |
| xseq = i |
| off = 0 |
| } |
| c := copy(buf[off:], txt[5:]) |
| off += c |
| } |
| joined = append(joined, string(buf[:off])) |
| return joined, nil |
| } |