summaryrefslogtreecommitdiff
path: root/network/topics.go
blob: 679075c839b0d9b8c6cb8ebbab91a9e58574dd7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright (C) 2019-2024 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package network

import (
	"encoding/binary"
	"fmt"

	"github.com/algorand/go-algorand/crypto"
)

// Constant strings used as keys for topics
const (
	requestHashKey = "RequestHash"
	ErrorKey       = "Error" // used for passing an error message
)

// Topic is a key-value pair
//
//msgp:ignore Topic
type Topic struct {
	key  string
	data []byte
}

// MakeTopic Creates a Topic
func MakeTopic(key string, data []byte) Topic {
	return Topic{key: key, data: data}
}

// Topics is an array of type Topic
// The maximum number of topics allowed is 32
// Each topic key can be 64 characters long and cannot be size 0
//
//msgp:ignore Topics
type Topics []Topic

// MarshallTopics serializes the topics into a byte array
func (ts Topics) MarshallTopics() (b []byte) {

	// Calculate the total buffer size required to store the topics
	bufferSize := binary.MaxVarintLen32 // store topic array size

	for _, val := range ts {
		bufferSize += 2 * binary.MaxVarintLen32 // store key size and the data size
		bufferSize += len(val.key)
		bufferSize += len(val.data)
	}

	buffer := make([]byte, bufferSize)
	bidx := binary.PutUvarint(buffer, uint64(len(ts)))
	for _, val := range ts {
		// write the key size
		n := binary.PutUvarint(buffer[bidx:], uint64(len(val.key)))
		bidx += n
		// write the key
		n = copy(buffer[bidx:], []byte(val.key))
		bidx += n

		// write the data size
		n = binary.PutUvarint(buffer[bidx:], uint64(len(val.data)))
		bidx += n
		// write the data
		n = copy(buffer[bidx:], val.data)
		bidx += n
	}
	return buffer[:bidx]
}

// UnmarshallTopics unmarshalls the topics from the byte array
func UnmarshallTopics(buffer []byte) (ts Topics, err error) {
	// Get the number of topics
	var idx int
	numTopics, nr := binary.Uvarint(buffer[idx:])
	if nr <= 0 {
		return nil, fmt.Errorf("UnmarshallTopics: could not read the number of topics")
	}
	if numTopics > 32 { // numTopics is uint64
		return nil, fmt.Errorf("UnmarshallTopics: number of topics %d is greater than 32", numTopics)
	}
	idx += nr
	topics := make([]Topic, numTopics)

	for x := 0; x < int(numTopics); x++ {
		// read the key length
		strlen, nr := binary.Uvarint(buffer[idx:])
		if nr <= 0 {
			return nil, fmt.Errorf("UnmarshallTopics: could not read the key length")
		}
		idx += nr

		// read the key
		if len(buffer) < idx+int(strlen) || strlen > 64 || strlen == 0 {
			return nil, fmt.Errorf("UnmarshallTopics: could not read the key")
		}
		topics[x].key = string(buffer[idx : idx+int(strlen)])
		idx += int(strlen)

		// read the data length
		dataLen, nr := binary.Uvarint(buffer[idx:])
		if nr <= 0 || dataLen > MaxMessageLength {
			return nil, fmt.Errorf("UnmarshallTopics: could not read the data length")
		}
		idx += nr

		// read the data
		if len(buffer) < idx+int(dataLen) {
			return nil, fmt.Errorf("UnmarshallTopics: data larger than buffer size")
		}
		topics[x].data = make([]byte, dataLen)
		copy(topics[x].data, buffer[idx:idx+int(dataLen)])
		idx += int(dataLen)
	}
	return topics, nil
}

// hashTopics returns the hash of serialized topics.
// Expects the nonce to be already added as a topic
func hashTopics(topics []byte) (partialHash uint64) {
	digest := crypto.Hash(topics)
	partialHash = digest.TrimUint64()
	return partialHash
}

// GetValue returns the value of the key if the key is found in the topics
func (ts *Topics) GetValue(key string) (val []byte, found bool) {
	for _, t := range *ts {
		if t.key == key {
			return t.data, true
		}
	}
	return
}