github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v1/transport.go (about) 1 // Copyright (c) 2017-2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package v1 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "strings" 11 "sync" 12 13 "github.com/choria-io/go-choria/protocol" 14 ) 15 16 type TransportMessage struct { 17 Protocol protocol.ProtocolVersion `json:"protocol"` 18 Data []byte `json:"data"` 19 Headers *TransportHeaders `json:"headers"` 20 21 mu sync.Mutex 22 } 23 24 type TransportHeaders struct { 25 ReplyTo string `json:"reply-to,omitempty"` 26 MCollectiveSender string `json:"mc_sender,omitempty"` 27 SeenBy [][3]string `json:"seen-by,omitempty"` 28 Federation *FederationTransportHeader `json:"federation,omitempty"` 29 } 30 31 type FederationTransportHeader struct { 32 RequestID string `json:"req,omitempty"` 33 ReplyTo string `json:"reply-to,omitempty"` 34 Targets []string `json:"target,omitempty"` 35 } 36 37 // Message retrieves the stored data 38 func (m *TransportMessage) Message() (data []byte, err error) { 39 m.mu.Lock() 40 defer m.mu.Unlock() 41 42 return m.Data, nil 43 } 44 45 // IsFederated determines if this message is federated 46 func (m *TransportMessage) IsFederated() bool { 47 m.mu.Lock() 48 defer m.mu.Unlock() 49 50 return m.Headers.Federation != nil 51 } 52 53 // FederationTargets retrieves the list of targets this message is destined for 54 func (m *TransportMessage) FederationTargets() (targets []string, federated bool) { 55 m.mu.Lock() 56 defer m.mu.Unlock() 57 58 if m.Headers.Federation == nil { 59 return nil, false 60 } 61 62 return m.Headers.Federation.Targets, true 63 } 64 65 // FederationReplyTo retrieves the reply to string set by the federation broker 66 func (m *TransportMessage) FederationReplyTo() (replyto string, federated bool) { 67 m.mu.Lock() 68 defer m.mu.Unlock() 69 70 if m.Headers.Federation == nil { 71 return "", false 72 } 73 74 return m.Headers.Federation.ReplyTo, true 75 } 76 77 // FederationRequestID retrieves the federation specific requestid 78 func (m *TransportMessage) FederationRequestID() (id string, federated bool) { 79 m.mu.Lock() 80 defer m.mu.Unlock() 81 82 if m.Headers.Federation == nil { 83 return "", false 84 } 85 86 return m.Headers.Federation.RequestID, true 87 } 88 89 // SenderID retrieves the identity of the sending host 90 func (m *TransportMessage) SenderID() string { 91 m.mu.Lock() 92 defer m.mu.Unlock() 93 94 return m.Headers.MCollectiveSender 95 } 96 97 // ReplyTo retrieves the destination description where replies should go to 98 func (m *TransportMessage) ReplyTo() string { 99 m.mu.Lock() 100 defer m.mu.Unlock() 101 102 return m.Headers.ReplyTo 103 } 104 105 // SeenBy retrieves the list of end points that this messages passed thruogh 106 func (m *TransportMessage) SeenBy() [][3]string { 107 m.mu.Lock() 108 defer m.mu.Unlock() 109 110 return m.Headers.SeenBy 111 } 112 113 // SetFederationTargets sets the list of hosts this message should go to. 114 // 115 // Federation brokers will duplicate the message and send one for each target 116 func (m *TransportMessage) SetFederationTargets(targets []string) { 117 m.mu.Lock() 118 defer m.mu.Unlock() 119 120 if m.Headers.Federation == nil { 121 m.Headers.Federation = &FederationTransportHeader{} 122 } 123 124 m.Headers.Federation.Targets = targets 125 } 126 127 // SetFederationReplyTo stores the original reply-to destination in the federation headers 128 func (m *TransportMessage) SetFederationReplyTo(reply string) { 129 m.mu.Lock() 130 defer m.mu.Unlock() 131 132 if m.Headers.Federation == nil { 133 m.Headers.Federation = &FederationTransportHeader{} 134 } 135 136 m.Headers.Federation.ReplyTo = reply 137 } 138 139 // SetFederationRequestID sets the request ID for federation purposes 140 func (m *TransportMessage) SetFederationRequestID(id string) { 141 m.mu.Lock() 142 defer m.mu.Unlock() 143 144 if m.Headers.Federation == nil { 145 m.Headers.Federation = &FederationTransportHeader{} 146 } 147 148 m.Headers.Federation.RequestID = id 149 } 150 151 // SetSender sets the "mc_sender" - typically the identity of the sending host 152 func (m *TransportMessage) SetSender(sender string) { 153 m.mu.Lock() 154 defer m.mu.Unlock() 155 156 m.Headers.MCollectiveSender = sender 157 } 158 159 // SetReplyTo sets the reply-to targget 160 func (m *TransportMessage) SetReplyTo(reply string) { 161 m.mu.Lock() 162 defer m.mu.Unlock() 163 164 m.Headers.ReplyTo = reply 165 } 166 167 // SetReplyData extracts the JSON body from a SecureReply and stores it 168 func (m *TransportMessage) SetReplyData(reply protocol.SecureReply) error { 169 m.mu.Lock() 170 defer m.mu.Unlock() 171 172 j, err := reply.JSON() 173 if err != nil { 174 return fmt.Errorf("could not JSON encode the Reply structure for transport: %s", err) 175 } 176 177 m.Data = j 178 179 return nil 180 } 181 182 // SetRequestData extracts the JSON body from a SecureRequest and stores it 183 func (m *TransportMessage) SetRequestData(request protocol.SecureRequest) error { 184 m.mu.Lock() 185 defer m.mu.Unlock() 186 187 j, err := request.JSON() 188 if err != nil { 189 return fmt.Errorf("could not JSON encode the Request structure for transport: %s", err) 190 } 191 192 m.Data = j 193 194 return nil 195 } 196 197 // RecordNetworkHop appends a hop onto the list of those who processed this message 198 func (m *TransportMessage) RecordNetworkHop(in string, processor string, out string) { 199 m.mu.Lock() 200 defer m.mu.Unlock() 201 202 m.Headers.SeenBy = append(m.Headers.SeenBy, [3]string{in, processor, out}) 203 } 204 205 // NetworkHops returns a list of tuples this messaged traveled through 206 func (m *TransportMessage) NetworkHops() [][3]string { 207 m.mu.Lock() 208 defer m.mu.Unlock() 209 210 return m.Headers.SeenBy 211 } 212 213 // JSON creates a JSON encoded message 214 func (m *TransportMessage) JSON() ([]byte, error) { 215 m.mu.Lock() 216 j, err := json.Marshal(m) 217 m.mu.Unlock() 218 if err != nil { 219 return nil, fmt.Errorf("could not JSON Marshal: %s", err) 220 } 221 222 if err = m.IsValidJSON(j); err != nil { 223 return nil, fmt.Errorf("the JSON produced from the Transport does not pass validation: %s", err) 224 } 225 226 return j, nil 227 } 228 229 // SetUnfederated removes any federation information from the message 230 func (m *TransportMessage) SetUnfederated() { 231 m.mu.Lock() 232 defer m.mu.Unlock() 233 234 m.Headers.Federation = nil 235 } 236 237 // Version retrieves the protocol version for this message 238 func (m *TransportMessage) Version() protocol.ProtocolVersion { 239 m.mu.Lock() 240 defer m.mu.Unlock() 241 242 return m.Protocol 243 } 244 245 // IsValidJSON validates the given JSON data against the Transport schema 246 func (m *TransportMessage) IsValidJSON(data []byte) error { 247 if !protocol.ClientStrictValidation { 248 return nil 249 } 250 251 _, errors, err := schemaValidate(protocol.TransportV1, data) 252 if err != nil { 253 return fmt.Errorf("could not validate Transport JSON data: %s", err) 254 } 255 256 if len(errors) != 0 { 257 return fmt.Errorf("supplied JSON document is not a valid Transport message: %s", strings.Join(errors, ", ")) 258 } 259 260 return nil 261 }