github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/grid/msg.go (about)

     1  // Copyright (c) 2015-2023 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package grid
    19  
    20  import (
    21  	"encoding/binary"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"github.com/tinylib/msgp/msgp"
    26  	"github.com/zeebo/xxh3"
    27  )
    28  
    29  // Op is operation type.
    30  //
    31  //go:generate msgp -unexported -file=$GOFILE
    32  //go:generate stringer -type=Op -output=msg_string.go -trimprefix=Op $GOFILE
    33  
    34  // Op is operation type messages.
    35  type Op uint8
    36  
    37  // HandlerID is the ID for the handler of a specific type.
    38  type HandlerID uint8
    39  
    40  const (
    41  	// OpConnect is a connect request.
    42  	OpConnect Op = iota + 1
    43  
    44  	// OpConnectResponse is a response to a connect request.
    45  	OpConnectResponse
    46  
    47  	// OpPing is a ping request.
    48  	// If a mux id is specified that mux is pinged.
    49  	// Clients send ping requests.
    50  	OpPing
    51  
    52  	// OpPong is a OpPing response returned by the server.
    53  	OpPong
    54  
    55  	// OpConnectMux will connect a new mux with optional payload.
    56  	OpConnectMux
    57  
    58  	// OpMuxConnectError is an  error while connecting a mux.
    59  	OpMuxConnectError
    60  
    61  	// OpDisconnectClientMux instructs a client to disconnect a mux
    62  	OpDisconnectClientMux
    63  
    64  	// OpDisconnectServerMux instructs a server to disconnect (cancel) a server mux
    65  	OpDisconnectServerMux
    66  
    67  	// OpMuxClientMsg contains a message to a client Mux
    68  	OpMuxClientMsg
    69  
    70  	// OpMuxServerMsg contains a message to a server Mux
    71  	OpMuxServerMsg
    72  
    73  	// OpUnblockSrvMux contains a message that a server mux is unblocked with one.
    74  	// Only Stateful streams has flow control.
    75  	OpUnblockSrvMux
    76  
    77  	// OpUnblockClMux contains a message that a client mux is unblocked with one.
    78  	// Only Stateful streams has flow control.
    79  	OpUnblockClMux
    80  
    81  	// OpAckMux acknowledges a mux was created.
    82  	OpAckMux
    83  
    84  	// OpRequest is a single request + response.
    85  	// MuxID is returned in response.
    86  	OpRequest
    87  
    88  	// OpResponse is a response to a single request.
    89  	// FlagPayloadIsErr is used to signify that the payload is a string error converted to byte slice.
    90  	// When a response is received, the mux is already removed from the remote.
    91  	OpResponse
    92  
    93  	// OpDisconnect instructs that remote wants to disconnect
    94  	OpDisconnect
    95  
    96  	// OpMerged is several operations merged into one.
    97  	OpMerged
    98  )
    99  
   100  const (
   101  	// FlagCRCxxh3 indicates that, the lower 32 bits of xxhash3 of the serialized
   102  	// message will be sent after the serialized message as little endian.
   103  	FlagCRCxxh3 Flags = 1 << iota
   104  
   105  	// FlagEOF the stream (either direction) is at EOF.
   106  	FlagEOF
   107  
   108  	// FlagStateless indicates the message is stateless.
   109  	// This will retain clients across reconnections or
   110  	// if sequence numbers are unexpected.
   111  	FlagStateless
   112  
   113  	// FlagPayloadIsErr can be used by individual ops to signify that
   114  	// The payload is a string error converted to byte slice.
   115  	FlagPayloadIsErr
   116  
   117  	// FlagPayloadIsZero means that payload is 0-length slice and not nil.
   118  	FlagPayloadIsZero
   119  
   120  	// FlagSubroute indicates that the message has subroute.
   121  	// Subroute will be 32 bytes long and added before any CRC.
   122  	FlagSubroute
   123  )
   124  
   125  // This struct cannot be changed and retain backwards compatibility.
   126  // If changed, endpoint version must be bumped.
   127  //
   128  //msgp:tuple message
   129  type message struct {
   130  	MuxID      uint64    // Mux to receive message if any.
   131  	Seq        uint32    // Sequence number.
   132  	DeadlineMS uint32    // If non-zero, milliseconds until deadline (max 1193h2m47.295s, ~49 days)
   133  	Handler    HandlerID // ID of handler if invoking a remote handler.
   134  	Op         Op        // Operation. Other fields change based on this value.
   135  	Flags      Flags     // Optional flags.
   136  	Payload    []byte    // Optional payload.
   137  }
   138  
   139  // Flags is a set of flags set on a message.
   140  type Flags uint8
   141  
   142  func (m message) String() string {
   143  	var res []string
   144  	if m.MuxID != 0 {
   145  		res = append(res, fmt.Sprintf("MuxID: %v", m.MuxID))
   146  	}
   147  	if m.Seq != 0 {
   148  		res = append(res, fmt.Sprintf("Seq: %v", m.Seq))
   149  	}
   150  	if m.DeadlineMS != 0 {
   151  		res = append(res, fmt.Sprintf("Deadline: %vms", m.DeadlineMS))
   152  	}
   153  	if m.Handler != handlerInvalid {
   154  		res = append(res, fmt.Sprintf("Handler: %v", m.Handler))
   155  	}
   156  	if m.Op != 0 {
   157  		res = append(res, fmt.Sprintf("Op: %v", m.Op))
   158  	}
   159  	res = append(res, fmt.Sprintf("Flags: %s", m.Flags.String()))
   160  	if len(m.Payload) != 0 {
   161  		res = append(res, fmt.Sprintf("Payload: %v", bytesOrLength(m.Payload)))
   162  	}
   163  	return "{" + strings.Join(res, ", ") + "}"
   164  }
   165  
   166  func (f Flags) String() string {
   167  	var res []string
   168  	if f&FlagCRCxxh3 != 0 {
   169  		res = append(res, "CRC")
   170  	}
   171  	if f&FlagEOF != 0 {
   172  		res = append(res, "EOF")
   173  	}
   174  	if f&FlagStateless != 0 {
   175  		res = append(res, "SL")
   176  	}
   177  	if f&FlagPayloadIsErr != 0 {
   178  		res = append(res, "ERR")
   179  	}
   180  	if f&FlagPayloadIsZero != 0 {
   181  		res = append(res, "ZERO")
   182  	}
   183  	if f&FlagSubroute != 0 {
   184  		res = append(res, "SUB")
   185  	}
   186  	return "[" + strings.Join(res, ",") + "]"
   187  }
   188  
   189  // Set one or more flags on f.
   190  func (f *Flags) Set(flags Flags) {
   191  	*f |= flags
   192  }
   193  
   194  // Clear one or more flags on f.
   195  func (f *Flags) Clear(flags Flags) {
   196  	*f &^= flags
   197  }
   198  
   199  // parse an incoming message.
   200  func (m *message) parse(b []byte) (*subHandlerID, []byte, error) {
   201  	var sub *subHandlerID
   202  	if m.Payload == nil {
   203  		m.Payload = GetByteBuffer()[:0]
   204  	}
   205  	h, err := m.UnmarshalMsg(b)
   206  	if err != nil {
   207  		return nil, nil, fmt.Errorf("read write: %v", err)
   208  	}
   209  	if len(m.Payload) == 0 && m.Flags&FlagPayloadIsZero == 0 {
   210  		PutByteBuffer(m.Payload)
   211  		m.Payload = nil
   212  	}
   213  	if m.Flags&FlagCRCxxh3 != 0 {
   214  		const hashLen = 4
   215  		if len(h) < hashLen {
   216  			return nil, nil, fmt.Errorf("want crc len 4, got %v", len(h))
   217  		}
   218  		got := uint32(xxh3.Hash(b[:len(b)-hashLen]))
   219  		want := binary.LittleEndian.Uint32(h[len(h)-hashLen:])
   220  		if got != want {
   221  			return nil, nil, fmt.Errorf("crc mismatch: 0x%08x (given) != 0x%08x (bytes)", want, got)
   222  		}
   223  		h = h[:len(h)-hashLen]
   224  	}
   225  	// Extract subroute if any.
   226  	if m.Flags&FlagSubroute != 0 {
   227  		if len(h) < 32 {
   228  			return nil, nil, fmt.Errorf("want subroute len 32, got %v", len(h))
   229  		}
   230  		subID := (*[32]byte)(h[len(h)-32:])
   231  		sub = (*subHandlerID)(subID)
   232  		// Add if more modifications to h is needed
   233  		h = h[:len(h)-32]
   234  	}
   235  	return sub, h, nil
   236  }
   237  
   238  // setZeroPayloadFlag will clear or set the FlagPayloadIsZero if
   239  // m.Payload is length 0, but not nil.
   240  func (m *message) setZeroPayloadFlag() {
   241  	m.Flags &^= FlagPayloadIsZero
   242  	if len(m.Payload) == 0 && m.Payload != nil {
   243  		m.Flags |= FlagPayloadIsZero
   244  	}
   245  }
   246  
   247  type receiver interface {
   248  	msgp.Unmarshaler
   249  	Op() Op
   250  }
   251  
   252  type sender interface {
   253  	msgp.MarshalSizer
   254  	Op() Op
   255  }
   256  
   257  type connectReq struct {
   258  	ID   [16]byte
   259  	Host string
   260  }
   261  
   262  func (connectReq) Op() Op {
   263  	return OpConnect
   264  }
   265  
   266  type connectResp struct {
   267  	ID             [16]byte
   268  	Accepted       bool
   269  	RejectedReason string
   270  }
   271  
   272  func (connectResp) Op() Op {
   273  	return OpConnectResponse
   274  }
   275  
   276  type muxConnectError struct {
   277  	Error string
   278  }
   279  
   280  func (muxConnectError) Op() Op {
   281  	return OpMuxConnectError
   282  }
   283  
   284  type pongMsg struct {
   285  	NotFound bool    `msg:"nf"`
   286  	Err      *string `msg:"e,allownil"`
   287  }
   288  
   289  func (pongMsg) Op() Op {
   290  	return OpPong
   291  }