github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v2/request.go (about) 1 // Copyright (c) 2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package v2 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/choria-io/go-choria/protocol" 15 ) 16 17 type Request struct { 18 Protocol protocol.ProtocolVersion `json:"protocol"` 19 MessageBody []byte `json:"message"` 20 21 ReqEnvelope 22 23 callerJWT string 24 signerJWT string 25 26 mu sync.Mutex 27 } 28 29 type ReqEnvelope struct { 30 RequestID string `json:"id"` 31 SenderID string `json:"sender"` 32 CallerID string `json:"caller"` 33 Collective string `json:"collective"` 34 Agent string `json:"agent"` 35 TTL int `json:"ttl"` 36 Time int64 `json:"time"` 37 Filter *protocol.Filter `json:"filter,omitempty"` 38 39 seenBy [][3]string 40 federation *FederationTransportHeader 41 } 42 43 // NewRequest creates a io.choria.protocol.v2.request 44 func NewRequest(agent string, sender string, caller string, ttl int, id string, collective string) (protocol.Request, error) { 45 req := &Request{ 46 Protocol: protocol.RequestV2, 47 ReqEnvelope: ReqEnvelope{ 48 SenderID: sender, 49 TTL: ttl, 50 RequestID: id, 51 Time: time.Now().UnixNano(), 52 }, 53 } 54 55 req.SetCollective(collective) 56 req.SetAgent(agent) 57 req.SetCallerID(caller) 58 req.SetFilter(protocol.NewFilter()) 59 60 return req, nil 61 } 62 63 // NewRequestFromSecureRequest creates a io.choria.protocol.v2.request based on the data contained in a SecureRequest 64 func NewRequestFromSecureRequest(sr protocol.SecureRequest) (protocol.Request, error) { 65 if sr.Version() != protocol.SecureRequestV2 { 66 return nil, fmt.Errorf("cannot create a version 2 SecureRequest from a %s SecureRequest", sr.Version()) 67 } 68 69 req := &Request{ 70 Protocol: protocol.RequestV2, 71 callerJWT: sr.(*SecureRequest).CallerJWT, 72 signerJWT: sr.(*SecureRequest).SignerJWT, 73 } 74 75 err := req.IsValidJSON(sr.Message()) 76 if err != nil { 77 return nil, fmt.Errorf("the JSON body from the SecureRequest is not a valid Request message: %s", err) 78 } 79 80 err = json.Unmarshal(sr.Message(), req) 81 if err != nil { 82 return nil, fmt.Errorf("could not parse JSON data from Secure Request: %s", err) 83 } 84 85 return req, nil 86 } 87 88 // RecordNetworkHop appends a hop onto the list of those who processed this message 89 func (r *Request) RecordNetworkHop(in string, processor string, out string) { 90 r.mu.Lock() 91 defer r.mu.Unlock() 92 93 r.ReqEnvelope.seenBy = append(r.ReqEnvelope.seenBy, [3]string{in, processor, out}) 94 } 95 96 // NetworkHops returns a list of tuples this messaged traveled through 97 func (r *Request) NetworkHops() [][3]string { 98 r.mu.Lock() 99 defer r.mu.Unlock() 100 101 return r.ReqEnvelope.seenBy 102 } 103 104 // SetFederationTargets sets the list of hosts this message should go to. 105 // 106 // Federation brokers will duplicate the message and send one for each target 107 func (r *Request) SetFederationTargets(targets []string) { 108 r.mu.Lock() 109 defer r.mu.Unlock() 110 111 if r.ReqEnvelope.federation == nil { 112 r.ReqEnvelope.federation = &FederationTransportHeader{} 113 } 114 115 r.ReqEnvelope.federation.Targets = targets 116 } 117 118 // SetFederationReplyTo stores the original reply-to destination in the federation headers 119 func (r *Request) SetFederationReplyTo(reply string) { 120 r.mu.Lock() 121 defer r.mu.Unlock() 122 123 if r.ReqEnvelope.federation == nil { 124 r.ReqEnvelope.federation = &FederationTransportHeader{} 125 } 126 127 r.ReqEnvelope.federation.ReplyTo = reply 128 } 129 130 // SetFederationRequestID sets the request ID for federation purposes 131 func (r *Request) SetFederationRequestID(id string) { 132 r.mu.Lock() 133 defer r.mu.Unlock() 134 135 if r.ReqEnvelope.federation == nil { 136 r.ReqEnvelope.federation = &FederationTransportHeader{} 137 } 138 139 r.ReqEnvelope.federation.RequestID = id 140 } 141 142 // IsFederated determines if this message is federated 143 func (r *Request) IsFederated() bool { 144 r.mu.Lock() 145 defer r.mu.Unlock() 146 147 return r.ReqEnvelope.federation != nil 148 } 149 150 // SetUnfederated removes any federation information from the message 151 func (r *Request) SetUnfederated() { 152 r.mu.Lock() 153 defer r.mu.Unlock() 154 155 r.ReqEnvelope.federation = nil 156 } 157 158 // FederationTargets retrieves the list of targets this message is destined for 159 func (r *Request) FederationTargets() (targets []string, federated bool) { 160 r.mu.Lock() 161 defer r.mu.Unlock() 162 163 if r.federation == nil { 164 return nil, false 165 } 166 167 return r.ReqEnvelope.federation.Targets, true 168 } 169 170 // FederationReplyTo retrieves the reply to string set by the federation broker 171 func (r *Request) FederationReplyTo() (replyTo string, federated bool) { 172 r.mu.Lock() 173 defer r.mu.Unlock() 174 175 if r.ReqEnvelope.federation == nil { 176 return "", false 177 } 178 179 return r.ReqEnvelope.federation.ReplyTo, true 180 } 181 182 // FederationRequestID retrieves the federation specific requestid 183 func (r *Request) FederationRequestID() (id string, federated bool) { 184 r.mu.Lock() 185 defer r.mu.Unlock() 186 187 if r.ReqEnvelope.federation == nil { 188 return "", false 189 } 190 191 return r.ReqEnvelope.federation.RequestID, true 192 } 193 194 // SetRequestID sets the request ID for this message 195 func (r *Request) SetRequestID(id string) { 196 r.mu.Lock() 197 defer r.mu.Unlock() 198 199 r.ReqEnvelope.RequestID = id 200 } 201 202 // SetMessage set the message body that's contained in this request 203 func (r *Request) SetMessage(message []byte) { 204 r.mu.Lock() 205 defer r.mu.Unlock() 206 207 r.MessageBody = message 208 } 209 210 // SetCallerID sets the caller id for this request 211 func (r *Request) SetCallerID(id string) { 212 r.mu.Lock() 213 defer r.mu.Unlock() 214 215 // TODO validate it 216 r.ReqEnvelope.CallerID = id 217 } 218 219 // SetCollective sets the collective this request is directed at 220 func (r *Request) SetCollective(collective string) { 221 r.mu.Lock() 222 defer r.mu.Unlock() 223 224 r.ReqEnvelope.Collective = collective 225 } 226 227 // SetAgent sets the agent this requires is created for 228 func (r *Request) SetAgent(agent string) { 229 r.mu.Lock() 230 defer r.mu.Unlock() 231 232 r.ReqEnvelope.Agent = agent 233 } 234 235 // SetTTL sets the validity period for this message 236 func (r *Request) SetTTL(ttl int) { 237 r.mu.Lock() 238 defer r.mu.Unlock() 239 240 r.ReqEnvelope.TTL = ttl 241 } 242 243 // Message retrieves the Message body 244 func (r *Request) Message() []byte { 245 r.mu.Lock() 246 defer r.mu.Unlock() 247 248 return r.MessageBody 249 } 250 251 // RequestID retrieves the unique request ID 252 func (r *Request) RequestID() string { 253 r.mu.Lock() 254 defer r.mu.Unlock() 255 256 return r.ReqEnvelope.RequestID 257 } 258 259 // SenderID retrieves the sender id that sent the message 260 func (r *Request) SenderID() string { 261 r.mu.Lock() 262 defer r.mu.Unlock() 263 264 return r.ReqEnvelope.SenderID 265 } 266 267 // CallerID retrieves the caller id that sent the message 268 func (r *Request) CallerID() string { 269 r.mu.Lock() 270 defer r.mu.Unlock() 271 272 return r.ReqEnvelope.CallerID 273 } 274 275 // Collective retrieves the name of the sub collective this message is aimed at 276 func (r *Request) Collective() string { 277 r.mu.Lock() 278 defer r.mu.Unlock() 279 280 return r.ReqEnvelope.Collective 281 } 282 283 // Agent retrieves the agent name this message is for 284 func (r *Request) Agent() string { 285 r.mu.Lock() 286 defer r.mu.Unlock() 287 288 return r.ReqEnvelope.Agent 289 } 290 291 // TTL retrieves the maximum allow lifetime of this message 292 func (r *Request) TTL() int { 293 r.mu.Lock() 294 defer r.mu.Unlock() 295 296 return r.ReqEnvelope.TTL 297 } 298 299 // Time retrieves the time this message was first made 300 func (r *Request) Time() time.Time { 301 r.mu.Lock() 302 defer r.mu.Unlock() 303 304 return time.Unix(0, r.ReqEnvelope.Time) 305 } 306 307 // Filter retrieves the filter for the message. The boolean is true when the filter is not empty 308 func (r *Request) Filter() (filter *protocol.Filter, filtered bool) { 309 r.mu.Lock() 310 defer r.mu.Unlock() 311 312 if r.ReqEnvelope.Filter == nil { 313 r.ReqEnvelope.Filter = protocol.NewFilter() 314 } 315 316 return r.ReqEnvelope.Filter, !r.ReqEnvelope.Filter.Empty() 317 } 318 319 // NewFilter creates a new empty filter and sets it 320 func (r *Request) NewFilter() *protocol.Filter { 321 r.mu.Lock() 322 defer r.mu.Unlock() 323 324 r.ReqEnvelope.Filter = protocol.NewFilter() 325 326 return r.ReqEnvelope.Filter 327 } 328 329 // JSON creates a JSON encoded request 330 func (r *Request) JSON() ([]byte, error) { 331 r.mu.Lock() 332 defer r.mu.Unlock() 333 334 j, err := json.Marshal(r) 335 if err != nil { 336 protocolErrorCtr.Inc() 337 return nil, fmt.Errorf("could not JSON Marshal: %s", err) 338 } 339 340 if err = r.isValidJSONUnlocked(j); err != nil { 341 return nil, fmt.Errorf("%w: %s", ErrInvalidJSON, err) 342 } 343 344 return j, nil 345 } 346 347 // SetFilter sets and overwrites the filter for a message with a new one 348 func (r *Request) SetFilter(filter *protocol.Filter) { 349 r.mu.Lock() 350 defer r.mu.Unlock() 351 352 r.ReqEnvelope.Filter = filter 353 } 354 355 // Version retrieves the protocol version for this message 356 func (r *Request) Version() protocol.ProtocolVersion { 357 r.mu.Lock() 358 defer r.mu.Unlock() 359 360 return r.Protocol 361 } 362 363 // IsValidJSON validates the given JSON data against the schema 364 func (r *Request) IsValidJSON(data []byte) error { 365 r.mu.Lock() 366 defer r.mu.Unlock() 367 368 return r.isValidJSONUnlocked(data) 369 } 370 371 func (r *Request) isValidJSONUnlocked(data []byte) error { 372 _, errors, err := schemaValidate(protocol.RequestV2, data) 373 if err != nil { 374 return fmt.Errorf("could not validate Request JSON data: %s", err) 375 } 376 377 if len(errors) != 0 { 378 return fmt.Errorf("%w: %s", ErrInvalidJSON, strings.Join(errors, ", ")) 379 } 380 381 return nil 382 } 383 384 // CallerPublicData is the JWT validated by the Secure Request, only set when a request is created from a SecureRequest 385 func (r *Request) CallerPublicData() string { 386 r.mu.Lock() 387 defer r.mu.Unlock() 388 389 return r.callerJWT 390 } 391 392 // SignerPublicData is the JWT of the request signer validated by the Secure Request, only set when a request is created from a SecureRequest 393 func (r *Request) SignerPublicData() string { 394 r.mu.Lock() 395 defer r.mu.Unlock() 396 397 return r.signerJWT 398 }