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  }