github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v2/transport.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  
    13  	"github.com/choria-io/go-choria/protocol"
    14  )
    15  
    16  type TransportMessage struct {
    17  	// The protocol version for this transport `io.choria.protocol.v2.transport` / protocol.TransportV2
    18  	Protocol protocol.ProtocolVersion `json:"protocol"`
    19  	// The payload to be transport, a Secure Request or Secure Reply
    20  	Data []byte `json:"data"`
    21  	// Optional headers
    22  	Headers *TransportHeaders `json:"headers,omitempty"`
    23  
    24  	mu sync.Mutex
    25  }
    26  
    27  type TransportHeaders struct {
    28  	// A transport specific response channel for this message, used in requests
    29  	ReplyTo string `json:"reply,omitempty"`
    30  	// The host that sent this message
    31  	Sender string `json:"sender,omitempty"`
    32  	// A trace of host/broker pairs that the message traversed
    33  	SeenBy [][3]string `json:"trace,omitempty"`
    34  	// Headers to assist federation
    35  	Federation *FederationTransportHeader `json:"federation,omitempty"`
    36  }
    37  
    38  type FederationTransportHeader struct {
    39  	// The request ID a federated message belongs to
    40  	RequestID string `json:"request,omitempty"`
    41  	// The original `reply` before federation
    42  	ReplyTo string `json:"reply,omitempty"`
    43  	// The identities who the federated message is for
    44  	Targets []string `json:"targets,omitempty"`
    45  }
    46  
    47  // NewTransportMessage creates a io.choria.protocol.v2.transport
    48  func NewTransportMessage(sender string) (message protocol.TransportMessage, err error) {
    49  	message = &TransportMessage{
    50  		Protocol: protocol.TransportV2,
    51  		Headers:  &TransportHeaders{},
    52  	}
    53  
    54  	message.SetSender(sender)
    55  
    56  	return message, nil
    57  }
    58  
    59  // NewTransportFromJSON creates a new TransportMessage from JSON
    60  func NewTransportFromJSON(data []byte) (message protocol.TransportMessage, err error) {
    61  	message = &TransportMessage{
    62  		Headers: &TransportHeaders{},
    63  	}
    64  
    65  	err = message.IsValidJSON(data)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	err = json.Unmarshal(data, &message)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	return message, nil
    76  }
    77  
    78  func (m *TransportMessage) SetFederationRequestID(id string) {
    79  	m.mu.Lock()
    80  	defer m.mu.Unlock()
    81  
    82  	if m.Headers.Federation == nil {
    83  		m.Headers.Federation = &FederationTransportHeader{}
    84  	}
    85  
    86  	m.Headers.Federation.RequestID = id
    87  }
    88  
    89  func (m *TransportMessage) SetFederationReplyTo(reply string) {
    90  	m.mu.Lock()
    91  	defer m.mu.Unlock()
    92  
    93  	if m.Headers.Federation == nil {
    94  		m.Headers.Federation = &FederationTransportHeader{}
    95  	}
    96  
    97  	m.Headers.Federation.ReplyTo = reply
    98  }
    99  
   100  func (m *TransportMessage) SetFederationTargets(targets []string) {
   101  	m.mu.Lock()
   102  	defer m.mu.Unlock()
   103  
   104  	if m.Headers.Federation == nil {
   105  		m.Headers.Federation = &FederationTransportHeader{}
   106  	}
   107  
   108  	m.Headers.Federation.Targets = targets
   109  }
   110  
   111  func (m *TransportMessage) SetUnfederated() {
   112  	m.mu.Lock()
   113  	defer m.mu.Unlock()
   114  
   115  	m.Headers.Federation = nil
   116  }
   117  
   118  func (m *TransportMessage) FederationRequestID() (string, bool) {
   119  	m.mu.Lock()
   120  	defer m.mu.Unlock()
   121  
   122  	if m.Headers.Federation == nil {
   123  		return "", false
   124  	}
   125  
   126  	return m.Headers.Federation.RequestID, true
   127  }
   128  
   129  func (m *TransportMessage) FederationReplyTo() (string, bool) {
   130  	m.mu.Lock()
   131  	defer m.mu.Unlock()
   132  
   133  	if m.Headers.Federation == nil {
   134  		return "", false
   135  	}
   136  
   137  	return m.Headers.Federation.ReplyTo, true
   138  }
   139  
   140  func (m *TransportMessage) FederationTargets() ([]string, bool) {
   141  	m.mu.Lock()
   142  	defer m.mu.Unlock()
   143  
   144  	if m.Headers.Federation == nil {
   145  		return nil, false
   146  	}
   147  
   148  	return m.Headers.Federation.Targets, true
   149  }
   150  
   151  func (m *TransportMessage) RecordNetworkHop(in string, processor string, out string) {
   152  	m.mu.Lock()
   153  	defer m.mu.Unlock()
   154  
   155  	m.Headers.SeenBy = append(m.Headers.SeenBy, [3]string{in, processor, out})
   156  }
   157  
   158  func (m *TransportMessage) NetworkHops() [][3]string {
   159  	m.mu.Lock()
   160  	defer m.mu.Unlock()
   161  
   162  	return m.Headers.SeenBy
   163  }
   164  
   165  func (m *TransportMessage) IsFederated() bool {
   166  	m.mu.Lock()
   167  	defer m.mu.Unlock()
   168  
   169  	return m.Headers.Federation != nil
   170  }
   171  
   172  func (m *TransportMessage) SetReplyData(reply protocol.SecureReply) error {
   173  	m.mu.Lock()
   174  	defer m.mu.Unlock()
   175  
   176  	j, err := reply.JSON()
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	m.Data = j
   182  
   183  	return nil
   184  }
   185  
   186  func (m *TransportMessage) SetRequestData(request protocol.SecureRequest) error {
   187  	m.mu.Lock()
   188  	defer m.mu.Unlock()
   189  
   190  	j, err := request.JSON()
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	m.Data = j
   196  
   197  	return nil
   198  }
   199  
   200  func (m *TransportMessage) SetReplyTo(reply string) {
   201  	m.mu.Lock()
   202  	defer m.mu.Unlock()
   203  
   204  	m.Headers.ReplyTo = reply
   205  }
   206  
   207  func (m *TransportMessage) SetSender(sender string) {
   208  	m.mu.Lock()
   209  	defer m.mu.Unlock()
   210  
   211  	m.Headers.Sender = sender
   212  }
   213  
   214  func (m *TransportMessage) ReplyTo() string {
   215  	m.mu.Lock()
   216  	defer m.mu.Unlock()
   217  
   218  	return m.Headers.ReplyTo
   219  }
   220  
   221  func (m *TransportMessage) SenderID() string {
   222  	m.mu.Lock()
   223  	defer m.mu.Unlock()
   224  
   225  	return m.Headers.Sender
   226  }
   227  
   228  func (m *TransportMessage) SeenBy() [][3]string {
   229  	m.mu.Lock()
   230  	defer m.mu.Unlock()
   231  
   232  	return m.Headers.SeenBy
   233  }
   234  
   235  func (m *TransportMessage) Message() ([]byte, error) {
   236  	m.mu.Lock()
   237  	defer m.mu.Unlock()
   238  
   239  	return m.Data, nil
   240  }
   241  
   242  func (m *TransportMessage) IsValidJSON(data []byte) error {
   243  	if !protocol.ClientStrictValidation {
   244  		return nil
   245  	}
   246  
   247  	_, errors, err := schemaValidate(protocol.TransportV2, data)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	if len(errors) != 0 {
   253  		return fmt.Errorf("%w: %s", ErrInvalidJSON, strings.Join(errors, ", "))
   254  	}
   255  
   256  	return nil
   257  }
   258  
   259  func (m *TransportMessage) JSON() ([]byte, error) {
   260  	m.mu.Lock()
   261  	j, err := json.Marshal(m)
   262  	m.mu.Unlock()
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	if err = m.IsValidJSON(j); err != nil {
   268  		return nil, fmt.Errorf("%w: %s", ErrInvalidJSON, err)
   269  	}
   270  
   271  	return j, nil
   272  }
   273  
   274  func (m *TransportMessage) Version() protocol.ProtocolVersion {
   275  	m.mu.Lock()
   276  	defer m.mu.Unlock()
   277  
   278  	return m.Protocol
   279  }