Merge "discovery: support large attributes in mdns"
diff --git a/lib/discovery/advertise.go b/lib/discovery/advertise.go
index 3930f63..5abdbe4 100644
--- a/lib/discovery/advertise.go
+++ b/lib/discovery/advertise.go
@@ -25,15 +25,13 @@
 	if len(service.InterfaceName) == 0 {
 		return verror.New(errNoInterfaceName, ctx)
 	}
-	if !IsAttributePackable(service.Attrs) {
-		return verror.New(errNotPackableAttributes, ctx)
-	}
 	if len(service.Addrs) == 0 {
 		return verror.New(errNoAddresses, ctx)
 	}
-	if !IsAddressPackable(service.Addrs) {
-		return verror.New(errNotPackableAddresses, ctx)
+	if err := validateAttributes(service.Attrs); err != nil {
+		return err
 	}
+
 	if len(service.InstanceUuid) == 0 {
 		service.InstanceUuid = NewInstanceUUID()
 	}
diff --git a/lib/discovery/discovery.go b/lib/discovery/discovery.go
index 4f2210b..e260074 100644
--- a/lib/discovery/discovery.go
+++ b/lib/discovery/discovery.go
@@ -36,7 +36,7 @@
 	Lost bool
 }
 
-type EncryptionAlgorithm byte
+type EncryptionAlgorithm int
 type EncryptionKey []byte
 
 const (
diff --git a/lib/discovery/encoding.go b/lib/discovery/encoding.go
index 0f27651..d58b315 100644
--- a/lib/discovery/encoding.go
+++ b/lib/discovery/encoding.go
@@ -6,91 +6,102 @@
 
 import (
 	"bytes"
-	"fmt"
+	"encoding/binary"
+	"errors"
+	"io"
 	"strings"
 
 	"v.io/v23/discovery"
 )
 
-// TODO(jhahn): Figure out how to overcome the size limit.
-
-// isAttributePackage returns false if the provided attributes cannot be serialized safely.
-func IsAttributePackable(attrs discovery.Attributes) bool {
-	for k, v := range attrs {
-		if strings.HasPrefix(k, "_") || strings.Contains(k, "=") {
-			return false
+// validateAttributes returns an error if the attributes are not suitable for advertising.
+func validateAttributes(attrs discovery.Attributes) error {
+	for k, _ := range attrs {
+		if len(k) == 0 {
+			return errors.New("empty key")
 		}
-		if len(k)+len(v) > 254 {
-			return false
+		if strings.HasPrefix(k, "_") {
+			return errors.New("key starts with '_'")
+		}
+		for _, c := range k {
+			if c < 0x20 || c > 0x7e {
+				return errors.New("key is not printable US-ASCII")
+			}
+			if c == '=' {
+				return errors.New("key includes '='")
+			}
 		}
 	}
-	return true
+	return nil
 }
 
-// IsAddressPackable returns false if any address is larger than 250 bytes.
-//
-// go-mdns-sd package limits the size of each txt record to 255 bytes. We use
-// 5 bytes for tag, so we limit the address to 250 bytes.
-func IsAddressPackable(addrs []string) bool {
-	for _, a := range addrs {
-		if len(a) > 250 {
-			return false
-		}
-	}
-	return true
-}
-
-// PackAddresses packs addresses into a byte slice. If any address exceeds
-// 255 bytes, it will panic.
+// PackAddresses packs addresses into a byte slice.
 func PackAddresses(addrs []string) []byte {
-	var b bytes.Buffer
+	var buf bytes.Buffer
 	for _, a := range addrs {
-		n := len(a)
-		if n > 255 {
-			panic(fmt.Sprintf("too large address %d: %s", n, a))
-		}
-		b.WriteByte(byte(n))
-		b.WriteString(a)
+		writeInt(&buf, len(a))
+		buf.WriteString(a)
 	}
-	return b.Bytes()
+	return buf.Bytes()
 }
 
 // UnpackAddresses unpacks addresses from a byte slice.
-func UnpackAddresses(data []byte) []string {
-	addrs := []string{}
-	for off := 0; off < len(data); {
-		n := int(data[off])
-		off++
-		addrs = append(addrs, string(data[off:off+n]))
-		off += n
-	}
-	return addrs
-}
-
-// PackEncryptionKeys packs keys into a byte slice.
-func PackEncryptionKeys(algo EncryptionAlgorithm, keys []EncryptionKey) []byte {
-	var b bytes.Buffer
-	b.WriteByte(byte(algo))
-	for _, k := range keys {
-		n := len(k)
-		if n > 255 {
-			panic(fmt.Sprintf("too large key %d", n))
+func UnpackAddresses(data []byte) ([]string, error) {
+	var addrs []string
+	for r := bytes.NewBuffer(data); r.Len() > 0; {
+		n, err := readInt(r)
+		if err != nil {
+			return nil, err
 		}
-		b.WriteByte(byte(n))
-		b.Write(k)
+		b := r.Next(n)
+		if len(b) != n {
+			return nil, errors.New("invalid addresses")
+		}
+		addrs = append(addrs, string(b))
 	}
-	return b.Bytes()
+	return addrs, nil
 }
 
-// UnpackEncryptionKeys unpacks keys from a byte slice.
-func UnpackEncryptionKeys(data []byte) (EncryptionAlgorithm, []EncryptionKey) {
-	algo := EncryptionAlgorithm(data[0])
-	keys := []EncryptionKey{}
-	for off := 1; off < len(data); {
-		n := int(data[off])
-		off++
-		keys = append(keys, EncryptionKey(data[off:off+n]))
-		off += n
+// PackEncryptionKeys packs encryption algorithm and keys into a byte slice.
+func PackEncryptionKeys(algo EncryptionAlgorithm, keys []EncryptionKey) []byte {
+	var buf bytes.Buffer
+	writeInt(&buf, int(algo))
+	for _, k := range keys {
+		writeInt(&buf, len(k))
+		buf.Write(k)
 	}
-	return algo, keys
+	return buf.Bytes()
+}
+
+// UnpackEncryptionKeys unpacks encryption algorithm and keys from a byte slice.
+func UnpackEncryptionKeys(data []byte) (EncryptionAlgorithm, []EncryptionKey, error) {
+	buf := bytes.NewBuffer(data)
+	algo, err := readInt(buf)
+	if err != nil {
+		return NoEncryption, nil, err
+	}
+	var keys []EncryptionKey
+	for buf.Len() > 0 {
+		n, err := readInt(buf)
+		if err != nil {
+			return NoEncryption, nil, err
+		}
+		v := buf.Next(n)
+		if len(v) != n {
+			return NoEncryption, nil, errors.New("invalid encryption keys")
+		}
+		keys = append(keys, EncryptionKey(v))
+	}
+	return EncryptionAlgorithm(algo), keys, nil
+}
+
+func writeInt(w io.Writer, x int) {
+	var b [binary.MaxVarintLen64]byte
+	n := binary.PutUvarint(b[:], uint64(x))
+	w.Write(b[0:n])
+}
+
+func readInt(r io.ByteReader) (int, error) {
+	x, err := binary.ReadUvarint(r)
+	return int(x), err
 }
diff --git a/lib/discovery/encoding_test.go b/lib/discovery/encoding_test.go
index ca74b57..488bc77 100644
--- a/lib/discovery/encoding_test.go
+++ b/lib/discovery/encoding_test.go
@@ -6,41 +6,31 @@
 
 import (
 	"reflect"
-	"strings"
 	"testing"
 
 	"v.io/v23/discovery"
 )
 
-func TestAttributePackable(t *testing.T) {
-	tests := []struct {
-		addrs discovery.Attributes
-		want  bool
-	}{
-		{discovery.Attributes{"k": "v"}, true},
-		{discovery.Attributes{"_k": "v"}, false},
-		{discovery.Attributes{"k=": "v"}, false},
-		{discovery.Attributes{strings.Repeat("k", 100): strings.Repeat("v", 154)}, true},
-		{discovery.Attributes{strings.Repeat("k", 100): strings.Repeat("v", 155)}, false},
+func TestValidateAttributes(t *testing.T) {
+	valids := []discovery.Attributes{
+		discovery.Attributes{"key": "v"},
+		discovery.Attributes{"k_e.y": "v"},
+		discovery.Attributes{"k!": "v"},
 	}
-	for i, test := range tests {
-		if got := IsAttributePackable(test.addrs); got != test.want {
-			t.Errorf("[%d]: packable %v, but want %v", i, got, test.want)
+	for i, attrs := range valids {
+		if err := validateAttributes(attrs); err != nil {
+			t.Errorf("[%d]: valid attributes got error: %v", i, err)
 		}
 	}
-}
 
-func TestAddressPackable(t *testing.T) {
-	tests := []struct {
-		addrs []string
-		want  bool
-	}{
-		{[]string{strings.Repeat("a", 250)}, true},
-		{[]string{strings.Repeat("a", 10), strings.Repeat("a", 251)}, false},
+	invalids := []discovery.Attributes{
+		discovery.Attributes{"_key": "v"},
+		discovery.Attributes{"k=ey": "v"},
+		discovery.Attributes{"key\n": "v"},
 	}
-	for i, test := range tests {
-		if got := IsAddressPackable(test.addrs); got != test.want {
-			t.Errorf("[%d]: packable %v, but want %v", i, got, test.want)
+	for i, attrs := range invalids {
+		if err := validateAttributes(attrs); err == nil {
+			t.Errorf("[%d]: invalid attributes didn't get error", i)
 		}
 	}
 }
@@ -49,12 +39,16 @@
 	tests := [][]string{
 		[]string{"a12345"},
 		[]string{"a1234", "b5678", "c9012"},
-		[]string{},
+		nil,
 	}
 
 	for _, test := range tests {
 		pack := PackAddresses(test)
-		unpack := UnpackAddresses(pack)
+		unpack, err := UnpackAddresses(pack)
+		if err != nil {
+			t.Errorf("unpacked error: %v", err)
+			continue
+		}
 		if !reflect.DeepEqual(test, unpack) {
 			t.Errorf("unpacked to %v, but want %v", unpack, test)
 		}
@@ -68,12 +62,16 @@
 	}{
 		{TestEncryption, []EncryptionKey{EncryptionKey("0123456789")}},
 		{IbeEncryption, []EncryptionKey{EncryptionKey("012345"), EncryptionKey("123456"), EncryptionKey("234567")}},
-		{NoEncryption, []EncryptionKey{}},
+		{NoEncryption, nil},
 	}
 
 	for _, test := range tests {
 		pack := PackEncryptionKeys(test.algo, test.keys)
-		algo, keys := UnpackEncryptionKeys(pack)
+		algo, keys, err := UnpackEncryptionKeys(pack)
+		if err != nil {
+			t.Errorf("unpacked error: %v", err)
+			continue
+		}
 		if algo != test.algo || !reflect.DeepEqual(keys, test.keys) {
 			t.Errorf("unpacked to (%d, %v), but want (%d, %v)", algo, keys, test.algo, test.keys)
 		}
diff --git a/lib/discovery/plugins/ble/advertisement.go b/lib/discovery/plugins/ble/advertisement.go
index b64ade1..7a8a3f9 100644
--- a/lib/discovery/plugins/ble/advertisement.go
+++ b/lib/discovery/plugins/ble/advertisement.go
@@ -25,6 +25,7 @@
 	// This uuids are v5 uuid generated out of band.  These constants need
 	// to be accessible in all the languages that have a ble implementation
 	instanceUUID      = "12db9a9c-1c7c-5560-bc6b-73a115c93413" // NewAttributeUUID("_instanceuuid")
+	instanceNameUUID  = "ffbdcff3-e56f-58f0-8c1a-e416c39aac0d" // NewAttributeUUID("_instancename")
 	interfaceNameUUID = "b2cadfd4-d003-576c-acad-58b8e3a9cbc8" // NewAttributeUUID("_interfacename")
 	addrsUUID         = "ad2566b7-59d8-50ae-8885-222f43f65fdc" // NewAttributeUUID("_addrs")
 	encryptionUUID    = "6286d80a-adaa-519a-8a06-281a4645a607" // NewAttributeUUID("_encryption")
@@ -34,8 +35,15 @@
 	attrs := map[string][]byte{
 		instanceUUID:      adv.InstanceUuid,
 		interfaceNameUUID: []byte(adv.InterfaceName),
-		addrsUUID:         discovery.PackAddresses(adv.Addrs),
-		encryptionUUID:    discovery.PackEncryptionKeys(adv.EncryptionAlgorithm, adv.EncryptionKeys),
+	}
+	if len(adv.InstanceName) > 0 {
+		attrs[instanceNameUUID] = []byte(adv.InstanceName)
+	}
+	if len(adv.Addrs) > 0 {
+		attrs[addrsUUID] = discovery.PackAddresses(adv.Addrs)
+	}
+	if adv.EncryptionAlgorithm != discovery.NoEncryption {
+		attrs[encryptionUUID] = discovery.PackEncryptionKeys(adv.EncryptionAlgorithm, adv.EncryptionKeys)
 	}
 
 	for k, v := range adv.Attrs {
@@ -58,16 +66,23 @@
 		ServiceUuid: a.serviceUUID,
 	}
 
+	var err error
 	for k, v := range a.attrs {
 		switch k {
 		case instanceUUID:
 			adv.InstanceUuid = v
+		case instanceNameUUID:
+			adv.InstanceName = string(v)
 		case interfaceNameUUID:
 			adv.InterfaceName = string(v)
 		case addrsUUID:
-			adv.Addrs = discovery.UnpackAddresses(v)
+			if adv.Addrs, err = discovery.UnpackAddresses(v); err != nil {
+				return nil, err
+			}
 		case encryptionUUID:
-			adv.EncryptionAlgorithm, adv.EncryptionKeys = discovery.UnpackEncryptionKeys(v)
+			if adv.EncryptionAlgorithm, adv.EncryptionKeys, err = discovery.UnpackEncryptionKeys(v); err != nil {
+				return nil, err
+			}
 		default:
 			parts := strings.SplitN(string(v), "=", 2)
 			if len(parts) != 2 {
diff --git a/lib/discovery/plugins/ble/advertisement_test.go b/lib/discovery/plugins/ble/advertisement_test.go
index 3fe95db..7525be1 100644
--- a/lib/discovery/plugins/ble/advertisement_test.go
+++ b/lib/discovery/plugins/ble/advertisement_test.go
@@ -19,6 +19,7 @@
 	v23Adv := discovery.Advertisement{
 		Service: vdiscovery.Service{
 			InstanceUuid: []byte(discovery.NewInstanceUUID()),
+			InstanceName: "service",
 			Attrs: vdiscovery.Attributes{
 				"key1": "value1",
 				"key2": "value2",
@@ -27,7 +28,7 @@
 		},
 		ServiceUuid:         uuid.NewUUID(),
 		EncryptionAlgorithm: discovery.TestEncryption,
-		EncryptionKeys:      []discovery.EncryptionKey{discovery.EncryptionKey("k1"), discovery.EncryptionKey("k2")},
+		EncryptionKeys:      []discovery.EncryptionKey{discovery.EncryptionKey("k")},
 	}
 
 	adv := newAdvertisment(v23Adv)
diff --git a/lib/discovery/plugins/mdns/encoding.go b/lib/discovery/plugins/mdns/encoding.go
new file mode 100644
index 0000000..2610dae
--- /dev/null
+++ b/lib/discovery/plugins/mdns/encoding.go
@@ -0,0 +1,105 @@
+// 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"
+)
+
+const (
+	// Limit the maximum large txt records to the maximum total txt records size.
+	maxLargeTxtRecordLen = maxTotalTxtRecordsLen
+)
+
+var (
+	// The key of encoded large txt records is "_x<i><j>", where 'i' and 'j' will
+	// be one digit numbers since we limit the large txt record to 1300 bytes.
+	reLargeTxtRecord = regexp.MustCompile("^" + attrLargeTxtPrefix + "[0-9][0-9]=")
+
+	errInvalidLargeTxtRecord = errors.New("invalid large txt record")
+)
+
+// encodeInstanceUuid encodes the given instance uuid to a valid host name by using
+// "Extended Hex Alphabet" defined in RFC 4648. This removes any padding characters.
+func encodeInstanceUuid(instanceUuid []byte) string {
+	return strings.TrimRight(base32.HexEncoding.EncodeToString(instanceUuid), "=")
+}
+
+// decodeInstanceUuid decodes the given host name to an instance uuid.
+func decodeInstanceUuid(hostname string) ([]byte, error) {
+	// Add padding characters if needed.
+	if p := len(hostname) % 8; p > 0 {
+		hostname += strings.Repeat("=", 8-p)
+	}
+	return base32.HexEncoding.DecodeString(hostname)
+}
+
+// maybeSplitLargeTXT slices txt records larger than 255 bytes into multiple txt records.
+func maybeSplitLargeTXT(txt []string) ([]string, error) {
+	splitted := make([]string, 0, len(txt))
+	xno := 0
+	for _, v := range txt {
+		switch n := len(v); {
+		case n > maxLargeTxtRecordLen:
+			return nil, errMaxTxtRecordLenExceeded
+		case n > maxTxtRecordLen:
+			var buf [maxTxtRecordLen]byte
+			copy(buf[:], attrLargeTxtPrefix)
+			for i, off := 0, 0; off < n; i++ {
+				buf[2] = byte(xno + '0')
+				buf[3] = byte(i + '0')
+				buf[4] = '='
+				c := copy(buf[5:], v[off:])
+				splitted = append(splitted, string(buf[:5+c]))
+				off += c
+			}
+			xno++
+		default:
+			splitted = append(splitted, v)
+		}
+	}
+	return splitted, nil
+}
+
+// maybeJoinLargeTXT joins the splitted large txt records.
+func maybeJoinLargeTXT(txt []string) ([]string, error) {
+	joined, splitted := make([]string, 0, len(txt)), make([]string, 0)
+	for _, v := range txt {
+		switch {
+		case strings.HasPrefix(v, attrLargeTxtPrefix):
+			if !reLargeTxtRecord.MatchString(v) {
+				return nil, errInvalidLargeTxtRecord
+			}
+			splitted = append(splitted, v)
+		default:
+			joined = append(joined, v)
+		}
+	}
+	if len(splitted) == 0 {
+		return joined, nil
+	}
+
+	sort.Strings(splitted)
+
+	var buf [maxLargeTxtRecordLen]byte
+	xno, off := 0, 0
+	for _, v := range splitted {
+		i := int(v[2] - '0')
+		if i > xno {
+			// A new large txt record started.
+			joined = append(joined, string(buf[:off]))
+			xno++
+			off = 0
+		}
+		c := copy(buf[off:], v[5:])
+		off += c
+	}
+	joined = append(joined, string(buf[:off]))
+	return joined, nil
+}
diff --git a/lib/discovery/plugins/mdns/encoding_test.go b/lib/discovery/plugins/mdns/encoding_test.go
new file mode 100644
index 0000000..cbfa213
--- /dev/null
+++ b/lib/discovery/plugins/mdns/encoding_test.go
@@ -0,0 +1,87 @@
+// 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 (
+	"crypto/rand"
+	"encoding/base64"
+	"reflect"
+	"sort"
+	"testing"
+)
+
+func TestEncodeInstanceUuid(t *testing.T) {
+	tests := [][]byte{
+		randInstanceUuid(1),
+		randInstanceUuid(10),
+		randInstanceUuid(16),
+		randInstanceUuid(32),
+	}
+
+	for i, test := range tests {
+		encoded := encodeInstanceUuid(test)
+		instanceUuid, err := decodeInstanceUuid(encoded)
+		if err != nil {
+			t.Errorf("[%d]: decodeInstanceUuid failed: %v", i, err)
+			continue
+		}
+		if !reflect.DeepEqual(instanceUuid, test) {
+			t.Errorf("[%d]: decoded to %v, but want %v", i, instanceUuid, test)
+		}
+	}
+}
+
+func randInstanceUuid(n int) []byte {
+	b := make([]byte, n)
+	_, err := rand.Read(b)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func TestSplitLargeTxt(t *testing.T) {
+	tests := [][]string{
+		[]string{randTxt(maxTxtRecordLen / 2)},
+		[]string{randTxt(maxTxtRecordLen / 2), randTxt(maxTxtRecordLen / 3)},
+		[]string{randTxt(maxTxtRecordLen * 2)},
+		[]string{randTxt(maxTxtRecordLen * 2), randTxt(maxTxtRecordLen * 3)},
+		[]string{randTxt(maxTxtRecordLen / 2), randTxt(maxTxtRecordLen * 3), randTxt(maxTxtRecordLen * 2), randTxt(maxTxtRecordLen / 3)},
+	}
+
+	for i, test := range tests {
+		splitted, err := maybeSplitLargeTXT(test)
+		if err != nil {
+			t.Errorf("[%d]: encodeLargeTxt failed: %v", i, err)
+			continue
+		}
+		for _, v := range splitted {
+			if len(v) > maxTxtRecordLen {
+				t.Errorf("[%d]: too large encoded txt %d - %v", i, len(v), v)
+			}
+		}
+
+		txt, err := maybeJoinLargeTXT(splitted)
+		if err != nil {
+			t.Errorf("[%d]: decodeLargeTxt failed: %v", i, err)
+			continue
+		}
+
+		sort.Strings(txt)
+		sort.Strings(test)
+		if !reflect.DeepEqual(txt, test) {
+			t.Errorf("[%d]: decoded to %#v, but want %#v", i, txt, test)
+		}
+	}
+}
+
+func randTxt(n int) string {
+	b := make([]byte, int((n*3+3)/4))
+	_, err := rand.Read(b)
+	if err != nil {
+		panic(err)
+	}
+	return base64.RawStdEncoding.EncodeToString(b)[:n]
+}
diff --git a/lib/discovery/plugins/mdns/mdns.go b/lib/discovery/plugins/mdns/mdns.go
index b036716..5056621 100644
--- a/lib/discovery/plugins/mdns/mdns.go
+++ b/lib/discovery/plugins/mdns/mdns.go
@@ -15,9 +15,9 @@
 package mdns
 
 import (
-	"encoding/hex"
+	"bytes"
+	"errors"
 	"fmt"
-	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -35,14 +35,25 @@
 	v23ServiceName    = "v23"
 	serviceNameSuffix = "._sub._" + v23ServiceName
 
-	// The attribute names should not exceed 4 bytes due to the txt record
-	// size limit.
-	attrServiceUuid = "_srv"
-	attrInterface   = "_itf"
-	attrAddr        = "_adr"
-	// TODO(jhahn): Remove attrEncryptionAlgorithm.
-	attrEncryptionAlgorithm = "_xxx"
-	attrEncryptionKeys      = "_key"
+	// Use short attribute names due to the txt record size limit.
+	attrName       = "_n"
+	attrInterface  = "_i"
+	attrAddrs      = "_a"
+	attrEncryption = "_e"
+
+	// The prefix for attribute names for encoded large txt records.
+	attrLargeTxtPrefix = "_x"
+
+	// RFC 6763 limits each DNS txt record to 255 bytes and recommends to not have
+	// the cumulative size be larger than 1300 bytes.
+	//
+	// TODO(jhahn): Figure out how to overcome this limit.
+	maxTxtRecordLen       = 255
+	maxTotalTxtRecordsLen = 1300
+)
+
+var (
+	errMaxTxtRecordLenExceeded = errors.New("max txt record size exceeded")
 )
 
 type plugin struct {
@@ -64,8 +75,8 @@
 	serviceName := ad.ServiceUuid.String() + serviceNameSuffix
 	// We use the instance uuid as the host name so that we can get the instance uuid
 	// from the lost service instance, which has no txt records at all.
-	hostName := hex.EncodeToString(ad.InstanceUuid)
-	txt, err := createTXTRecords(&ad)
+	hostName := encodeInstanceUuid(ad.InstanceUuid)
+	txt, err := createTxtRecords(&ad)
 	if err != nil {
 		return err
 	}
@@ -134,7 +145,7 @@
 			case <-ctx.Done():
 				return
 			}
-			ad, err := decodeAdvertisement(service)
+			ad, err := createAdvertisement(service)
 			if err != nil {
 				ctx.Error(err)
 				continue
@@ -149,65 +160,89 @@
 	return nil
 }
 
-func createTXTRecords(ad *ldiscovery.Advertisement) ([]string, error) {
-	// Prepare a TXT record with attributes and addresses to announce.
-	//
-	// TODO(jhahn): Currently, the packet size is limited to 2000 bytes in
-	// go-mdns-sd package. Think about how to handle a large number of TXT
-	// records.
-	txt := make([]string, 0, len(ad.Attrs)+4)
-	txt = append(txt, fmt.Sprintf("%s=%s", attrServiceUuid, ad.ServiceUuid))
-	txt = append(txt, fmt.Sprintf("%s=%s", attrInterface, ad.InterfaceName))
+func createTxtRecords(ad *ldiscovery.Advertisement) ([]string, error) {
+	// Prepare a txt record with attributes and addresses to announce.
+	txt := appendTxtRecord(nil, attrInterface, ad.InterfaceName)
+	if len(ad.InstanceName) > 0 {
+		txt = appendTxtRecord(txt, attrName, ad.InstanceName)
+	}
+	if len(ad.Addrs) > 0 {
+		addrs := ldiscovery.PackAddresses(ad.Addrs)
+		txt = appendTxtRecord(txt, attrAddrs, string(addrs))
+	}
+	if ad.EncryptionAlgorithm != ldiscovery.NoEncryption {
+		enc := ldiscovery.PackEncryptionKeys(ad.EncryptionAlgorithm, ad.EncryptionKeys)
+		txt = appendTxtRecord(txt, attrEncryption, string(enc))
+	}
 	for k, v := range ad.Attrs {
-		txt = append(txt, fmt.Sprintf("%s=%s", k, v))
+		txt = appendTxtRecord(txt, k, v)
 	}
-	for _, a := range ad.Addrs {
-		txt = append(txt, fmt.Sprintf("%s=%s", attrAddr, a))
+	txt, err := maybeSplitLargeTXT(txt)
+	if err != nil {
+		return nil, err
 	}
-	txt = append(txt, fmt.Sprintf("%s=%d", attrEncryptionAlgorithm, ad.EncryptionAlgorithm))
-	for _, k := range ad.EncryptionKeys {
-		txt = append(txt, fmt.Sprintf("%s=%s", attrEncryptionKeys, k))
+	n := 0
+	for _, v := range txt {
+		n += len(v)
+		if n > maxTotalTxtRecordsLen {
+			return nil, errMaxTxtRecordLenExceeded
+		}
 	}
 	return txt, nil
 }
 
-func decodeAdvertisement(service mdns.ServiceInstance) (ldiscovery.Advertisement, error) {
+func appendTxtRecord(txt []string, k, v string) []string {
+	var buf bytes.Buffer
+	buf.WriteString(k)
+	buf.WriteByte('=')
+	buf.WriteString(v)
+	kv := buf.String()
+	txt = append(txt, kv)
+	return txt
+}
+
+func createAdvertisement(service mdns.ServiceInstance) (ldiscovery.Advertisement, error) {
 	// Note that service.Name starts with a host name, which is the instance uuid.
 	p := strings.SplitN(service.Name, ".", 2)
 	if len(p) < 1 {
-		return ldiscovery.Advertisement{}, fmt.Errorf("invalid host name: %s", service.Name)
+		return ldiscovery.Advertisement{}, fmt.Errorf("invalid service name: %s", service.Name)
 	}
-	instanceUuid, err := hex.DecodeString(p[0])
+	instanceUuid, err := decodeInstanceUuid(p[0])
 	if err != nil {
 		return ldiscovery.Advertisement{}, fmt.Errorf("invalid host name: %v", err)
 	}
 
-	ad := ldiscovery.Advertisement{
-		Service: discovery.Service{
-			InstanceUuid: instanceUuid,
-			Attrs:        make(discovery.Attributes),
-		},
-		Lost: len(service.SrvRRs) == 0 && len(service.TxtRRs) == 0,
+	ad := ldiscovery.Advertisement{Service: discovery.Service{InstanceUuid: instanceUuid}}
+	if len(service.SrvRRs) == 0 && len(service.TxtRRs) == 0 {
+		ad.Lost = true
+		return ad, nil
 	}
 
+	ad.Attrs = make(discovery.Attributes)
 	for _, rr := range service.TxtRRs {
-		for _, txt := range rr.Txt {
-			kv := strings.SplitN(txt, "=", 2)
-			if len(kv) != 2 {
+		txt, err := maybeJoinLargeTXT(rr.Txt)
+		if err != nil {
+			return ldiscovery.Advertisement{}, err
+		}
+
+		for _, kv := range txt {
+			p := strings.SplitN(kv, "=", 2)
+			if len(p) != 2 {
 				return ldiscovery.Advertisement{}, fmt.Errorf("invalid txt record: %s", txt)
 			}
-			switch k, v := kv[0], kv[1]; k {
-			case attrServiceUuid:
-				ad.ServiceUuid = uuid.Parse(v)
+			switch k, v := p[0], p[1]; k {
+			case attrName:
+				ad.InstanceName = v
 			case attrInterface:
 				ad.InterfaceName = v
-			case attrAddr:
-				ad.Addrs = append(ad.Addrs, v)
-			case attrEncryptionAlgorithm:
-				a, _ := strconv.Atoi(v)
-				ad.EncryptionAlgorithm = ldiscovery.EncryptionAlgorithm(a)
-			case attrEncryptionKeys:
-				ad.EncryptionKeys = append(ad.EncryptionKeys, ldiscovery.EncryptionKey(v))
+			case attrAddrs:
+				if ad.Addrs, err = ldiscovery.UnpackAddresses([]byte(v)); err != nil {
+					return ldiscovery.Advertisement{}, err
+				}
+			case attrEncryption:
+				if ad.EncryptionAlgorithm, ad.EncryptionKeys, err = ldiscovery.UnpackEncryptionKeys([]byte(v)); err != nil {
+					return ldiscovery.Advertisement{}, err
+				}
 			default:
 				ad.Attrs[k] = v
 			}
diff --git a/lib/discovery/plugins/mdns/mdns_test.go b/lib/discovery/plugins/mdns/mdns_test.go
index 5b6a7c3..27956dc 100644
--- a/lib/discovery/plugins/mdns/mdns_test.go
+++ b/lib/discovery/plugins/mdns/mdns_test.go
@@ -8,6 +8,7 @@
 	"fmt"
 	"reflect"
 	"runtime"
+	"strings"
 	"testing"
 	"time"
 
@@ -128,6 +129,7 @@
 	services := []discovery.Service{
 		{
 			InstanceUuid:  ldiscovery.NewInstanceUUID(),
+			InstanceName:  "service1",
 			InterfaceName: "v.io/x",
 			Attrs: discovery.Attributes{
 				"a": "a1234",
@@ -139,6 +141,7 @@
 		},
 		{
 			InstanceUuid:  ldiscovery.NewInstanceUUID(),
+			InstanceName:  "service2",
 			InterfaceName: "v.io/x",
 			Attrs: discovery.Attributes{
 				"a": "a5678",
@@ -150,6 +153,7 @@
 		},
 		{
 			InstanceUuid:  ldiscovery.NewInstanceUUID(),
+			InstanceName:  "service3",
 			InterfaceName: "v.io/y",
 			Attrs: discovery.Attributes{
 				"c": "c1234",
@@ -229,3 +233,40 @@
 		t.Error(err)
 	}
 }
+
+func TestLargeTxt(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	service := discovery.Service{
+		InstanceUuid:  ldiscovery.NewInstanceUUID(),
+		InstanceName:  "service2",
+		InterfaceName: strings.Repeat("i", 280),
+		Attrs: discovery.Attributes{
+			"k": strings.Repeat("v", 280),
+		},
+		Addrs: []string{
+			strings.Repeat("a1", 100),
+			strings.Repeat("a2", 100),
+		},
+	}
+
+	p1, err := newWithLoopback("m1", true)
+	if err != nil {
+		t.Fatalf("New() failed: %v", err)
+	}
+	stop, err := advertise(ctx, p1, service)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer stop()
+
+	p2, err := newWithLoopback("m2", true)
+	if err != nil {
+		t.Fatalf("New() failed: %v", err)
+	}
+
+	if err := scanAndMatch(ctx, p2, "", service); err != nil {
+		t.Error(err)
+	}
+}