1 package peer
2 3 import (
4 "fmt"
5 "strings"
6 "time"
7 8 "github.com/p9c/p9/pkg/chainhash"
9 "github.com/p9c/p9/pkg/txscript"
10 "github.com/p9c/p9/pkg/wire"
11 )
12 13 const (
14 // maxRejectReasonLen is the maximum length of a sanitized reject reason that will be logged.
15 maxRejectReasonLen = 250
16 )
17 18 // formatLockTime returns a transaction lock time as a human-readable string.
19 func formatLockTime(lockTime uint32) string {
20 // The lock time field of a transaction is either a block height at which the transaction is finalized or a
21 // timestamp depending on if the value is before the lockTimeThreshold. When it is under the threshold it is a block
22 // height.
23 if lockTime < txscript.LockTimeThreshold {
24 return fmt.Sprintf("height %d", lockTime)
25 }
26 27 return time.Unix(int64(lockTime), 0).String()
28 }
29 30 // invSummary returns an inventory message as a human-readable string.
31 func invSummary(invList []*wire.InvVect) string {
32 // No inventory.
33 invLen := len(invList)
34 if invLen == 0 {
35 return "empty"
36 }
37 38 // One inventory item.
39 if invLen == 1 {
40 iv := invList[0]
41 switch iv.Type {
42 case wire.InvTypeError:
43 return fmt.Sprintf("error %s", iv.Hash)
44 // case wire.InvTypeWitnessBlock:
45 // return fmt.Sprintf("witness block %s", iv.Hash)
46 case wire.InvTypeBlock:
47 return fmt.Sprintf("block %s", iv.Hash)
48 // case wire.InvTypeWitnessTx:
49 // return fmt.Sprintf("witness tx %s", iv.Hash)
50 case wire.InvTypeTx:
51 return fmt.Sprintf("tx %s", iv.Hash)
52 }
53 54 return fmt.Sprintf("unknown (%d) %s", uint32(iv.Type), iv.Hash)
55 }
56 57 // More than one inv item.
58 return fmt.Sprintf("size %d", invLen)
59 }
60 61 // locatorSummary returns a block locator as a human-readable string.
62 func locatorSummary(locator []*chainhash.Hash, stopHash *chainhash.Hash) string {
63 if len(locator) > 0 {
64 return fmt.Sprintf("locator %s, stop %s", locator[0], stopHash)
65 }
66 67 return fmt.Sprintf("no locator, stop %s", stopHash)
68 69 }
70 71 // sanitizeString strips any characters which are even remotely dangerous, such as html control characters, from the
72 // passed string. It also limits it to the passed maximum size, which can be 0 for unlimited. When the string is
73 // limited, it will also add "..." to the string to indicate it was truncated.
74 func sanitizeString(str string, maxLength uint) string {
75 const safeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY" +
76 "Z01234567890 .,;_/:?@"
77 78 // Strip any characters not in the safeChars string removed.
79 str = strings.Map(func(r rune) rune {
80 if strings.ContainsRune(safeChars, r) {
81 return r
82 }
83 return -1
84 }, str,
85 )
86 87 // Limit the string to the max allowed length.
88 if maxLength > 0 && uint(len(str)) > maxLength {
89 str = str[:maxLength]
90 str = str + "..."
91 }
92 return str
93 }
94 95 // messageSummary returns a human-readable string which summarizes a message. Not all messages have or need a summary.
96 // This is used for debug logging.
97 func messageSummary(msg wire.Message) string {
98 switch msg := msg.(type) {
99 case *wire.MsgVersion:
100 return fmt.Sprintf("agent %s, pver %d, block %d",
101 msg.UserAgent, msg.ProtocolVersion, msg.LastBlock,
102 )
103 104 case *wire.MsgVerAck:
105 // No summary.
106 107 case *wire.MsgGetAddr:
108 // No summary.
109 110 case *wire.MsgAddr:
111 return fmt.Sprintf("%d addr", len(msg.AddrList))
112 113 case *wire.MsgPing:
114 // No summary - perhaps add Nonce.
115 116 case *wire.MsgPong:
117 // No summary - perhaps add Nonce.
118 119 case *wire.MsgAlert:
120 // No summary.
121 122 case *wire.MsgMemPool:
123 // No summary.
124 125 case *wire.MsgTx:
126 return fmt.Sprintf("hash %s, %d inputs, %d outputs, lock %s",
127 msg.TxHash(), len(msg.TxIn), len(msg.TxOut),
128 formatLockTime(msg.LockTime),
129 )
130 131 case *wire.Block:
132 header := &msg.Header
133 return fmt.Sprintf("hash %s, ver %d, %d tx, %s", msg.BlockHash(),
134 header.Version, len(msg.Transactions), header.Timestamp,
135 )
136 137 case *wire.MsgInv:
138 return invSummary(msg.InvList)
139 140 case *wire.MsgNotFound:
141 return invSummary(msg.InvList)
142 143 case *wire.MsgGetData:
144 return invSummary(msg.InvList)
145 146 case *wire.MsgGetBlocks:
147 return locatorSummary(msg.BlockLocatorHashes, &msg.HashStop)
148 149 case *wire.MsgGetHeaders:
150 return locatorSummary(msg.BlockLocatorHashes, &msg.HashStop)
151 152 case *wire.MsgHeaders:
153 return fmt.Sprintf("num %d", len(msg.Headers))
154 155 case *wire.MsgGetCFHeaders:
156 return fmt.Sprintf("start_height=%d, stop_hash=%v",
157 msg.StartHeight, msg.StopHash,
158 )
159 160 case *wire.MsgCFHeaders:
161 return fmt.Sprintf("stop_hash=%v, num_filter_hashes=%d",
162 msg.StopHash, len(msg.FilterHashes),
163 )
164 165 case *wire.MsgReject:
166 // Ensure the variable length strings don't contain any characters which are even remotely dangerous such as
167 // HTML control characters, etc. Also limit them to sane length for logging.
168 rejCommand := sanitizeString(msg.Cmd, wire.CommandSize)
169 rejReason := sanitizeString(msg.Reason, maxRejectReasonLen)
170 summary := fmt.Sprintf("cmd %v, code %v, reason %v", rejCommand,
171 msg.Code, rejReason,
172 )
173 if rejCommand == wire.CmdBlock || rejCommand == wire.CmdTx {
174 summary += fmt.Sprintf(", hash %v", msg.Hash)
175 }
176 return summary
177 }
178 179 // No summary for other messages.
180 return ""
181 }
182