1// Copyright (c) 2017 The btcsuite developers
2// Use of this source code is governed by an ISC
3// license that can be found in the LICENSE file.
4
5package wire
6
7import (
8	"fmt"
9	"io"
10
11	"github.com/btcsuite/btcd/chaincfg/chainhash"
12)
13
14const (
15	// MaxCFHeaderPayload is the maximum byte size of a committed
16	// filter header.
17	MaxCFHeaderPayload = chainhash.HashSize
18
19	// MaxCFHeadersPerMsg is the maximum number of committed filter headers
20	// that can be in a single bitcoin cfheaders message.
21	MaxCFHeadersPerMsg = 2000
22)
23
24// MsgCFHeaders implements the Message interface and represents a bitcoin
25// cfheaders message.  It is used to deliver committed filter header information
26// in response to a getcfheaders message (MsgGetCFHeaders). The maximum number
27// of committed filter headers per message is currently 2000. See
28// MsgGetCFHeaders for details on requesting the headers.
29type MsgCFHeaders struct {
30	FilterType       FilterType
31	StopHash         chainhash.Hash
32	PrevFilterHeader chainhash.Hash
33	FilterHashes     []*chainhash.Hash
34}
35
36// AddCFHash adds a new filter hash to the message.
37func (msg *MsgCFHeaders) AddCFHash(hash *chainhash.Hash) error {
38	if len(msg.FilterHashes)+1 > MaxCFHeadersPerMsg {
39		str := fmt.Sprintf("too many block headers in message [max %v]",
40			MaxBlockHeadersPerMsg)
41		return messageError("MsgCFHeaders.AddCFHash", str)
42	}
43
44	msg.FilterHashes = append(msg.FilterHashes, hash)
45	return nil
46}
47
48// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
49// This is part of the Message interface implementation.
50func (msg *MsgCFHeaders) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error {
51	// Read filter type
52	err := readElement(r, &msg.FilterType)
53	if err != nil {
54		return err
55	}
56
57	// Read stop hash
58	err = readElement(r, &msg.StopHash)
59	if err != nil {
60		return err
61	}
62
63	// Read prev filter header
64	err = readElement(r, &msg.PrevFilterHeader)
65	if err != nil {
66		return err
67	}
68
69	// Read number of filter headers
70	count, err := ReadVarInt(r, pver)
71	if err != nil {
72		return err
73	}
74
75	// Limit to max committed filter headers per message.
76	if count > MaxCFHeadersPerMsg {
77		str := fmt.Sprintf("too many committed filter headers for "+
78			"message [count %v, max %v]", count,
79			MaxBlockHeadersPerMsg)
80		return messageError("MsgCFHeaders.BtcDecode", str)
81	}
82
83	// Create a contiguous slice of hashes to deserialize into in order to
84	// reduce the number of allocations.
85	msg.FilterHashes = make([]*chainhash.Hash, 0, count)
86	for i := uint64(0); i < count; i++ {
87		var cfh chainhash.Hash
88		err := readElement(r, &cfh)
89		if err != nil {
90			return err
91		}
92		msg.AddCFHash(&cfh)
93	}
94
95	return nil
96}
97
98// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
99// This is part of the Message interface implementation.
100func (msg *MsgCFHeaders) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error {
101	// Write filter type
102	err := writeElement(w, msg.FilterType)
103	if err != nil {
104		return err
105	}
106
107	// Write stop hash
108	err = writeElement(w, msg.StopHash)
109	if err != nil {
110		return err
111	}
112
113	// Write prev filter header
114	err = writeElement(w, msg.PrevFilterHeader)
115	if err != nil {
116		return err
117	}
118
119	// Limit to max committed headers per message.
120	count := len(msg.FilterHashes)
121	if count > MaxCFHeadersPerMsg {
122		str := fmt.Sprintf("too many committed filter headers for "+
123			"message [count %v, max %v]", count,
124			MaxBlockHeadersPerMsg)
125		return messageError("MsgCFHeaders.BtcEncode", str)
126	}
127
128	err = WriteVarInt(w, pver, uint64(count))
129	if err != nil {
130		return err
131	}
132
133	for _, cfh := range msg.FilterHashes {
134		err := writeElement(w, cfh)
135		if err != nil {
136			return err
137		}
138	}
139
140	return nil
141}
142
143// Deserialize decodes a filter header from r into the receiver using a format
144// that is suitable for long-term storage such as a database. This function
145// differs from BtcDecode in that BtcDecode decodes from the bitcoin wire
146// protocol as it was sent across the network.  The wire encoding can
147// technically differ depending on the protocol version and doesn't even really
148// need to match the format of a stored filter header at all. As of the time
149// this comment was written, the encoded filter header is the same in both
150// instances, but there is a distinct difference and separating the two allows
151// the API to be flexible enough to deal with changes.
152func (msg *MsgCFHeaders) Deserialize(r io.Reader) error {
153	// At the current time, there is no difference between the wire encoding
154	// and the stable long-term storage format.  As a result, make use of
155	// BtcDecode.
156	return msg.BtcDecode(r, 0, BaseEncoding)
157}
158
159// Command returns the protocol command string for the message.  This is part
160// of the Message interface implementation.
161func (msg *MsgCFHeaders) Command() string {
162	return CmdCFHeaders
163}
164
165// MaxPayloadLength returns the maximum length the payload can be for the
166// receiver. This is part of the Message interface implementation.
167func (msg *MsgCFHeaders) MaxPayloadLength(pver uint32) uint32 {
168	// Hash size + filter type + num headers (varInt) +
169	// (header size * max headers).
170	return 1 + chainhash.HashSize + chainhash.HashSize + MaxVarIntPayload +
171		(MaxCFHeaderPayload * MaxCFHeadersPerMsg)
172}
173
174// NewMsgCFHeaders returns a new bitcoin cfheaders message that conforms to
175// the Message interface. See MsgCFHeaders for details.
176func NewMsgCFHeaders() *MsgCFHeaders {
177	return &MsgCFHeaders{
178		FilterHashes: make([]*chainhash.Hash, 0, MaxCFHeadersPerMsg),
179	}
180}
181