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  }