1// Copyright 2017 Michal Witkowski. All Rights Reserved.
2// See LICENSE for licensing terms.
3
4package grpc_opentracing
5
6import (
7	"encoding/base64"
8	"fmt"
9	"strings"
10
11	"google.golang.org/grpc/metadata"
12)
13
14const (
15	binHdrSuffix = "-bin"
16)
17
18// metadataTextMap extends a metadata.MD to be an opentracing textmap
19type metadataTextMap metadata.MD
20
21// Set is a opentracing.TextMapReader interface that extracts values.
22func (m metadataTextMap) Set(key, val string) {
23	// gRPC allows for complex binary values to be written.
24	encodedKey, encodedVal := encodeKeyValue(key, val)
25	// The metadata object is a multimap, and previous values may exist, but for opentracing headers, we do not append
26	// we just override.
27	m[encodedKey] = []string{encodedVal}
28}
29
30// ForeachKey is a opentracing.TextMapReader interface that extracts values.
31func (m metadataTextMap) ForeachKey(callback func(key, val string) error) error {
32	for k, vv := range m {
33		for _, v := range vv {
34			if decodedKey, decodedVal, err := metadata.DecodeKeyValue(k, v); err == nil {
35				if err = callback(decodedKey, decodedVal); err != nil {
36					return err
37				}
38			} else {
39				return fmt.Errorf("failed decoding opentracing from gRPC metadata: %v", err)
40			}
41		}
42	}
43	return nil
44}
45
46// encodeKeyValue encodes key and value qualified for transmission via gRPC.
47// note: copy pasted from private values of grpc.metadata
48func encodeKeyValue(k, v string) (string, string) {
49	k = strings.ToLower(k)
50	if strings.HasSuffix(k, binHdrSuffix) {
51		val := base64.StdEncoding.EncodeToString([]byte(v))
52		v = string(val)
53	}
54	return k, v
55}
56