github.com/pion/dtls/v2@v2.2.12/pkg/protocol/extension/alpn.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  package extension
     5  
     6  import (
     7  	"golang.org/x/crypto/cryptobyte"
     8  )
     9  
    10  // ALPN is a TLS extension for application-layer protocol negotiation within
    11  // the TLS handshake.
    12  //
    13  // https://tools.ietf.org/html/rfc7301
    14  type ALPN struct {
    15  	ProtocolNameList []string
    16  }
    17  
    18  // TypeValue returns the extension TypeValue
    19  func (a ALPN) TypeValue() TypeValue {
    20  	return ALPNTypeValue
    21  }
    22  
    23  // Marshal encodes the extension
    24  func (a *ALPN) Marshal() ([]byte, error) {
    25  	var b cryptobyte.Builder
    26  	b.AddUint16(uint16(a.TypeValue()))
    27  	b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
    28  		b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
    29  			for _, proto := range a.ProtocolNameList {
    30  				p := proto // Satisfy range scope lint
    31  				b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
    32  					b.AddBytes([]byte(p))
    33  				})
    34  			}
    35  		})
    36  	})
    37  	return b.Bytes()
    38  }
    39  
    40  // Unmarshal populates the extension from encoded data
    41  func (a *ALPN) Unmarshal(data []byte) error {
    42  	val := cryptobyte.String(data)
    43  
    44  	var extension uint16
    45  	val.ReadUint16(&extension)
    46  	if TypeValue(extension) != a.TypeValue() {
    47  		return errInvalidExtensionType
    48  	}
    49  
    50  	var extData cryptobyte.String
    51  	val.ReadUint16LengthPrefixed(&extData)
    52  
    53  	var protoList cryptobyte.String
    54  	if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() {
    55  		return ErrALPNInvalidFormat
    56  	}
    57  	for !protoList.Empty() {
    58  		var proto cryptobyte.String
    59  		if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() {
    60  			return ErrALPNInvalidFormat
    61  		}
    62  		a.ProtocolNameList = append(a.ProtocolNameList, string(proto))
    63  	}
    64  	return nil
    65  }
    66  
    67  // ALPNProtocolSelection negotiates a shared protocol according to #3.2 of rfc7301
    68  func ALPNProtocolSelection(supportedProtocols, peerSupportedProtocols []string) (string, error) {
    69  	if len(supportedProtocols) == 0 || len(peerSupportedProtocols) == 0 {
    70  		return "", nil
    71  	}
    72  	for _, s := range supportedProtocols {
    73  		for _, c := range peerSupportedProtocols {
    74  			if s == c {
    75  				return s, nil
    76  			}
    77  		}
    78  	}
    79  	return "", errALPNNoAppProto
    80  }