init
This commit is contained in:
		
							parent
							
								
									7dfeef5599
								
							
						
					
					
						commit
						d4226d3b66
					
				|  | @ -0,0 +1,59 @@ | ||||||
|  | package auth | ||||||
|  | 
 | ||||||
|  | import "strings" | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	auths = make(map[string]Authentication) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Authentication for Network server
 | ||||||
|  | type Authentication interface { | ||||||
|  | 	// Init authentication initialize arguments
 | ||||||
|  | 	Init(args ...string) | ||||||
|  | 	// Authenticate authentication client's credential
 | ||||||
|  | 	Authenticate(payload string) bool | ||||||
|  | 	// Name authentication name
 | ||||||
|  | 	Name() string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Register register authentication
 | ||||||
|  | func Register(authentication Authentication) { | ||||||
|  | 	auths[authentication.Name()] = authentication | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetAuth get authentication by name
 | ||||||
|  | func GetAuth(name string) (Authentication, bool) { | ||||||
|  | 	auth, ok := auths[name] | ||||||
|  | 	return auth, ok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Credential client credential
 | ||||||
|  | type Credential struct { | ||||||
|  | 	name    string | ||||||
|  | 	payload string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewCredential create client credential
 | ||||||
|  | func NewCredential(payload string) *Credential { | ||||||
|  | 	idx := strings.Index(payload, ":") | ||||||
|  | 	if idx != -1 { | ||||||
|  | 		authName := payload[:idx] | ||||||
|  | 		idx++ | ||||||
|  | 		authPayload := payload[idx:] | ||||||
|  | 		return &Credential{ | ||||||
|  | 			name:    authName, | ||||||
|  | 			payload: authPayload, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return &Credential{name: "none"} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Payload client credential payload
 | ||||||
|  | func (c *Credential) Payload() string { | ||||||
|  | 	return c.payload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Name client credential name
 | ||||||
|  | func (c *Credential) Name() string { | ||||||
|  | 	return c.name | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | @startuml | ||||||
|  | namespace auth { | ||||||
|  |     interface Authentication  { | ||||||
|  |         + Init(args ...string) | ||||||
|  |         + Authenticate(payload string) bool | ||||||
|  |         + Name() string | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class Credential << (S,Aquamarine) >> { | ||||||
|  |         - name string | ||||||
|  |         - payload string | ||||||
|  | 
 | ||||||
|  |         + Payload() string | ||||||
|  |         + Name() string | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @enduml | ||||||
|  | @ -0,0 +1,465 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"git.hpds.cc/Component/network/hpds_err" | ||||||
|  | 	"git.hpds.cc/Component/network/id" | ||||||
|  | 	pkgtls "git.hpds.cc/Component/network/tls" | ||||||
|  | 	"net" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/lucas-clemente/quic-go" | ||||||
|  | 
 | ||||||
|  | 	"git.hpds.cc/Component/network/auth" | ||||||
|  | 	"git.hpds.cc/Component/network/frame" | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ClientOption client options
 | ||||||
|  | type ClientOption func(*ClientOptions) | ||||||
|  | 
 | ||||||
|  | // ConnState describes the state of the connection.
 | ||||||
|  | type ConnState = string | ||||||
|  | 
 | ||||||
|  | // Client is the abstraction of a HPDS-Client. a HPDS-Client can be
 | ||||||
|  | // Protocol Gateway, Message Queue or StreamFunction.
 | ||||||
|  | type Client struct { | ||||||
|  | 	name       string                     // name of the client
 | ||||||
|  | 	clientId   string                     // id of the client
 | ||||||
|  | 	clientType ClientType                 // type of the connection
 | ||||||
|  | 	conn       quic.Connection            // quic connection
 | ||||||
|  | 	stream     quic.Stream                // quic stream
 | ||||||
|  | 	state      ConnState                  // state of the connection
 | ||||||
|  | 	processor  func(*frame.DataFrame)     // functions to invoke when data arrived
 | ||||||
|  | 	receiver   func(*frame.BackFlowFrame) // functions to invoke when data is processed
 | ||||||
|  | 	addr       string                     // the address of server connected to
 | ||||||
|  | 	mu         sync.Mutex | ||||||
|  | 	opts       ClientOptions | ||||||
|  | 	localAddr  string // client local addr, it will be changed on reconnect
 | ||||||
|  | 	logger     log.Logger | ||||||
|  | 	errChan    chan error | ||||||
|  | 	closeChan  chan bool | ||||||
|  | 	closed     bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewClient creates a new HPDS-Client.
 | ||||||
|  | func NewClient(appName string, connType ClientType, opts ...ClientOption) *Client { | ||||||
|  | 	c := &Client{ | ||||||
|  | 		name:       appName, | ||||||
|  | 		clientId:   id.New(), | ||||||
|  | 		clientType: connType, | ||||||
|  | 		state:      ConnStateReady, | ||||||
|  | 		opts:       ClientOptions{}, | ||||||
|  | 		errChan:    make(chan error), | ||||||
|  | 		closeChan:  make(chan bool), | ||||||
|  | 	} | ||||||
|  | 	c.Init(opts...) | ||||||
|  | 	once.Do(func() { | ||||||
|  | 		c.init() | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Init the options.
 | ||||||
|  | func (c *Client) Init(opts ...ClientOption) error { | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&c.opts) | ||||||
|  | 	} | ||||||
|  | 	return c.initOptions() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Connect connects to HPDS-MessageQueue.
 | ||||||
|  | func (c *Client) Connect(ctx context.Context, addr string) error { | ||||||
|  | 
 | ||||||
|  | 	// TODO: refactor this later as a Connection Manager
 | ||||||
|  | 	// reconnect
 | ||||||
|  | 	// for download mq
 | ||||||
|  | 	// If you do not check for errors, the connection will be automatically reconnected
 | ||||||
|  | 	go c.reconnect(ctx, addr) | ||||||
|  | 
 | ||||||
|  | 	// connect
 | ||||||
|  | 	if err := c.connect(ctx, addr); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Client) connect(ctx context.Context, addr string) error { | ||||||
|  | 	c.addr = addr | ||||||
|  | 	c.state = ConnStateConnecting | ||||||
|  | 
 | ||||||
|  | 	// create quic connection
 | ||||||
|  | 	conn, err := quic.DialAddrContext(ctx, addr, c.opts.TLSConfig, c.opts.QuicConfig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.state = ConnStateDisconnected | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// quic stream
 | ||||||
|  | 	stream, err := conn.OpenStreamSync(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.state = ConnStateDisconnected | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.stream = stream | ||||||
|  | 	c.conn = conn | ||||||
|  | 
 | ||||||
|  | 	c.state = ConnStateAuthenticating | ||||||
|  | 	// send handshake
 | ||||||
|  | 	handshake := frame.NewHandshakeFrame( | ||||||
|  | 		c.name, | ||||||
|  | 		c.clientId, | ||||||
|  | 		byte(c.clientType), | ||||||
|  | 		c.opts.ObserveDataTags, | ||||||
|  | 		c.opts.Credential.Name(), | ||||||
|  | 		c.opts.Credential.Payload(), | ||||||
|  | 	) | ||||||
|  | 	err = c.WriteFrame(handshake) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.state = ConnStateRejected | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	c.state = ConnStateConnected | ||||||
|  | 	c.localAddr = c.conn.LocalAddr().String() | ||||||
|  | 
 | ||||||
|  | 	c.logger.Printf("%s [%s][%s](%s) is connected to HPDS-MQ %s", ClientLogPrefix, c.name, c.clientId, c.localAddr, addr) | ||||||
|  | 
 | ||||||
|  | 	// receiving frames
 | ||||||
|  | 	go c.handleFrame() | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // handleFrame handles the logic when receiving frame from server.
 | ||||||
|  | func (c *Client) handleFrame() { | ||||||
|  | 	// transform raw QUIC stream to wire format
 | ||||||
|  | 	fs := NewFrameStream(c.stream) | ||||||
|  | 	for { | ||||||
|  | 		c.logger.Debugf("%shandleFrame connection state=%v", ClientLogPrefix, c.state) | ||||||
|  | 		// this will block until a frame is received
 | ||||||
|  | 		f, err := fs.ReadFrame() | ||||||
|  | 		if err != nil { | ||||||
|  | 			defer c.stream.Close() | ||||||
|  | 			// defer c.conn.CloseWithError(0xD0, err.Error())
 | ||||||
|  | 
 | ||||||
|  | 			c.logger.Debugf("%shandleFrame(): %T | %v", ClientLogPrefix, err, err) | ||||||
|  | 			if e, ok := err.(*quic.IdleTimeoutError); ok { | ||||||
|  | 				c.logger.Errorf("%sconnection timeout, err=%v, mq addr=%s", ClientLogPrefix, e, c.addr) | ||||||
|  | 				c.setState(ConnStateDisconnected) | ||||||
|  | 			} else if e, ok := err.(*quic.ApplicationError); ok { | ||||||
|  | 				c.logger.Infof("%sapplication error, err=%v, errcode=%v", ClientLogPrefix, e, e.ErrorCode) | ||||||
|  | 				if hpds_err.Is(e.ErrorCode, hpds_err.ErrorCodeRejected) { | ||||||
|  | 					// if connection is rejected(eg: authenticate fails) from server
 | ||||||
|  | 					c.logger.Errorf("%sIllegal client, server rejected.", ClientLogPrefix) | ||||||
|  | 					c.setState(ConnStateRejected) | ||||||
|  | 					break | ||||||
|  | 				} else if hpds_err.Is(e.ErrorCode, hpds_err.ErrorCodeClientAbort) { | ||||||
|  | 					// client abort
 | ||||||
|  | 					c.logger.Infof("%sclient close the connection", ClientLogPrefix) | ||||||
|  | 					c.setState(ConnStateAborted) | ||||||
|  | 					break | ||||||
|  | 				} else if hpds_err.Is(e.ErrorCode, hpds_err.ErrorCodeGoaway) { | ||||||
|  | 					// server goaway
 | ||||||
|  | 					c.logger.Infof("%sserver goaway the connection", ClientLogPrefix) | ||||||
|  | 					c.setState(ConnStateGoaway) | ||||||
|  | 					break | ||||||
|  | 				} else if hpds_err.Is(e.ErrorCode, hpds_err.ErrorCodeHandshake) { | ||||||
|  | 					// handshake
 | ||||||
|  | 					c.logger.Errorf("%shandshake fails", ClientLogPrefix) | ||||||
|  | 					c.setState(ConnStateRejected) | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} else if errors.Is(err, net.ErrClosed) { | ||||||
|  | 				// if client close the connection, net.ErrClosed will be raised
 | ||||||
|  | 				c.logger.Errorf("%sconnection is closed, err=%v", ClientLogPrefix, err) | ||||||
|  | 				c.setState(ConnStateDisconnected) | ||||||
|  | 				// by quic-go IdleTimeoutError after connection's KeepAlive config.
 | ||||||
|  | 				break | ||||||
|  | 			} else { | ||||||
|  | 				// any error occurred, we should close the stream
 | ||||||
|  | 				// after this, conn.AcceptStream() will raise the error
 | ||||||
|  | 				c.setState(ConnStateClosed) | ||||||
|  | 				c.conn.CloseWithError(hpds_err.To(hpds_err.ErrorCodeUnknown), err.Error()) | ||||||
|  | 				c.logger.Errorf("%sunknown error occurred, err=%v, state=%v", ClientLogPrefix, err, c.getState()) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if f == nil { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		// read frame
 | ||||||
|  | 		// first, get frame type
 | ||||||
|  | 		frameType := f.Type() | ||||||
|  | 		c.logger.Debugf("%stype=%s, frame=%# x", ClientLogPrefix, frameType, frame.Shortly(f.Encode())) | ||||||
|  | 		switch frameType { | ||||||
|  | 		case frame.TagOfHandshakeFrame: | ||||||
|  | 			if v, ok := f.(*frame.HandshakeFrame); ok { | ||||||
|  | 				c.logger.Debugf("%sreceive HandshakeFrame, name=%v", ClientLogPrefix, v.Name) | ||||||
|  | 			} | ||||||
|  | 		case frame.TagOfPongFrame: | ||||||
|  | 			c.setState(ConnStatePong) | ||||||
|  | 		case frame.TagOfAcceptedFrame: | ||||||
|  | 			c.setState(ConnStateAccepted) | ||||||
|  | 		case frame.TagOfRejectedFrame: | ||||||
|  | 			c.setState(ConnStateRejected) | ||||||
|  | 			if v, ok := f.(*frame.RejectedFrame); ok { | ||||||
|  | 				c.logger.Errorf("%s receive RejectedFrame, message=%s", ClientLogPrefix, v.Message()) | ||||||
|  | 				c.conn.CloseWithError(hpds_err.To(hpds_err.ErrorCodeRejected), v.Message()) | ||||||
|  | 				c.errChan <- errors.New(v.Message()) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		case frame.TagOfGoawayFrame: | ||||||
|  | 			c.setState(ConnStateGoaway) | ||||||
|  | 			if v, ok := f.(*frame.GoawayFrame); ok { | ||||||
|  | 				c.logger.Errorf("%s️ receive GoawayFrame, message=%s", ClientLogPrefix, v.Message()) | ||||||
|  | 				c.conn.CloseWithError(hpds_err.To(hpds_err.ErrorCodeGoaway), v.Message()) | ||||||
|  | 				c.errChan <- errors.New(v.Message()) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		case frame.TagOfDataFrame: // DataFrame carries user's data
 | ||||||
|  | 			if v, ok := f.(*frame.DataFrame); ok { | ||||||
|  | 				c.setState(ConnStateTransportData) | ||||||
|  | 				c.logger.Debugf("%sreceive DataFrame, tag=%#x, tid=%s, carry=%# x", ClientLogPrefix, v.GetDataTag(), v.TransactionId(), v.GetCarriage()) | ||||||
|  | 				if c.processor == nil { | ||||||
|  | 					c.logger.Warnf("%sprocessor is nil", ClientLogPrefix) | ||||||
|  | 				} else { | ||||||
|  | 					// TODO: should c.processor accept a DataFrame as parameter?
 | ||||||
|  | 					// c.processor(v.GetDataTagID(), v.GetCarriage(), v.GetMetaFrame())
 | ||||||
|  | 					c.processor(v) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case frame.TagOfBackFlowFrame: | ||||||
|  | 			if v, ok := f.(*frame.BackFlowFrame); ok { | ||||||
|  | 				c.logger.Debugf("%sreceive BackFlowFrame, tag=%#x, carry=%# x", ClientLogPrefix, v.GetDataTag(), v.GetCarriage()) | ||||||
|  | 				if c.receiver == nil { | ||||||
|  | 					c.logger.Warnf("%sreceiver is nil", ClientLogPrefix) | ||||||
|  | 				} else { | ||||||
|  | 					c.setState(ConnStateBackFlow) | ||||||
|  | 					c.receiver(v) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		default: | ||||||
|  | 			c.logger.Errorf("%sunknown signal", ClientLogPrefix) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close the client.
 | ||||||
|  | func (c *Client) Close() (err error) { | ||||||
|  | 	if c.conn != nil { | ||||||
|  | 		c.logger.Printf("%sclose the connection, name:%s, id:%s, addr:%s", ClientLogPrefix, c.name, c.clientId, c.conn.RemoteAddr().String()) | ||||||
|  | 	} | ||||||
|  | 	if c.stream != nil { | ||||||
|  | 		err = c.stream.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.logger.Errorf("%s stream.Close(): %v", ClientLogPrefix, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if c.conn != nil { | ||||||
|  | 		err = c.conn.CloseWithError(0, "client-ask-to-close-this-connection") | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.logger.Errorf("%s connection.Close(): %v", ClientLogPrefix, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// close channel
 | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	if !c.closed { | ||||||
|  | 		close(c.errChan) | ||||||
|  | 		close(c.closeChan) | ||||||
|  | 		c.closed = true | ||||||
|  | 	} | ||||||
|  | 	c.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WriteFrame writes a frame to the connection, gurantee threadsafe.
 | ||||||
|  | func (c *Client) WriteFrame(frm frame.Frame) error { | ||||||
|  | 	// write on QUIC stream
 | ||||||
|  | 	if c.stream == nil { | ||||||
|  | 		return errors.New("stream is nil") | ||||||
|  | 	} | ||||||
|  | 	if c.state == ConnStateDisconnected || c.state == ConnStateRejected { | ||||||
|  | 		return fmt.Errorf("client connection state is %s", c.state) | ||||||
|  | 	} | ||||||
|  | 	c.logger.Debugf("%s[%s](%s)@%s WriteFrame() will write frame: %s", ClientLogPrefix, c.name, c.localAddr, c.state, frm.Type()) | ||||||
|  | 
 | ||||||
|  | 	data := frm.Encode() | ||||||
|  | 	// emit raw bytes of Frame
 | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	n, err := c.stream.Write(data) | ||||||
|  | 	c.mu.Unlock() | ||||||
|  | 	c.logger.Debugf("%sWriteFrame() wrote n=%d, data=%# x", ClientLogPrefix, n, frame.Shortly(data)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.setState(ConnStateDisconnected) | ||||||
|  | 		// c.state = ConnStateDisconnected
 | ||||||
|  | 		if e, ok := err.(*quic.IdleTimeoutError); ok { | ||||||
|  | 			c.logger.Errorf("%sWriteFrame() connection timeout, err=%v", ClientLogPrefix, e) | ||||||
|  | 		} else { | ||||||
|  | 			c.logger.Errorf("%sWriteFrame() wrote error=%v", ClientLogPrefix, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if n != len(data) { | ||||||
|  | 		err := errors.New("[client] hpds Client .Write() wrote error") | ||||||
|  | 		c.logger.Errorf("%s error:%v", ClientLogPrefix, err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // update connection state
 | ||||||
|  | func (c *Client) setState(state ConnState) { | ||||||
|  | 	c.logger.Debugf("setState to:%s", state) | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	c.state = state | ||||||
|  | 	c.mu.Unlock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // getState get connection state
 | ||||||
|  | func (c *Client) getState() ConnState { | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	defer c.mu.Unlock() | ||||||
|  | 	return c.state | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // update connection local addr
 | ||||||
|  | func (c *Client) setLocalAddr(addr string) { | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	c.localAddr = addr | ||||||
|  | 	c.mu.Unlock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetDataFrameObserver sets the data frame handler.
 | ||||||
|  | func (c *Client) SetDataFrameObserver(fn func(*frame.DataFrame)) { | ||||||
|  | 	c.processor = fn | ||||||
|  | 	c.logger.Debugf("%sSetDataFrameObserver(%v)", ClientLogPrefix, c.processor) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetBackFlowFrameObserver sets the backflow frame handler.
 | ||||||
|  | func (c *Client) SetBackFlowFrameObserver(fn func(*frame.BackFlowFrame)) { | ||||||
|  | 	c.receiver = fn | ||||||
|  | 	c.logger.Debugf("%sSetBackFlowFrameObserver(%v)", ClientLogPrefix, c.receiver) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // reconnect the connection between client and server.
 | ||||||
|  | func (c *Client) reconnect(ctx context.Context, addr string) { | ||||||
|  | 	t := time.NewTicker(1 * time.Second) | ||||||
|  | 	defer t.Stop() | ||||||
|  | 
 | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			c.logger.Debugf("%s[%s](%s) context.Done()", ClientLogPrefix, c.name, c.localAddr) | ||||||
|  | 			return | ||||||
|  | 		case <-c.closeChan: | ||||||
|  | 			c.logger.Debugf("%s[%s](%s) close channel", ClientLogPrefix, c.name, c.localAddr) | ||||||
|  | 			return | ||||||
|  | 		case <-t.C: | ||||||
|  | 			if c.getState() == ConnStateDisconnected { | ||||||
|  | 				c.logger.Printf("%s[%s][%s](%s) is reconnecting to HPDS-MQ %s...", ClientLogPrefix, c.name, c.clientId, c.localAddr, addr) | ||||||
|  | 				err := c.connect(ctx, addr) | ||||||
|  | 				if err != nil { | ||||||
|  | 					c.logger.Errorf("%s[%s][%s](%s) reconnect error:%v", ClientLogPrefix, c.name, c.clientId, c.localAddr, err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Client) init() { | ||||||
|  | 	// // tracing
 | ||||||
|  | 	// _, _, err := tracing.NewTracerProvider(c.name)
 | ||||||
|  | 	// if err != nil {
 | ||||||
|  | 	// 	logger.Errorf("tracing: %v", err)
 | ||||||
|  | 	// }
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ServerAddr returns the address of the server.
 | ||||||
|  | func (c *Client) ServerAddr() string { | ||||||
|  | 	return c.addr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // initOptions init options defaults
 | ||||||
|  | func (c *Client) initOptions() error { | ||||||
|  | 	// logger
 | ||||||
|  | 	if c.logger == nil { | ||||||
|  | 		if c.opts.Logger != nil { | ||||||
|  | 			c.logger = c.opts.Logger | ||||||
|  | 		} else { | ||||||
|  | 			c.logger = log.Default() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// observe tag list
 | ||||||
|  | 	if c.opts.ObserveDataTags == nil { | ||||||
|  | 		c.opts.ObserveDataTags = make([]byte, 0) | ||||||
|  | 	} | ||||||
|  | 	// credential
 | ||||||
|  | 	if c.opts.Credential == nil { | ||||||
|  | 		c.opts.Credential = auth.NewCredential("") | ||||||
|  | 	} | ||||||
|  | 	// tls config
 | ||||||
|  | 	if c.opts.TLSConfig == nil { | ||||||
|  | 		tc, err := pkgtls.CreateClientTLSConfig() | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.logger.Errorf("%sCreateClientTLSConfig: %v", ClientLogPrefix, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		c.opts.TLSConfig = tc | ||||||
|  | 	} | ||||||
|  | 	// quic config
 | ||||||
|  | 	if c.opts.QuicConfig == nil { | ||||||
|  | 		c.opts.QuicConfig = &quic.Config{ | ||||||
|  | 			Versions:                       []quic.VersionNumber{quic.Version1, quic.VersionDraft29}, | ||||||
|  | 			MaxIdleTimeout:                 time.Second * 40, | ||||||
|  | 			KeepAlivePeriod:                time.Second * 20, | ||||||
|  | 			MaxIncomingStreams:             1000, | ||||||
|  | 			MaxIncomingUniStreams:          1000, | ||||||
|  | 			HandshakeIdleTimeout:           time.Second * 3, | ||||||
|  | 			InitialStreamReceiveWindow:     1024 * 1024 * 2, | ||||||
|  | 			InitialConnectionReceiveWindow: 1024 * 1024 * 2, | ||||||
|  | 			TokenStore:                     quic.NewLRUTokenStore(10, 5), | ||||||
|  | 			DisablePathMTUDiscovery:        true, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// credential
 | ||||||
|  | 	if c.opts.Credential != nil { | ||||||
|  | 		c.logger.Printf("%suse credential: [%s]", ClientLogPrefix, c.opts.Credential.Name()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetObserveDataTags set the data tag list that will be observed.
 | ||||||
|  | // Deprecated: use hpds.WithObserveDataTags instead
 | ||||||
|  | func (c *Client) SetObserveDataTags(tag ...byte) { | ||||||
|  | 	c.opts.ObserveDataTags = append(c.opts.ObserveDataTags, tag...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Logger get client's logger instance, you can customize this using `hpds.WithLogger`
 | ||||||
|  | func (c *Client) Logger() log.Logger { | ||||||
|  | 	return c.logger | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetErrorHandler set error handler
 | ||||||
|  | func (c *Client) SetErrorHandler(fn func(err error)) { | ||||||
|  | 	if fn != nil { | ||||||
|  | 		go func() { | ||||||
|  | 			err := <-c.errChan | ||||||
|  | 			if err != nil { | ||||||
|  | 				fn(err) | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ClientId return the client ID
 | ||||||
|  | func (c *Client) ClientId() string { | ||||||
|  | 	return c.clientId | ||||||
|  | } | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"github.com/lucas-clemente/quic-go" | ||||||
|  | 
 | ||||||
|  | 	"git.hpds.cc/Component/network/auth" | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ClientOptions are the options for HPDS client.
 | ||||||
|  | type ClientOptions struct { | ||||||
|  | 	ObserveDataTags []byte | ||||||
|  | 	QuicConfig      *quic.Config | ||||||
|  | 	TLSConfig       *tls.Config | ||||||
|  | 	Credential      *auth.Credential | ||||||
|  | 	Logger          log.Logger | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithObserveDataTags sets data tag list for the client.
 | ||||||
|  | func WithObserveDataTags(tags ...byte) ClientOption { | ||||||
|  | 	return func(o *ClientOptions) { | ||||||
|  | 		o.ObserveDataTags = tags | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithCredential sets the client credential method (used by client).
 | ||||||
|  | func WithCredential(payload string) ClientOption { | ||||||
|  | 	return func(o *ClientOptions) { | ||||||
|  | 		o.Credential = auth.NewCredential(payload) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithClientTLSConfig sets tls config for the client.
 | ||||||
|  | func WithClientTLSConfig(tc *tls.Config) ClientOption { | ||||||
|  | 	return func(o *ClientOptions) { | ||||||
|  | 		o.TLSConfig = tc | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithClientQuicConfig sets quic config for the client.
 | ||||||
|  | func WithClientQuicConfig(qc *quic.Config) ClientOption { | ||||||
|  | 	return func(o *ClientOptions) { | ||||||
|  | 		o.QuicConfig = qc | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithLogger sets logger for the client.
 | ||||||
|  | func WithLogger(logger log.Logger) ClientOption { | ||||||
|  | 	return func(o *ClientOptions) { | ||||||
|  | 		o.Logger = logger | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// ClientTypeNone is connection type "None".
 | ||||||
|  | 	ClientTypeNone ClientType = 0xFF | ||||||
|  | 	// ClientTypeProtocolGateway is connection type "Protocol Gateway".
 | ||||||
|  | 	ClientTypeProtocolGateway ClientType = 0x5F | ||||||
|  | 	// ClientTypeMessageQueue is connection type "Message Queue".
 | ||||||
|  | 	ClientTypeMessageQueue ClientType = 0x5E | ||||||
|  | 	// ClientTypeStreamFunction is connection type "Stream Function".
 | ||||||
|  | 	ClientTypeStreamFunction ClientType = 0x5D | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ClientType represents the connection type.
 | ||||||
|  | type ClientType byte | ||||||
|  | 
 | ||||||
|  | func (c ClientType) String() string { | ||||||
|  | 	switch c { | ||||||
|  | 	case ClientTypeProtocolGateway: | ||||||
|  | 		return "Source" | ||||||
|  | 	case ClientTypeMessageQueue: | ||||||
|  | 		return "Message Queue" | ||||||
|  | 	case ClientTypeStreamFunction: | ||||||
|  | 		return "Stream Function" | ||||||
|  | 	default: | ||||||
|  | 		return "None" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,95 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"git.hpds.cc/Component/network/frame" | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | 	"io" | ||||||
|  | 	"sync" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Connection wraps the specific io connections (typically quic.Connection) to transfer coder frames
 | ||||||
|  | type Connection interface { | ||||||
|  | 	io.Closer | ||||||
|  | 
 | ||||||
|  | 	// Name returns the name of the connection, which is set by clients
 | ||||||
|  | 	Name() string | ||||||
|  | 	// ClientId connection client ID
 | ||||||
|  | 	ClientId() string | ||||||
|  | 	// ClientType returns the type of the client (Protocol Gateway | Message Queue | Stream Function)
 | ||||||
|  | 	ClientType() ClientType | ||||||
|  | 	// Metadata returns the extra info of the application
 | ||||||
|  | 	Metadata() Metadata | ||||||
|  | 	// Write should goroutine-safely send coder frames to peer side
 | ||||||
|  | 	Write(f frame.Frame) error | ||||||
|  | 	// ObserveDataTags observed data tags
 | ||||||
|  | 	ObserveDataTags() []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type connection struct { | ||||||
|  | 	name       string | ||||||
|  | 	clientType ClientType | ||||||
|  | 	metadata   Metadata | ||||||
|  | 	stream     io.ReadWriteCloser | ||||||
|  | 	clientId   string | ||||||
|  | 	observed   []byte // observed data tags
 | ||||||
|  | 	mu         sync.Mutex | ||||||
|  | 	closed     bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newConnection(name string, clientId string, clientType ClientType, metadata Metadata, | ||||||
|  | 	stream io.ReadWriteCloser, observed []byte) Connection { | ||||||
|  | 	return &connection{ | ||||||
|  | 		name:       name, | ||||||
|  | 		clientId:   clientId, | ||||||
|  | 		clientType: clientType, | ||||||
|  | 		observed:   observed, | ||||||
|  | 		metadata:   metadata, | ||||||
|  | 		stream:     stream, | ||||||
|  | 		closed:     false, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close implements io.Close interface
 | ||||||
|  | func (c *connection) Close() error { | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	defer c.mu.Unlock() | ||||||
|  | 	c.closed = true | ||||||
|  | 	return c.stream.Close() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Name returns the name of the connection, which is set by clients
 | ||||||
|  | func (c *connection) Name() string { | ||||||
|  | 	return c.name | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ClientType returns the type of the connection (Protocol Gateway | Message Queue | Stream Function )
 | ||||||
|  | func (c *connection) ClientType() ClientType { | ||||||
|  | 	return c.clientType | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Metadata returns the extra info of the application
 | ||||||
|  | func (c *connection) Metadata() Metadata { | ||||||
|  | 	return c.metadata | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Write should goroutine-safely send coder frames to peer side
 | ||||||
|  | func (c *connection) Write(f frame.Frame) error { | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	defer c.mu.Unlock() | ||||||
|  | 	if c.closed { | ||||||
|  | 		log.Warnf("%sclient stream is closed: %s", ServerLogPrefix, c.clientId) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	_, err := c.stream.Write(f.Encode()) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ObserveDataTags observed data tags
 | ||||||
|  | func (c *connection) ObserveDataTags() []byte { | ||||||
|  | 	return c.observed | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ClientId connection client id
 | ||||||
|  | func (c *connection) ClientId() string { | ||||||
|  | 	return c.clientId | ||||||
|  | } | ||||||
|  | @ -0,0 +1,87 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | 	"sync" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var _ Connector = &connector{} | ||||||
|  | 
 | ||||||
|  | // Connector is an interface to manage the connections and applications.
 | ||||||
|  | type Connector interface { | ||||||
|  | 	// Add a connection.
 | ||||||
|  | 	Add(connId string, conn Connection) | ||||||
|  | 	// Remove a connection.
 | ||||||
|  | 	Remove(connId string) | ||||||
|  | 	// Get a connection by connection id.
 | ||||||
|  | 	Get(connId string) Connection | ||||||
|  | 	// GetSnapshot gets the snapshot of all connections.
 | ||||||
|  | 	GetSnapshot() map[string]string | ||||||
|  | 	// GetProtocolGatewayConnections gets the connections by Protocol Gateway observe tags.
 | ||||||
|  | 	GetProtocolGatewayConnections(sourceId string, tags byte) []Connection | ||||||
|  | 	// Clean the connector.
 | ||||||
|  | 	Clean() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type connector struct { | ||||||
|  | 	conns sync.Map | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newConnector() Connector { | ||||||
|  | 	return &connector{conns: sync.Map{}} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add a connection.
 | ||||||
|  | func (c *connector) Add(connID string, conn Connection) { | ||||||
|  | 	log.Debugf("%sconnector add: connId=%s", ServerLogPrefix, connID) | ||||||
|  | 	c.conns.Store(connID, conn) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Remove a connection.
 | ||||||
|  | func (c *connector) Remove(connID string) { | ||||||
|  | 	log.Debugf("%sconnector remove: connId=%s", ServerLogPrefix, connID) | ||||||
|  | 	c.conns.Delete(connID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get a connection by connection id.
 | ||||||
|  | func (c *connector) Get(connID string) Connection { | ||||||
|  | 	log.Debugf("%sconnector get connection: connId=%s", ServerLogPrefix, connID) | ||||||
|  | 	if conn, ok := c.conns.Load(connID); ok { | ||||||
|  | 		return conn.(Connection) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetProtocolGatewayConnections gets the Protocol Gateway connection by tag.
 | ||||||
|  | func (c *connector) GetProtocolGatewayConnections(sourceId string, tag byte) []Connection { | ||||||
|  | 	conns := make([]Connection, 0) | ||||||
|  | 
 | ||||||
|  | 	c.conns.Range(func(key interface{}, val interface{}) bool { | ||||||
|  | 		conn := val.(Connection) | ||||||
|  | 		for _, v := range conn.ObserveDataTags() { | ||||||
|  | 			if v == tag && conn.ClientType() == ClientTypeProtocolGateway && conn.ClientId() == sourceId { | ||||||
|  | 				conns = append(conns, conn) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return conns | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetSnapshot gets the snapshot of all connections.
 | ||||||
|  | func (c *connector) GetSnapshot() map[string]string { | ||||||
|  | 	result := make(map[string]string) | ||||||
|  | 	c.conns.Range(func(key interface{}, val interface{}) bool { | ||||||
|  | 		connID := key.(string) | ||||||
|  | 		conn := val.(Connection) | ||||||
|  | 		result[connID] = conn.Name() | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Clean the connector.
 | ||||||
|  | func (c *connector) Clean() { | ||||||
|  | 	c.conns = sync.Map{} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math/rand" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	once sync.Once | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ConnState represents the state of a connection.
 | ||||||
|  | const ( | ||||||
|  | 	ConnStateReady          ConnState = "Ready" | ||||||
|  | 	ConnStateDisconnected   ConnState = "Disconnected" | ||||||
|  | 	ConnStateConnecting     ConnState = "Connecting" | ||||||
|  | 	ConnStateConnected      ConnState = "Connected" | ||||||
|  | 	ConnStateAuthenticating ConnState = "Authenticating" | ||||||
|  | 	ConnStateAccepted       ConnState = "Accepted" | ||||||
|  | 	ConnStateRejected       ConnState = "Rejected" | ||||||
|  | 	ConnStatePing           ConnState = "Ping" | ||||||
|  | 	ConnStatePong           ConnState = "Pong" | ||||||
|  | 	ConnStateTransportData  ConnState = "TransportData" | ||||||
|  | 	ConnStateAborted        ConnState = "Aborted" | ||||||
|  | 	ConnStateClosed         ConnState = "Closed" // close connection by server
 | ||||||
|  | 	ConnStateGoaway         ConnState = "Goaway" | ||||||
|  | 	ConnStateBackFlow       ConnState = "BackFlow" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Prefix is the prefix for logger.
 | ||||||
|  | const ( | ||||||
|  | 	ClientLogPrefix     = "\033[36m[network:client]\033[0m " | ||||||
|  | 	ServerLogPrefix     = "\033[32m[network:server]\033[0m " | ||||||
|  | 	ParseFrameLogPrefix = "\033[36m[network:stream_parser]\033[0m " | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	rand.Seed(time.Now().Unix()) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,191 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"git.hpds.cc/Component/network/hpds_err" | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | 	"io" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.hpds.cc/Component/network/frame" | ||||||
|  | 	"github.com/lucas-clemente/quic-go" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Context for Network Server.
 | ||||||
|  | type Context struct { | ||||||
|  | 	// Conn is the connection of client.
 | ||||||
|  | 	Conn   quic.Connection | ||||||
|  | 	connId string | ||||||
|  | 	// Stream is the long-lived connection between client and server.
 | ||||||
|  | 	Stream io.ReadWriteCloser | ||||||
|  | 	// Frame receives from client.
 | ||||||
|  | 	Frame frame.Frame | ||||||
|  | 	// Keys store the key/value pairs in context.
 | ||||||
|  | 	Keys map[string]interface{} | ||||||
|  | 
 | ||||||
|  | 	mu sync.RWMutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newContext(conn quic.Connection, stream quic.Stream) *Context { | ||||||
|  | 	return &Context{ | ||||||
|  | 		Conn:   conn, | ||||||
|  | 		connId: conn.RemoteAddr().String(), | ||||||
|  | 		Stream: stream, | ||||||
|  | 		// keys:    make(map[string]interface{}),
 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithFrame sets a frame to context.
 | ||||||
|  | func (c *Context) WithFrame(f frame.Frame) *Context { | ||||||
|  | 	c.Frame = f | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Clean the context.
 | ||||||
|  | func (c *Context) Clean() { | ||||||
|  | 	log.Debugf("%sconn[%s] context clean", ServerLogPrefix, c.connId) | ||||||
|  | 	c.Stream = nil | ||||||
|  | 	c.Frame = nil | ||||||
|  | 	c.Keys = nil | ||||||
|  | 	c.Conn = nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CloseWithError closes the stream and cleans the context.
 | ||||||
|  | func (c *Context) CloseWithError(code hpds_err.ErrorCode, msg string) { | ||||||
|  | 	log.Debugf("%sconn[%s] context close, errCode=%#x, msg=%s", ServerLogPrefix, c.connId, code, msg) | ||||||
|  | 	if c.Stream != nil { | ||||||
|  | 		c.Stream.Close() | ||||||
|  | 	} | ||||||
|  | 	if c.Conn != nil { | ||||||
|  | 		c.Conn.CloseWithError(quic.ApplicationErrorCode(code), msg) | ||||||
|  | 	} | ||||||
|  | 	c.Clean() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ConnId get quic connection id
 | ||||||
|  | func (c *Context) ConnId() string { | ||||||
|  | 	return c.connId | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Set a key/value pair to context.
 | ||||||
|  | func (c *Context) Set(key string, value interface{}) { | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	if c.Keys == nil { | ||||||
|  | 		c.Keys = make(map[string]interface{}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.Keys[key] = value | ||||||
|  | 	c.mu.Unlock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get the value by a specified key.
 | ||||||
|  | func (c *Context) Get(key string) (value interface{}, exists bool) { | ||||||
|  | 	c.mu.RLock() | ||||||
|  | 	value, exists = c.Keys[key] | ||||||
|  | 	c.mu.RUnlock() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetString gets a string value by a specified key.
 | ||||||
|  | func (c *Context) GetString(key string) (s string) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		s, _ = val.(string) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetBool gets a bool value by a specified key.
 | ||||||
|  | func (c *Context) GetBool(key string) (b bool) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		b, _ = val.(bool) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetInt gets an int value by a specified key.
 | ||||||
|  | func (c *Context) GetInt(key string) (i int) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		i, _ = val.(int) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetInt64 gets an int64 value by a specified key.
 | ||||||
|  | func (c *Context) GetInt64(key string) (i64 int64) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		i64, _ = val.(int64) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUint gets an uint value by a specified key.
 | ||||||
|  | func (c *Context) GetUint(key string) (ui uint) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		ui, _ = val.(uint) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUint64 gets an uint64 value by a specified key.
 | ||||||
|  | func (c *Context) GetUint64(key string) (ui64 uint64) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		ui64, _ = val.(uint64) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetFloat64 gets a float64 value by a specified key.
 | ||||||
|  | func (c *Context) GetFloat64(key string) (f64 float64) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		f64, _ = val.(float64) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetTime gets a time.Time value by a specified key.
 | ||||||
|  | func (c *Context) GetTime(key string) (t time.Time) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		t, _ = val.(time.Time) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetDuration gets a time.Duration value by a specified key.
 | ||||||
|  | func (c *Context) GetDuration(key string) (d time.Duration) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		d, _ = val.(time.Duration) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetStringSlice gets a []string value by a specified key.
 | ||||||
|  | func (c *Context) GetStringSlice(key string) (ss []string) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		ss, _ = val.([]string) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetStringMap gets a map[string]interface{} value by a specified key.
 | ||||||
|  | func (c *Context) GetStringMap(key string) (sm map[string]interface{}) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		sm, _ = val.(map[string]interface{}) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetStringMapString gets a map[string]string value by a specified key.
 | ||||||
|  | func (c *Context) GetStringMapString(key string) (sms map[string]string) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		sms, _ = val.(map[string]string) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetStringMapStringSlice gets a map[string][]string value by a specified key.
 | ||||||
|  | func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) { | ||||||
|  | 	if val, ok := c.Get(key); ok && val != nil { | ||||||
|  | 		smss, _ = val.(map[string][]string) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // AcceptedFrame is  encoded bytes, Tag is a fixed value TYPE_ID_ACCEPTED_FRAME
 | ||||||
|  | type AcceptedFrame struct{} | ||||||
|  | 
 | ||||||
|  | // NewAcceptedFrame creates a new AcceptedFrame with a given TagId of user's data
 | ||||||
|  | func NewAcceptedFrame() *AcceptedFrame { | ||||||
|  | 	return &AcceptedFrame{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type gets the type of Frame.
 | ||||||
|  | func (m *AcceptedFrame) Type() Type { | ||||||
|  | 	return TagOfAcceptedFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode to coder encoded bytes.
 | ||||||
|  | func (m *AcceptedFrame) Encode() []byte { | ||||||
|  | 	accepted := coder.NewNodePacketEncoder(byte(m.Type())) | ||||||
|  | 	accepted.AddBytes(nil) | ||||||
|  | 
 | ||||||
|  | 	return accepted.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToAcceptedFrame decodes coder encoded bytes to AcceptedFrame.
 | ||||||
|  | func DecodeToAcceptedFrame(buf []byte) (*AcceptedFrame, error) { | ||||||
|  | 	nodeBlock := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &nodeBlock) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &AcceptedFrame{}, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestAcceptedFrameEncode(t *testing.T) { | ||||||
|  | 	f := NewAcceptedFrame() | ||||||
|  | 	assert.Equal(t, []byte{0x80 | byte(TagOfAcceptedFrame), 0x00}, f.Encode()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAcceptedFrameDecode(t *testing.T) { | ||||||
|  | 	buf := []byte{0x80 | byte(TagOfAcceptedFrame), 0x00} | ||||||
|  | 	ping, err := DecodeToAcceptedFrame(buf) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, []byte{0x80 | byte(TagOfAcceptedFrame), 0x00}, ping.Encode()) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // BackFlowFrame is a coder encoded bytes
 | ||||||
|  | // It's used to receive stream function processed result
 | ||||||
|  | type BackFlowFrame struct { | ||||||
|  | 	Tag      byte | ||||||
|  | 	Carriage []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewBackFlowFrame creates a new BackFlowFrame with a given tag and carriage
 | ||||||
|  | func NewBackFlowFrame(tag byte, carriage []byte) *BackFlowFrame { | ||||||
|  | 	return &BackFlowFrame{ | ||||||
|  | 		Tag:      tag, | ||||||
|  | 		Carriage: carriage, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type gets the type of Frame.
 | ||||||
|  | func (f *BackFlowFrame) Type() Type { | ||||||
|  | 	return TagOfBackFlowFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetCarriage sets the user's raw data
 | ||||||
|  | func (f *BackFlowFrame) SetCarriage(buf []byte) *BackFlowFrame { | ||||||
|  | 	f.Carriage = buf | ||||||
|  | 	return f | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode to coder encoded bytes
 | ||||||
|  | func (f *BackFlowFrame) Encode() []byte { | ||||||
|  | 	carriage := coder.NewPrimitivePacketEncoder(f.Tag) | ||||||
|  | 	carriage.SetBytesValue(f.Carriage) | ||||||
|  | 
 | ||||||
|  | 	node := coder.NewNodePacketEncoder(byte(TagOfBackFlowFrame)) | ||||||
|  | 	node.AddPrimitivePacket(carriage) | ||||||
|  | 
 | ||||||
|  | 	return node.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetDataTag return the Tag of user's data
 | ||||||
|  | func (f *BackFlowFrame) GetDataTag() byte { | ||||||
|  | 	return f.Tag | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetCarriage return data
 | ||||||
|  | func (f *BackFlowFrame) GetCarriage() []byte { | ||||||
|  | 	return f.Carriage | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToBackFlowFrame decodes coder encoded bytes to BackFlowFrame
 | ||||||
|  | func DecodeToBackFlowFrame(buf []byte) (*BackFlowFrame, error) { | ||||||
|  | 	nodeBlock := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &nodeBlock) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	payload := &BackFlowFrame{} | ||||||
|  | 	for _, v := range nodeBlock.PrimitivePackets { | ||||||
|  | 		payload.Tag = v.SeqId() | ||||||
|  | 		payload.Carriage = v.GetValBuf() | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return payload, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,110 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DataFrame defines the data structure carried with user's data
 | ||||||
|  | type DataFrame struct { | ||||||
|  | 	metaFrame    *MetaFrame | ||||||
|  | 	payloadFrame *PayloadFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDataFrame create `DataFrame` with a transactionId string,
 | ||||||
|  | // consider change transactionID to UUID type later
 | ||||||
|  | func NewDataFrame() *DataFrame { | ||||||
|  | 	data := &DataFrame{ | ||||||
|  | 		metaFrame: NewMetaFrame(), | ||||||
|  | 	} | ||||||
|  | 	return data | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type gets the type of Frame.
 | ||||||
|  | func (d *DataFrame) Type() Type { | ||||||
|  | 	return TagOfDataFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Tag return the tag of carriage data.
 | ||||||
|  | func (d *DataFrame) Tag() byte { | ||||||
|  | 	return d.payloadFrame.Tag | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetCarriage set user's raw data in `DataFrame`
 | ||||||
|  | func (d *DataFrame) SetCarriage(tag byte, carriage []byte) { | ||||||
|  | 	d.payloadFrame = NewPayloadFrame(tag).SetCarriage(carriage) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetCarriage return user's raw data in `DataFrame`
 | ||||||
|  | func (d *DataFrame) GetCarriage() []byte { | ||||||
|  | 	return d.payloadFrame.Carriage | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TransactionId return transactionId string
 | ||||||
|  | func (d *DataFrame) TransactionId() string { | ||||||
|  | 	return d.metaFrame.TransactionId() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetTransactionId set transactionId string
 | ||||||
|  | func (d *DataFrame) SetTransactionId(transactionID string) { | ||||||
|  | 	d.metaFrame.SetTransactionId(transactionID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMetaFrame return MetaFrame.
 | ||||||
|  | func (d *DataFrame) GetMetaFrame() *MetaFrame { | ||||||
|  | 	return d.metaFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetDataTag return the Tag of user's data
 | ||||||
|  | func (d *DataFrame) GetDataTag() byte { | ||||||
|  | 	return d.payloadFrame.Tag | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetSourceId set the source id.
 | ||||||
|  | func (d *DataFrame) SetSourceId(sourceID string) { | ||||||
|  | 	d.metaFrame.SetSourceId(sourceID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SourceId returns source id
 | ||||||
|  | func (d *DataFrame) SourceId() string { | ||||||
|  | 	return d.metaFrame.SourceId() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode return coder encoded bytes of `DataFrame`
 | ||||||
|  | func (d *DataFrame) Encode() []byte { | ||||||
|  | 	data := coder.NewNodePacketEncoder(byte(d.Type())) | ||||||
|  | 	// MetaFrame
 | ||||||
|  | 	data.AddBytes(d.metaFrame.Encode()) | ||||||
|  | 	// PayloadFrame
 | ||||||
|  | 	data.AddBytes(d.payloadFrame.Encode()) | ||||||
|  | 
 | ||||||
|  | 	return data.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToDataFrame decode coder encoded bytes to `DataFrame`
 | ||||||
|  | func DecodeToDataFrame(buf []byte) (*DataFrame, error) { | ||||||
|  | 	packet := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &packet) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	data := &DataFrame{} | ||||||
|  | 
 | ||||||
|  | 	if metaBlock, ok := packet.NodePackets[byte(TagOfMetaFrame)]; ok { | ||||||
|  | 		meta, err := DecodeToMetaFrame(metaBlock.GetRawBytes()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		data.metaFrame = meta | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if payloadBlock, ok := packet.NodePackets[byte(TagOfPayloadFrame)]; ok { | ||||||
|  | 		payload, err := DecodeToPayloadFrame(payloadBlock.GetRawBytes()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		data.payloadFrame = payload | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return data, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestDataFrameEncode(t *testing.T) { | ||||||
|  | 	var userDataTag byte = 0x15 | ||||||
|  | 	d := NewDataFrame() | ||||||
|  | 	d.SetCarriage(userDataTag, []byte("hpds")) | ||||||
|  | 
 | ||||||
|  | 	tidbuf := []byte(d.TransactionId()) | ||||||
|  | 	result := []byte{ | ||||||
|  | 		0x80 | byte(TagOfDataFrame), byte(len(tidbuf) + 4 + 8 + 2), | ||||||
|  | 		0x80 | byte(TagOfMetaFrame), byte(len(tidbuf) + 2 + 2), | ||||||
|  | 		byte(TagOfTransactionId), byte(len(tidbuf))} | ||||||
|  | 	result = append(result, tidbuf...) | ||||||
|  | 	result = append(result, byte(TagOfSourceId), 0x0) | ||||||
|  | 	result = append(result, 0x80|byte(TagOfPayloadFrame), 0x06, | ||||||
|  | 		userDataTag, 0x04, 0x79, 0x6F, 0x6D, 0x6F) | ||||||
|  | 	assert.Equal(t, result, d.Encode()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestDataFrameDecode(t *testing.T) { | ||||||
|  | 	var userDataTag byte = 0x15 | ||||||
|  | 	buf := []byte{ | ||||||
|  | 		0x80 | byte(TagOfDataFrame), 0x10, | ||||||
|  | 		0x80 | byte(TagOfMetaFrame), 0x06, | ||||||
|  | 		byte(TagOfTransactionId), 0x04, 0x31, 0x32, 0x33, 0x34, | ||||||
|  | 		0x80 | byte(TagOfPayloadFrame), 0x06, | ||||||
|  | 		userDataTag, 0x04, 0x79, 0x6F, 0x6D, 0x6F} | ||||||
|  | 	data, err := DecodeToDataFrame(buf) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.EqualValues(t, "1234", data.TransactionId()) | ||||||
|  | 	assert.EqualValues(t, userDataTag, data.GetDataTag()) | ||||||
|  | 	assert.EqualValues(t, []byte("hpds"), data.GetCarriage()) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,106 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // debugFrameSize print frame data size on debug mode
 | ||||||
|  | var debugFrameSize = 16 | ||||||
|  | 
 | ||||||
|  | // Kinds of frames transferable within HPDS
 | ||||||
|  | const ( | ||||||
|  | 	// DataFrame
 | ||||||
|  | 	TagOfDataFrame Type = 0x3F | ||||||
|  | 	// MetaFrame of DataFrame
 | ||||||
|  | 	TagOfMetaFrame     Type = 0x2F | ||||||
|  | 	TagOfMetadata      Type = 0x03 | ||||||
|  | 	TagOfTransactionId Type = 0x01 | ||||||
|  | 	TagOfSourceId      Type = 0x02 | ||||||
|  | 	// PayloadFrame of DataFrame
 | ||||||
|  | 	TagOfPayloadFrame  Type = 0x2E | ||||||
|  | 	TagOfBackFlowFrame Type = 0x2D | ||||||
|  | 
 | ||||||
|  | 	TagOfTokenFrame Type = 0x3E | ||||||
|  | 	// HandshakeFrame
 | ||||||
|  | 	TagOfHandshakeFrame           Type = 0x3D | ||||||
|  | 	TagOfHandshakeName            Type = 0x01 | ||||||
|  | 	TagOfHandshakeType            Type = 0x02 | ||||||
|  | 	TagOfHandshakeId              Type = 0x03 | ||||||
|  | 	TagOfHandshakeAuthName        Type = 0x04 | ||||||
|  | 	TagOfHandshakeAuthPayload     Type = 0x05 | ||||||
|  | 	TagOfHandshakeObserveDataTags Type = 0x06 | ||||||
|  | 
 | ||||||
|  | 	TagOfPingFrame       Type = 0x3C | ||||||
|  | 	TagOfPongFrame       Type = 0x3B | ||||||
|  | 	TagOfAcceptedFrame   Type = 0x3A | ||||||
|  | 	TagOfRejectedFrame   Type = 0x39 | ||||||
|  | 	TagOfRejectedMessage Type = 0x02 | ||||||
|  | 	// GoawayFrame
 | ||||||
|  | 	TagOfGoawayFrame   Type = 0x30 | ||||||
|  | 	TagOfGoawayCode    Type = 0x01 | ||||||
|  | 	TagOfGoawayMessage Type = 0x02 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Type represents the type of frame.
 | ||||||
|  | type Type uint8 | ||||||
|  | 
 | ||||||
|  | // Frame is the interface for frame.
 | ||||||
|  | type Frame interface { | ||||||
|  | 	// Type gets the type of Frame.
 | ||||||
|  | 	Type() Type | ||||||
|  | 
 | ||||||
|  | 	// Encode the frame into []byte.
 | ||||||
|  | 	Encode() []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f Type) String() string { | ||||||
|  | 	switch f { | ||||||
|  | 	case TagOfDataFrame: | ||||||
|  | 		return "DataFrame" | ||||||
|  | 	case TagOfTokenFrame: | ||||||
|  | 		return "TokenFrame" | ||||||
|  | 	case TagOfHandshakeFrame: | ||||||
|  | 		return "HandshakeFrame" | ||||||
|  | 	case TagOfPingFrame: | ||||||
|  | 		return "PingFrame" | ||||||
|  | 	case TagOfPongFrame: | ||||||
|  | 		return "PongFrame" | ||||||
|  | 	case TagOfAcceptedFrame: | ||||||
|  | 		return "AcceptedFrame" | ||||||
|  | 	case TagOfRejectedFrame: | ||||||
|  | 		return "RejectedFrame" | ||||||
|  | 	case TagOfGoawayFrame: | ||||||
|  | 		return "GoawayFrame" | ||||||
|  | 	case TagOfBackFlowFrame: | ||||||
|  | 		return "BackFlowFrame" | ||||||
|  | 	case TagOfMetaFrame: | ||||||
|  | 		return "MetaFrame" | ||||||
|  | 	case TagOfPayloadFrame: | ||||||
|  | 		return "PayloadFrame" | ||||||
|  | 	// case TagOfTransactionId:
 | ||||||
|  | 	// 	return "TransactionId"
 | ||||||
|  | 	case TagOfHandshakeName: | ||||||
|  | 		return "HandshakeName" | ||||||
|  | 	case TagOfHandshakeType: | ||||||
|  | 		return "HandshakeType" | ||||||
|  | 	default: | ||||||
|  | 		return "UnknownFrame" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Shortly reduce data size for easy viewing
 | ||||||
|  | func Shortly(data []byte) []byte { | ||||||
|  | 	if len(data) > debugFrameSize { | ||||||
|  | 		return data[:debugFrameSize] | ||||||
|  | 	} | ||||||
|  | 	return data | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	if envFrameSize := os.Getenv("DEBUG_FRAME_SIZE"); envFrameSize != "" { | ||||||
|  | 		if val, err := strconv.Atoi(envFrameSize); err == nil { | ||||||
|  | 			debugFrameSize = val | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,110 @@ | ||||||
|  | @startuml | ||||||
|  | namespace frame { | ||||||
|  |     class AcceptedFrame << (S,Aquamarine) >> { | ||||||
|  |         + Type() Type | ||||||
|  |         + Encode() []byte | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class BackFlowFrame << (S,Aquamarine) >> { | ||||||
|  |         + Tag byte | ||||||
|  |         + Carriage []byte | ||||||
|  | 
 | ||||||
|  |         + Type() Type | ||||||
|  |         + SetCarriage(buf []byte) *BackFlowFrame | ||||||
|  |         + Encode() []byte | ||||||
|  |         + GetDataTag() byte | ||||||
|  |         + GetCarriage() []byte | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class DataFrame << (S,Aquamarine) >> { | ||||||
|  |         - metaFrame *MetaFrame | ||||||
|  |         - payloadFrame *PayloadFrame | ||||||
|  | 
 | ||||||
|  |         + Type() Type | ||||||
|  |         + Tag() byte | ||||||
|  |         + SetCarriage(tag byte, carriage []byte) | ||||||
|  |         + GetCarriage() []byte | ||||||
|  |         + TransactionId() string | ||||||
|  |         + SetTransactionId(transactionID string) | ||||||
|  |         + GetMetaFrame() *MetaFrame | ||||||
|  |         + GetDataTag() byte | ||||||
|  |         + SetSourceId(sourceID string) | ||||||
|  |         + SourceId() string | ||||||
|  |         + Encode() []byte | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     interface Frame  { | ||||||
|  |         + Type() Type | ||||||
|  |         + Encode() []byte | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class GoawayFrame << (S,Aquamarine) >> { | ||||||
|  |         - message string | ||||||
|  | 
 | ||||||
|  |         + Type() Type | ||||||
|  |         + Encode() []byte | ||||||
|  |         + Message() string | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class HandshakeFrame << (S,Aquamarine) >> { | ||||||
|  |         - authName string | ||||||
|  |         - authPayload string | ||||||
|  | 
 | ||||||
|  |         + Name string | ||||||
|  |         + ClientId string | ||||||
|  |         + ClientType byte | ||||||
|  |         + ObserveDataTags []byte | ||||||
|  | 
 | ||||||
|  |         + Type() Type | ||||||
|  |         + Encode() []byte | ||||||
|  |         + AuthPayload() string | ||||||
|  |         + AuthName() string | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class MetaFrame << (S,Aquamarine) >> { | ||||||
|  |         - tid string | ||||||
|  |         - metadata []byte | ||||||
|  |         - sourceId string | ||||||
|  | 
 | ||||||
|  |         + SetTransactionId(transactionId string) | ||||||
|  |         + TransactionId() string | ||||||
|  |         + SetMetadata(metadata []byte) | ||||||
|  |         + Metadata() []byte | ||||||
|  |         + SetSourceId(sourceId string) | ||||||
|  |         + SourceId() string | ||||||
|  |         + Encode() []byte | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class PayloadFrame << (S,Aquamarine) >> { | ||||||
|  |         + Tag byte | ||||||
|  |         + Carriage []byte | ||||||
|  | 
 | ||||||
|  |         + SetCarriage(buf []byte) *PayloadFrame | ||||||
|  |         + Encode() []byte | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class RejectedFrame << (S,Aquamarine) >> { | ||||||
|  |         - message string | ||||||
|  | 
 | ||||||
|  |         + Type() Type | ||||||
|  |         + Encode() []byte | ||||||
|  |         + Message() string | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class Type << (S,Aquamarine) >> { | ||||||
|  |         + String() string | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     class frame.Type << (T, #FF7700) >>  { | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | "frame.Frame" <|-- "frame.AcceptedFrame" | ||||||
|  | "frame.Frame" <|-- "frame.BackFlowFrame" | ||||||
|  | "frame.Frame" <|-- "frame.DataFrame" | ||||||
|  | "frame.Frame" <|-- "frame.GoawayFrame" | ||||||
|  | "frame.Frame" <|-- "frame.HandshakeFrame" | ||||||
|  | "frame.Frame" <|-- "frame.RejectedFrame" | ||||||
|  | 
 | ||||||
|  | "__builtin__.uint8" #.. "frame.Type" | ||||||
|  | @enduml | ||||||
|  | @ -0,0 +1,57 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // GoawayFrame is a coder encoded bytes, Tag is a fixed value TYPE_ID_GOAWAY_FRAME
 | ||||||
|  | type GoawayFrame struct { | ||||||
|  | 	message string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewGoawayFrame creates a new GoawayFrame
 | ||||||
|  | func NewGoawayFrame(msg string) *GoawayFrame { | ||||||
|  | 	return &GoawayFrame{message: msg} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type gets the type of Frame.
 | ||||||
|  | func (f *GoawayFrame) Type() Type { | ||||||
|  | 	return TagOfGoawayFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode to coder encoded bytes
 | ||||||
|  | func (f *GoawayFrame) Encode() []byte { | ||||||
|  | 	goaway := coder.NewNodePacketEncoder(byte(f.Type())) | ||||||
|  | 	// message
 | ||||||
|  | 	msgBlock := coder.NewPrimitivePacketEncoder(byte(TagOfGoawayMessage)) | ||||||
|  | 	msgBlock.SetStringValue(f.message) | ||||||
|  | 
 | ||||||
|  | 	goaway.AddPrimitivePacket(msgBlock) | ||||||
|  | 
 | ||||||
|  | 	return goaway.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Message goaway message
 | ||||||
|  | func (f *GoawayFrame) Message() string { | ||||||
|  | 	return f.message | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToGoawayFrame decodes coder encoded bytes to GoawayFrame
 | ||||||
|  | func DecodeToGoawayFrame(buf []byte) (*GoawayFrame, error) { | ||||||
|  | 	node := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &node) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	goaway := &GoawayFrame{} | ||||||
|  | 	// message
 | ||||||
|  | 	if msgBlock, ok := node.PrimitivePackets[byte(TagOfGoawayMessage)]; ok { | ||||||
|  | 		msg, err := msgBlock.ToUTF8String() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		goaway.message = msg | ||||||
|  | 	} | ||||||
|  | 	return goaway, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,131 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // HandshakeFrame is a coder encoded.
 | ||||||
|  | type HandshakeFrame struct { | ||||||
|  | 	// Name is client name
 | ||||||
|  | 	Name string | ||||||
|  | 	// ClientId represents client id
 | ||||||
|  | 	ClientId string | ||||||
|  | 	// ClientType represents client type (Protocol Gateway | Stream Function)
 | ||||||
|  | 	ClientType byte | ||||||
|  | 	// ObserveDataTags are the client data tag list.
 | ||||||
|  | 	ObserveDataTags []byte | ||||||
|  | 	// auth
 | ||||||
|  | 	authName    string | ||||||
|  | 	authPayload string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewHandshakeFrame creates a new HandshakeFrame.
 | ||||||
|  | func NewHandshakeFrame(name string, clientId string, clientType byte, observeDataTags []byte, authName string, authPayload string) *HandshakeFrame { | ||||||
|  | 	return &HandshakeFrame{ | ||||||
|  | 		Name:            name, | ||||||
|  | 		ClientId:        clientId, | ||||||
|  | 		ClientType:      clientType, | ||||||
|  | 		ObserveDataTags: observeDataTags, | ||||||
|  | 		authName:        authName, | ||||||
|  | 		authPayload:     authPayload, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type gets the type of Frame.
 | ||||||
|  | func (h *HandshakeFrame) Type() Type { | ||||||
|  | 	return TagOfHandshakeFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode to coder encoding.
 | ||||||
|  | func (h *HandshakeFrame) Encode() []byte { | ||||||
|  | 	// name
 | ||||||
|  | 	nameBlock := coder.NewPrimitivePacketEncoder(byte(TagOfHandshakeName)) | ||||||
|  | 	nameBlock.SetStringValue(h.Name) | ||||||
|  | 	// client ID
 | ||||||
|  | 	idBlock := coder.NewPrimitivePacketEncoder(byte(TagOfHandshakeId)) | ||||||
|  | 	idBlock.SetStringValue(h.ClientId) | ||||||
|  | 	// client type
 | ||||||
|  | 	typeBlock := coder.NewPrimitivePacketEncoder(byte(TagOfHandshakeType)) | ||||||
|  | 	typeBlock.SetBytesValue([]byte{h.ClientType}) | ||||||
|  | 	// observe data tags
 | ||||||
|  | 	observeDataTagsBlock := coder.NewPrimitivePacketEncoder(byte(TagOfHandshakeObserveDataTags)) | ||||||
|  | 	observeDataTagsBlock.SetBytesValue(h.ObserveDataTags) | ||||||
|  | 	// auth
 | ||||||
|  | 	authNameBlock := coder.NewPrimitivePacketEncoder(byte(TagOfHandshakeAuthName)) | ||||||
|  | 	authNameBlock.SetStringValue(h.authName) | ||||||
|  | 	authPayloadBlock := coder.NewPrimitivePacketEncoder(byte(TagOfHandshakeAuthPayload)) | ||||||
|  | 	authPayloadBlock.SetStringValue(h.authPayload) | ||||||
|  | 	// handshake frame
 | ||||||
|  | 	handshake := coder.NewNodePacketEncoder(byte(h.Type())) | ||||||
|  | 	handshake.AddPrimitivePacket(nameBlock) | ||||||
|  | 	handshake.AddPrimitivePacket(idBlock) | ||||||
|  | 	handshake.AddPrimitivePacket(typeBlock) | ||||||
|  | 	handshake.AddPrimitivePacket(observeDataTagsBlock) | ||||||
|  | 	handshake.AddPrimitivePacket(authNameBlock) | ||||||
|  | 	handshake.AddPrimitivePacket(authPayloadBlock) | ||||||
|  | 
 | ||||||
|  | 	return handshake.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToHandshakeFrame decodes coder encoded bytes to HandshakeFrame.
 | ||||||
|  | func DecodeToHandshakeFrame(buf []byte) (*HandshakeFrame, error) { | ||||||
|  | 	node := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &node) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	handshake := &HandshakeFrame{} | ||||||
|  | 	// name
 | ||||||
|  | 	if nameBlock, ok := node.PrimitivePackets[byte(TagOfHandshakeName)]; ok { | ||||||
|  | 		name, err := nameBlock.ToUTF8String() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		handshake.Name = name | ||||||
|  | 	} | ||||||
|  | 	// client id
 | ||||||
|  | 	if idBlock, ok := node.PrimitivePackets[byte(TagOfHandshakeId)]; ok { | ||||||
|  | 		id, err := idBlock.ToUTF8String() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		handshake.ClientId = id | ||||||
|  | 	} | ||||||
|  | 	// client type
 | ||||||
|  | 	if typeBlock, ok := node.PrimitivePackets[byte(TagOfHandshakeType)]; ok { | ||||||
|  | 		clientType := typeBlock.ToBytes() | ||||||
|  | 		handshake.ClientType = clientType[0] | ||||||
|  | 	} | ||||||
|  | 	// observe data tag list
 | ||||||
|  | 	if observeDataTagsBlock, ok := node.PrimitivePackets[byte(TagOfHandshakeObserveDataTags)]; ok { | ||||||
|  | 		handshake.ObserveDataTags = observeDataTagsBlock.ToBytes() | ||||||
|  | 	} | ||||||
|  | 	// auth
 | ||||||
|  | 	if authNameBlock, ok := node.PrimitivePackets[byte(TagOfHandshakeAuthName)]; ok { | ||||||
|  | 		authName, err := authNameBlock.ToUTF8String() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		handshake.authName = authName | ||||||
|  | 	} | ||||||
|  | 	if authPayloadBlock, ok := node.PrimitivePackets[byte(TagOfHandshakeAuthPayload)]; ok { | ||||||
|  | 		authPayload, err := authPayloadBlock.ToUTF8String() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		handshake.authPayload = authPayload | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return handshake, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AuthPayload authentication payload
 | ||||||
|  | func (h *HandshakeFrame) AuthPayload() string { | ||||||
|  | 	return h.authPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AuthName authentication name
 | ||||||
|  | func (h *HandshakeFrame) AuthName() string { | ||||||
|  | 	return h.authName | ||||||
|  | } | ||||||
|  | @ -0,0 +1,30 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestHandshakeFrameEncode(t *testing.T) { | ||||||
|  | 	expectedName := "1234" | ||||||
|  | 	var expectedType byte = 0xD3 | ||||||
|  | 	m := NewHandshakeFrame(expectedName, "", expectedType, []byte{0x01, 0x02}, "token", "a") | ||||||
|  | 	assert.Equal(t, []byte{ | ||||||
|  | 		0x80 | byte(TagOfHandshakeFrame), 0x19, | ||||||
|  | 		byte(TagOfHandshakeName), 0x04, 0x31, 0x32, 0x33, 0x34, | ||||||
|  | 		byte(TagOfHandshakeId), 0x0, | ||||||
|  | 		byte(TagOfHandshakeType), 0x01, 0xD3, | ||||||
|  | 		byte(TagOfHandshakeObserveDataTags), 0x02, 0x01, 0x02, | ||||||
|  | 		// byte(TagOfHandshakeAppID), 0x0,
 | ||||||
|  | 		byte(TagOfHandshakeAuthName), 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, | ||||||
|  | 		byte(TagOfHandshakeAuthPayload), 0x01, 0x61, | ||||||
|  | 	}, | ||||||
|  | 		m.Encode(), | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	Handshake, err := DecodeToHandshakeFrame(m.Encode()) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.EqualValues(t, expectedName, Handshake.Name) | ||||||
|  | 	assert.EqualValues(t, expectedType, Handshake.ClientType) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | 	gonanoid "github.com/matoous/go-nanoid/v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // MetaFrame is a coder encoded bytes, SeqId is a fixed value of TYPE_ID_TRANSACTION.
 | ||||||
|  | // used for describes metadata for a DataFrame.
 | ||||||
|  | type MetaFrame struct { | ||||||
|  | 	tid      string | ||||||
|  | 	metadata []byte | ||||||
|  | 	sourceId string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewMetaFrame creates a new MetaFrame instance.
 | ||||||
|  | func NewMetaFrame() *MetaFrame { | ||||||
|  | 	tid, err := gonanoid.New() | ||||||
|  | 	if err != nil { | ||||||
|  | 		tid = strconv.FormatInt(time.Now().UnixMicro(), 10) | ||||||
|  | 	} | ||||||
|  | 	return &MetaFrame{tid: tid} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetTransactionId set the transaction id.
 | ||||||
|  | func (m *MetaFrame) SetTransactionId(transactionId string) { | ||||||
|  | 	m.tid = transactionId | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TransactionId returns transactionId
 | ||||||
|  | func (m *MetaFrame) TransactionId() string { | ||||||
|  | 	return m.tid | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetMetadata set the extra info of the application
 | ||||||
|  | func (m *MetaFrame) SetMetadata(metadata []byte) { | ||||||
|  | 	m.metadata = metadata | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Metadata returns the extra info of the application
 | ||||||
|  | func (m *MetaFrame) Metadata() []byte { | ||||||
|  | 	return m.metadata | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetSourceId set the source ID.
 | ||||||
|  | func (m *MetaFrame) SetSourceId(sourceId string) { | ||||||
|  | 	m.sourceId = sourceId | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SourceId returns source ID
 | ||||||
|  | func (m *MetaFrame) SourceId() string { | ||||||
|  | 	return m.sourceId | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode implements Frame.Encode method.
 | ||||||
|  | func (m *MetaFrame) Encode() []byte { | ||||||
|  | 	meta := coder.NewNodePacketEncoder(byte(TagOfMetaFrame)) | ||||||
|  | 	// transaction ID
 | ||||||
|  | 	transactionId := coder.NewPrimitivePacketEncoder(byte(TagOfTransactionId)) | ||||||
|  | 	transactionId.SetStringValue(m.tid) | ||||||
|  | 	meta.AddPrimitivePacket(transactionId) | ||||||
|  | 
 | ||||||
|  | 	// source ID
 | ||||||
|  | 	sourceId := coder.NewPrimitivePacketEncoder(byte(TagOfSourceId)) | ||||||
|  | 	sourceId.SetStringValue(m.sourceId) | ||||||
|  | 	meta.AddPrimitivePacket(sourceId) | ||||||
|  | 
 | ||||||
|  | 	// metadata
 | ||||||
|  | 	if m.metadata != nil { | ||||||
|  | 		metadata := coder.NewPrimitivePacketEncoder(byte(TagOfMetadata)) | ||||||
|  | 		metadata.SetBytesValue(m.metadata) | ||||||
|  | 		meta.AddPrimitivePacket(metadata) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return meta.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToMetaFrame decode a MetaFrame instance from given buffer.
 | ||||||
|  | func DecodeToMetaFrame(buf []byte) (*MetaFrame, error) { | ||||||
|  | 	nodeBlock := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &nodeBlock) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	meta := &MetaFrame{} | ||||||
|  | 	for k, v := range nodeBlock.PrimitivePackets { | ||||||
|  | 		switch k { | ||||||
|  | 		case byte(TagOfTransactionId): | ||||||
|  | 			val, err := v.ToUTF8String() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			meta.tid = val | ||||||
|  | 			break | ||||||
|  | 		case byte(TagOfMetadata): | ||||||
|  | 			meta.metadata = v.ToBytes() | ||||||
|  | 			break | ||||||
|  | 		case byte(TagOfSourceId): | ||||||
|  | 			sourceId, err := v.ToUTF8String() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			meta.sourceId = sourceId | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return meta, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMetaFrameEncode(t *testing.T) { | ||||||
|  | 	m := NewMetaFrame() | ||||||
|  | 	tidbuf := []byte(m.tid) | ||||||
|  | 	result := []byte{0x80 | byte(TagOfMetaFrame), byte(1 + 1 + len(tidbuf) + 2), byte(TagOfTransactionId), byte(len(tidbuf))} | ||||||
|  | 	result = append(result, tidbuf...) | ||||||
|  | 	result = append(result, byte(TagOfSourceId), 0x0) | ||||||
|  | 	assert.Equal(t, result, m.Encode()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMetaFrameDecode(t *testing.T) { | ||||||
|  | 	buf := []byte{0x80 | byte(TagOfMetaFrame), 0x09, byte(TagOfTransactionId), 0x04, 0x31, 0x32, 0x33, 0x34, byte(TagOfSourceId), 0x01, 0x31} | ||||||
|  | 	meta, err := DecodeToMetaFrame(buf) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.EqualValues(t, "1234", meta.TransactionId()) | ||||||
|  | 	assert.EqualValues(t, "1", meta.SourceId()) | ||||||
|  | 	t.Logf("%# x", buf) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,55 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // PayloadFrame is a coder encoded bytes, Tag is a fixed value TYPE_ID_PAYLOAD_FRAME
 | ||||||
|  | // the Len is the length of Val. Val is also a coder encoded PrimitivePacket, storing
 | ||||||
|  | // raw bytes as user's data
 | ||||||
|  | type PayloadFrame struct { | ||||||
|  | 	Tag      byte | ||||||
|  | 	Carriage []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewPayloadFrame creates a new PayloadFrame with a given TagId of user's data
 | ||||||
|  | func NewPayloadFrame(tag byte) *PayloadFrame { | ||||||
|  | 	return &PayloadFrame{ | ||||||
|  | 		Tag: tag, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetCarriage sets the user's raw data
 | ||||||
|  | func (m *PayloadFrame) SetCarriage(buf []byte) *PayloadFrame { | ||||||
|  | 	m.Carriage = buf | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode to coder encoded bytes
 | ||||||
|  | func (m *PayloadFrame) Encode() []byte { | ||||||
|  | 	carriage := coder.NewPrimitivePacketEncoder(m.Tag) | ||||||
|  | 	carriage.SetBytesValue(m.Carriage) | ||||||
|  | 
 | ||||||
|  | 	payload := coder.NewNodePacketEncoder(byte(TagOfPayloadFrame)) | ||||||
|  | 	payload.AddPrimitivePacket(carriage) | ||||||
|  | 
 | ||||||
|  | 	return payload.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToPayloadFrame decodes coder encoded bytes to PayloadFrame
 | ||||||
|  | func DecodeToPayloadFrame(buf []byte) (*PayloadFrame, error) { | ||||||
|  | 	nodeBlock := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &nodeBlock) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	payload := &PayloadFrame{} | ||||||
|  | 	for _, v := range nodeBlock.PrimitivePackets { | ||||||
|  | 		payload.Tag = v.SeqId() | ||||||
|  | 		payload.Carriage = v.GetValBuf() | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return payload, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestPayloadFrameEncode(t *testing.T) { | ||||||
|  | 	f := NewPayloadFrame(0x13).SetCarriage([]byte("hpds")) | ||||||
|  | 	assert.Equal(t, []byte{0x80 | byte(TagOfPayloadFrame), 0x06, 0x13, 0x04, 0x79, 0x6F, 0x6D, 0x6F}, f.Encode()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestPayloadFrameDecode(t *testing.T) { | ||||||
|  | 	buf := []byte{0x80 | byte(TagOfPayloadFrame), 0x06, 0x13, 0x04, 0x79, 0x6F, 0x6D, 0x6F} | ||||||
|  | 	payload, err := DecodeToPayloadFrame(buf) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.EqualValues(t, 0x13, payload.Tag) | ||||||
|  | 	assert.Equal(t, []byte{0x79, 0x6F, 0x6D, 0x6F}, payload.Carriage) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,56 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // RejectedFrame is a coder encoded bytes, Tag is a fixed value TYPE_ID_REJECTED_FRAME
 | ||||||
|  | type RejectedFrame struct { | ||||||
|  | 	message string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewRejectedFrame creates a new RejectedFrame with a given TagId of user's data
 | ||||||
|  | func NewRejectedFrame(msg string) *RejectedFrame { | ||||||
|  | 	return &RejectedFrame{message: msg} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type gets the type of Frame.
 | ||||||
|  | func (f *RejectedFrame) Type() Type { | ||||||
|  | 	return TagOfRejectedFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encode to coder encoded bytes
 | ||||||
|  | func (f *RejectedFrame) Encode() []byte { | ||||||
|  | 	rejected := coder.NewNodePacketEncoder(byte(f.Type())) | ||||||
|  | 	// message
 | ||||||
|  | 	msgBlock := coder.NewPrimitivePacketEncoder(byte(TagOfRejectedMessage)) | ||||||
|  | 	msgBlock.SetStringValue(f.message) | ||||||
|  | 
 | ||||||
|  | 	rejected.AddPrimitivePacket(msgBlock) | ||||||
|  | 
 | ||||||
|  | 	return rejected.Encode() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Message rejected message
 | ||||||
|  | func (f *RejectedFrame) Message() string { | ||||||
|  | 	return f.message | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeToRejectedFrame decodes coder encoded bytes to RejectedFrame
 | ||||||
|  | func DecodeToRejectedFrame(buf []byte) (*RejectedFrame, error) { | ||||||
|  | 	node := coder.NodePacket{} | ||||||
|  | 	_, err := coder.DecodeToNodePacket(buf, &node) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	rejected := &RejectedFrame{} | ||||||
|  | 	// message
 | ||||||
|  | 	if msgBlock, ok := node.PrimitivePackets[byte(TagOfRejectedMessage)]; ok { | ||||||
|  | 		msg, e := msgBlock.ToUTF8String() | ||||||
|  | 		if e != nil { | ||||||
|  | 			return nil, e | ||||||
|  | 		} | ||||||
|  | 		rejected.message = msg | ||||||
|  | 	} | ||||||
|  | 	return rejected, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | package frame | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestRejectedFrameEncode(t *testing.T) { | ||||||
|  | 	f := NewRejectedFrame("") | ||||||
|  | 	assert.Equal(t, []byte{0x80 | byte(TagOfRejectedFrame), 0x02, 0x02, 0x00}, f.Encode()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestRejectedFrameDecode(t *testing.T) { | ||||||
|  | 	buf := []byte{0x80 | byte(TagOfRejectedFrame), 0x00} | ||||||
|  | 	ping, err := DecodeToRejectedFrame(buf) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, []byte{0x80 | byte(TagOfRejectedFrame), 0x2, 0x2, 0x0}, ping.Encode()) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"git.hpds.cc/Component/network/frame" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // FrameStream is the QUIC Stream with the minimum unit Frame.
 | ||||||
|  | type FrameStream struct { | ||||||
|  | 	// Stream is a QUIC stream.
 | ||||||
|  | 	stream io.ReadWriter | ||||||
|  | 	mu     sync.Mutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewFrameStream creates a new FrameStream.
 | ||||||
|  | func NewFrameStream(s io.ReadWriter) *FrameStream { | ||||||
|  | 	return &FrameStream{ | ||||||
|  | 		stream: s, | ||||||
|  | 		mu:     sync.Mutex{}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadFrame reads next frame from QUIC stream.
 | ||||||
|  | func (fs *FrameStream) ReadFrame() (frame.Frame, error) { | ||||||
|  | 	if fs.stream == nil { | ||||||
|  | 		return nil, errors.New("network.ReadStream: stream can not be nil") | ||||||
|  | 	} | ||||||
|  | 	return ParseFrame(fs.stream) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WriteFrame writes a frame into QUIC stream.
 | ||||||
|  | func (fs *FrameStream) WriteFrame(f frame.Frame) (int, error) { | ||||||
|  | 	if fs.stream == nil { | ||||||
|  | 		return 0, errors.New("network.WriteFrame: stream can not be nil") | ||||||
|  | 	} | ||||||
|  | 	fs.mu.Lock() | ||||||
|  | 	defer fs.mu.Unlock() | ||||||
|  | 	return fs.stream.Write(f.Encode()) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | module git.hpds.cc/Component/network | ||||||
|  | 
 | ||||||
|  | go 1.19 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	git.hpds.cc/Component/mq_coder v0.0.0-20221010064749-174ae7ae3340 | ||||||
|  | 	github.com/lucas-clemente/quic-go v0.29.1 | ||||||
|  | 	github.com/matoous/go-nanoid/v2 v2.0.0 | ||||||
|  | 	github.com/stretchr/testify v1.8.0 | ||||||
|  | 	go.uber.org/zap v1.23.0 | ||||||
|  | 	gopkg.in/natefinch/lumberjack.v2 v2.0.0 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/BurntSushi/toml v1.2.0 // indirect | ||||||
|  | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
|  | 	github.com/fsnotify/fsnotify v1.4.9 // indirect | ||||||
|  | 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect | ||||||
|  | 	github.com/golang/mock v1.6.0 // indirect | ||||||
|  | 	github.com/kr/pretty v0.3.1 // indirect | ||||||
|  | 	github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect | ||||||
|  | 	github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect | ||||||
|  | 	github.com/nxadm/tail v1.4.8 // indirect | ||||||
|  | 	github.com/onsi/ginkgo v1.16.4 // indirect | ||||||
|  | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
|  | 	go.uber.org/atomic v1.7.0 // indirect | ||||||
|  | 	go.uber.org/multierr v1.6.0 // indirect | ||||||
|  | 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect | ||||||
|  | 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect | ||||||
|  | 	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect | ||||||
|  | 	golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect | ||||||
|  | 	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect | ||||||
|  | 	golang.org/x/tools v0.1.10 // indirect | ||||||
|  | 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect | ||||||
|  | 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect | ||||||
|  | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import "git.hpds.cc/Component/network/frame" | ||||||
|  | 
 | ||||||
|  | // AsyncHandler is the request-response mode (async)
 | ||||||
|  | type AsyncHandler func([]byte) (byte, []byte) | ||||||
|  | 
 | ||||||
|  | // PipeHandler is the bidirectional stream mode (blocking).
 | ||||||
|  | type PipeHandler func(in <-chan []byte, out chan<- *frame.PayloadFrame) | ||||||
|  | @ -0,0 +1,125 @@ | ||||||
|  | package hpds_err | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	quic "github.com/lucas-clemente/quic-go" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // HpdsError hpds error
 | ||||||
|  | type HpdsError struct { | ||||||
|  | 	errorCode ErrorCode | ||||||
|  | 	err       error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New create hpds error
 | ||||||
|  | func New(code ErrorCode, err error) *HpdsError { | ||||||
|  | 	return &HpdsError{ | ||||||
|  | 		errorCode: code, | ||||||
|  | 		err:       err, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *HpdsError) Error() string { | ||||||
|  | 	return fmt.Sprintf("%s error: message=%s", e.errorCode, e.err.Error()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ErrorCode error code
 | ||||||
|  | type ErrorCode uint64 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// ErrorCodeClientAbort client abort
 | ||||||
|  | 	ErrorCodeClientAbort ErrorCode = 0x00 | ||||||
|  | 	// ErrorCodeUnknown unknown error
 | ||||||
|  | 	ErrorCodeUnknown ErrorCode = 0xC0 | ||||||
|  | 	// ErrorCodeClosed net closed
 | ||||||
|  | 	ErrorCodeClosed ErrorCode = 0xC1 | ||||||
|  | 	// ErrorCodeBeforeHandler before handler
 | ||||||
|  | 	ErrorCodeBeforeHandler ErrorCode = 0xC2 | ||||||
|  | 	// ErrorCodeMainHandler main handler
 | ||||||
|  | 	ErrorCodeMainHandler ErrorCode = 0xC3 | ||||||
|  | 	// ErrorCodeAfterHandler after handler
 | ||||||
|  | 	ErrorCodeAfterHandler ErrorCode = 0xC4 | ||||||
|  | 	// ErrorCodeHandshake handshake frame
 | ||||||
|  | 	ErrorCodeHandshake ErrorCode = 0xC5 | ||||||
|  | 	// ErrorCodeRejected server rejected
 | ||||||
|  | 	ErrorCodeRejected ErrorCode = 0xCC | ||||||
|  | 	// ErrorCodeGoaway goaway frame
 | ||||||
|  | 	ErrorCodeGoaway ErrorCode = 0xCF | ||||||
|  | 	// ErrorCodeData data frame
 | ||||||
|  | 	ErrorCodeData ErrorCode = 0xCE | ||||||
|  | 	// ErrorCodeUnknownClient unknown client error
 | ||||||
|  | 	ErrorCodeUnknownClient ErrorCode = 0xCD | ||||||
|  | 	// ErrorCodeDuplicateName unknown client error
 | ||||||
|  | 	ErrorCodeDuplicateName ErrorCode = 0xC6 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (e ErrorCode) String() string { | ||||||
|  | 	switch e { | ||||||
|  | 	case ErrorCodeClientAbort: | ||||||
|  | 		return "ClientAbort" | ||||||
|  | 	case ErrorCodeUnknown: | ||||||
|  | 		return "UnknownError" | ||||||
|  | 	case ErrorCodeClosed: | ||||||
|  | 		return "NetClosed" | ||||||
|  | 	case ErrorCodeBeforeHandler: | ||||||
|  | 		return "BeforeHandler" | ||||||
|  | 	case ErrorCodeMainHandler: | ||||||
|  | 		return "MainHandler" | ||||||
|  | 	case ErrorCodeAfterHandler: | ||||||
|  | 		return "AfterHandler" | ||||||
|  | 	case ErrorCodeHandshake: | ||||||
|  | 		return "Handshake" | ||||||
|  | 	case ErrorCodeRejected: | ||||||
|  | 		return "Rejected" | ||||||
|  | 	case ErrorCodeGoaway: | ||||||
|  | 		return "Goaway" | ||||||
|  | 	case ErrorCodeData: | ||||||
|  | 		return "DataFrame" | ||||||
|  | 	case ErrorCodeUnknownClient: | ||||||
|  | 		return "UnknownClient" | ||||||
|  | 	case ErrorCodeDuplicateName: | ||||||
|  | 		return "DuplicateName" | ||||||
|  | 	default: | ||||||
|  | 		return "XXX" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Is parse quic ApplicationErrorCode to hpds ErrorCode
 | ||||||
|  | func Is(he quic.ApplicationErrorCode, yerr ErrorCode) bool { | ||||||
|  | 	return uint64(he) == uint64(yerr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Parse parse quic ApplicationErrorCode
 | ||||||
|  | func Parse(he quic.ApplicationErrorCode) ErrorCode { | ||||||
|  | 	return ErrorCode(he) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // To convert hpds ErrorCode to quic ApplicationErrorCode
 | ||||||
|  | func To(code ErrorCode) quic.ApplicationErrorCode { | ||||||
|  | 	return quic.ApplicationErrorCode(code) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DuplicateNameError duplicate name(sfn)
 | ||||||
|  | type DuplicateNameError struct { | ||||||
|  | 	connId string | ||||||
|  | 	err    error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDuplicateNameError create a duplicate name error
 | ||||||
|  | func NewDuplicateNameError(connId string, err error) DuplicateNameError { | ||||||
|  | 	return DuplicateNameError{ | ||||||
|  | 		connId: connId, | ||||||
|  | 		err:    err, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Error raw error
 | ||||||
|  | func (e DuplicateNameError) Error() string { | ||||||
|  | 	return e.err.Error() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ConnId duplicate connection ID
 | ||||||
|  | func (e DuplicateNameError) ConnId() string { | ||||||
|  | 	return e.connId | ||||||
|  | } | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | package id | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | 	gonanoid "github.com/matoous/go-nanoid/v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // New generate id
 | ||||||
|  | func New() string { | ||||||
|  | 	id, err := gonanoid.New() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Errorf("generated id err=%v", err) | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return id | ||||||
|  | } | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | 	"github.com/lucas-clemente/quic-go" | ||||||
|  | 	"net" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	pkgtls "git.hpds.cc/Component/network/tls" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A Listener for incoming connections
 | ||||||
|  | type Listener interface { | ||||||
|  | 	quic.Listener | ||||||
|  | 	// Name Listener's name
 | ||||||
|  | 	Name() string | ||||||
|  | 	// Versions get Version
 | ||||||
|  | 	Versions() []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ Listener = (*defaultListener)(nil) | ||||||
|  | 
 | ||||||
|  | type defaultListener struct { | ||||||
|  | 	conf *quic.Config | ||||||
|  | 	quic.Listener | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DefaultQuicConfig be used when `quicConfig` is nil.
 | ||||||
|  | var DefaultQuicConfig = &quic.Config{ | ||||||
|  | 	Versions:                       []quic.VersionNumber{quic.Version1, quic.VersionDraft29}, | ||||||
|  | 	MaxIdleTimeout:                 time.Second * 5, | ||||||
|  | 	KeepAlivePeriod:                time.Second * 2, | ||||||
|  | 	MaxIncomingStreams:             1000, | ||||||
|  | 	MaxIncomingUniStreams:          1000, | ||||||
|  | 	HandshakeIdleTimeout:           time.Second * 3, | ||||||
|  | 	InitialStreamReceiveWindow:     1024 * 1024 * 2, | ||||||
|  | 	InitialConnectionReceiveWindow: 1024 * 1024 * 2, | ||||||
|  | 	// DisablePathMTUDiscovery:        true,
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newListener(conn net.PacketConn, tlsConfig *tls.Config, quicConfig *quic.Config) (*defaultListener, error) { | ||||||
|  | 	if tlsConfig == nil { | ||||||
|  | 		tc, err := pkgtls.CreateServerTLSConfig(conn.LocalAddr().String()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Errorf("%sCreateServerTLSConfig: %v", ServerLogPrefix, err) | ||||||
|  | 			return &defaultListener{}, err | ||||||
|  | 		} | ||||||
|  | 		tlsConfig = tc | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if quicConfig == nil { | ||||||
|  | 		quicConfig = DefaultQuicConfig | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	quicListener, err := quic.Listen(conn, tlsConfig, quicConfig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Errorf("%squic Listen: %v", ServerLogPrefix, err) | ||||||
|  | 		return &defaultListener{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &defaultListener{conf: quicConfig, Listener: quicListener}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *defaultListener) Name() string { return "QUIC-Server" } | ||||||
|  | 
 | ||||||
|  | func (l *defaultListener) Versions() []string { | ||||||
|  | 	versions := make([]string, len(l.conf.Versions)) | ||||||
|  | 	for k, v := range l.conf.Versions { | ||||||
|  | 		versions[k] = v.String() | ||||||
|  | 	} | ||||||
|  | 	return versions | ||||||
|  | } | ||||||
|  | @ -0,0 +1,143 @@ | ||||||
|  | package log | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Level of log
 | ||||||
|  | type Level uint8 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// DebugLevel defines debug log level.
 | ||||||
|  | 	DebugLevel Level = iota + 1 | ||||||
|  | 	// InfoLevel defines info log level.
 | ||||||
|  | 	InfoLevel | ||||||
|  | 	// WarnLevel defines warn log level.
 | ||||||
|  | 	WarnLevel | ||||||
|  | 	// ErrorLevel defines error log level.
 | ||||||
|  | 	ErrorLevel | ||||||
|  | 	// NoLevel defines an absent log level.
 | ||||||
|  | 	NoLevel Level = 254 | ||||||
|  | 	// Disabled disables the logger.
 | ||||||
|  | 	Disabled Level = 255 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Logger is the interface for logger.
 | ||||||
|  | type Logger interface { | ||||||
|  | 	// SetLevel sets the logger level
 | ||||||
|  | 	SetLevel(Level) | ||||||
|  | 	// SetEncoding sets the logger's encoding
 | ||||||
|  | 	SetEncoding(encoding string) | ||||||
|  | 	// Printf logs a message without level
 | ||||||
|  | 	Printf(template string, args ...interface{}) | ||||||
|  | 	// Debugf logs a message at DebugLevel
 | ||||||
|  | 	Debugf(template string, args ...interface{}) | ||||||
|  | 	// Infof logs a message at InfoLevel
 | ||||||
|  | 	Infof(template string, args ...interface{}) | ||||||
|  | 	// Warnf logs a message at WarnLevel
 | ||||||
|  | 	Warnf(template string, args ...interface{}) | ||||||
|  | 	// Errorf logs a message at ErrorLevel
 | ||||||
|  | 	Errorf(template string, args ...interface{}) | ||||||
|  | 	// Output file path to write log message
 | ||||||
|  | 	Output(file string) | ||||||
|  | 	// ErrorOutput file path to write error message
 | ||||||
|  | 	ErrorOutput(file string) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String the logger level
 | ||||||
|  | func (l Level) String() string { | ||||||
|  | 	switch l { | ||||||
|  | 	case DebugLevel: | ||||||
|  | 		return "DEBUG" | ||||||
|  | 	case ErrorLevel: | ||||||
|  | 		return "ERROR" | ||||||
|  | 	case WarnLevel: | ||||||
|  | 		return "WARN" | ||||||
|  | 	case InfoLevel: | ||||||
|  | 		return "INFO" | ||||||
|  | 	default: | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 实例
 | ||||||
|  | var logger Logger | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	logger = Default(isEnableDebug()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetLogger allows developers to customize the logger instance.
 | ||||||
|  | func SetLogger(l Logger) { | ||||||
|  | 	logger = l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // EnableDebug enables the development model for logging.
 | ||||||
|  | // Deprecated
 | ||||||
|  | func EnableDebug() { | ||||||
|  | 	logger = Default(true) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Printf prints a formatted message without a specified level.
 | ||||||
|  | func Printf(format string, v ...interface{}) { | ||||||
|  | 	logger.Printf(format, v...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Debugf logs a message at DebugLevel.
 | ||||||
|  | func Debugf(template string, args ...interface{}) { | ||||||
|  | 	logger.Debugf(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Infof logs a message at InfoLevel.
 | ||||||
|  | func Infof(template string, args ...interface{}) { | ||||||
|  | 	logger.Infof(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Warnf logs a message at WarnLevel.
 | ||||||
|  | func Warnf(template string, args ...interface{}) { | ||||||
|  | 	logger.Warnf(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Errorf logs a message at ErrorLevel.
 | ||||||
|  | func Errorf(template string, args ...interface{}) { | ||||||
|  | 	logger.Errorf(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isEnableDebug indicates whether the debug is enabled.
 | ||||||
|  | func isEnableDebug() bool { | ||||||
|  | 	return os.Getenv("HPDS_ENABLE_DEBUG") == "true" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isJSONFormat indicates whether the log is in JSON format.
 | ||||||
|  | func isJSONFormat() bool { | ||||||
|  | 	return os.Getenv("HPDS_LOG_FORMAT") == "json" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func logFormat() string { | ||||||
|  | 	return os.Getenv("HPDS_LOG_FORMAT") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func logLevel() Level { | ||||||
|  | 	envLevel := strings.ToLower(os.Getenv("HPDS_LOG_LEVEL")) | ||||||
|  | 	level := ErrorLevel | ||||||
|  | 	switch envLevel { | ||||||
|  | 	case "debug": | ||||||
|  | 		return DebugLevel | ||||||
|  | 	case "info": | ||||||
|  | 		return InfoLevel | ||||||
|  | 	case "warn": | ||||||
|  | 		return WarnLevel | ||||||
|  | 	case "error": | ||||||
|  | 		return ErrorLevel | ||||||
|  | 	} | ||||||
|  | 	return level | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func output() string { | ||||||
|  | 	return strings.ToLower(os.Getenv("HPDS_LOG_OUTPUT")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func errorOutput() string { | ||||||
|  | 	return strings.ToLower(os.Getenv("HPDS_LOG_ERROR_OUTPUT")) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,227 @@ | ||||||
|  | package log | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	stdlog "log" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap" | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | 	"gopkg.in/natefinch/lumberjack.v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	timeFormat = "2006-01-02 15:04:05.000" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // zapLogger is the logger implementation in go.uber.org/zap
 | ||||||
|  | type zapLogger struct { | ||||||
|  | 	level       zapcore.Level | ||||||
|  | 	debug       bool | ||||||
|  | 	encoding    string | ||||||
|  | 	opts        []zap.Option | ||||||
|  | 	logger      *zap.Logger | ||||||
|  | 	instance    *zap.SugaredLogger | ||||||
|  | 	output      string | ||||||
|  | 	errorOutput string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Default the default logger instance
 | ||||||
|  | func Default(debug ...bool) Logger { | ||||||
|  | 	z := New() | ||||||
|  | 	z.SetLevel(logLevel()) | ||||||
|  | 	if isJSONFormat() { | ||||||
|  | 		z.SetEncoding("json") | ||||||
|  | 	} | ||||||
|  | 	// env debug
 | ||||||
|  | 	if isEnableDebug() { | ||||||
|  | 		z.SetLevel(DebugLevel) | ||||||
|  | 	} | ||||||
|  | 	if len(debug) > 0 { | ||||||
|  | 		if debug[0] { | ||||||
|  | 			z.SetLevel(DebugLevel) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	z.Output(output()) | ||||||
|  | 	z.ErrorOutput(errorOutput()) | ||||||
|  | 
 | ||||||
|  | 	return z | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New create new logger instance
 | ||||||
|  | func New(opts ...zap.Option) Logger { | ||||||
|  | 	// std logger
 | ||||||
|  | 	stdlog.Default().SetFlags(0) | ||||||
|  | 	stdlog.Default().SetOutput(new(logWriter)) | ||||||
|  | 
 | ||||||
|  | 	z := zapLogger{ | ||||||
|  | 		level:    zap.ErrorLevel, | ||||||
|  | 		debug:    false, | ||||||
|  | 		encoding: "console", | ||||||
|  | 		opts:     opts, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &z | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func openSinks(cfg zap.Config) (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { | ||||||
|  | 	sink, closeOut, err := zap.Open(cfg.OutputPaths...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 	errSink, _, err := zap.Open(cfg.ErrorOutputPaths...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		closeOut() | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 	return sink, errSink, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetEncoding set logger message coding
 | ||||||
|  | func (z *zapLogger) SetEncoding(enc string) { | ||||||
|  | 	z.encoding = enc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetLevel set logger level
 | ||||||
|  | func (z *zapLogger) SetLevel(lvl Level) { | ||||||
|  | 	isDebug := lvl == DebugLevel | ||||||
|  | 	level := zap.ErrorLevel | ||||||
|  | 	switch lvl { | ||||||
|  | 	case DebugLevel: | ||||||
|  | 		level = zap.DebugLevel | ||||||
|  | 	case InfoLevel: | ||||||
|  | 		level = zap.InfoLevel | ||||||
|  | 	case WarnLevel: | ||||||
|  | 		level = zap.WarnLevel | ||||||
|  | 	case ErrorLevel: | ||||||
|  | 		level = zap.ErrorLevel | ||||||
|  | 	} | ||||||
|  | 	z.level = level | ||||||
|  | 	z.debug = isDebug | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Output file path to write log message
 | ||||||
|  | func (z *zapLogger) Output(file string) { | ||||||
|  | 	if file != "" { | ||||||
|  | 		z.output = file | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ErrorOutput file path to write log message
 | ||||||
|  | func (z *zapLogger) ErrorOutput(file string) { | ||||||
|  | 	if file != "" { | ||||||
|  | 		z.errorOutput = file | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Printf logs a message wihout level
 | ||||||
|  | func (z *zapLogger) Printf(format string, v ...interface{}) { | ||||||
|  | 	stdlog.Printf(format, v...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Debugf logs a message at DebugLevel
 | ||||||
|  | func (z *zapLogger) Debugf(template string, args ...interface{}) { | ||||||
|  | 	z.Instance().Debugf(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Infof logs a message at InfoLevel
 | ||||||
|  | func (z *zapLogger) Infof(template string, args ...interface{}) { | ||||||
|  | 	z.Instance().Infof(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Warnf logs a message at WarnLevel
 | ||||||
|  | func (z zapLogger) Warnf(template string, args ...interface{}) { | ||||||
|  | 	z.Instance().Warnf(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Errorf logs a message at ErrorLevel
 | ||||||
|  | func (z zapLogger) Errorf(template string, args ...interface{}) { | ||||||
|  | 	z.Instance().Errorf(template, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (z *zapLogger) Instance() *zap.SugaredLogger { | ||||||
|  | 	if z.instance == nil { | ||||||
|  | 		// zap
 | ||||||
|  | 		encoderConfig := zapcore.EncoderConfig{ | ||||||
|  | 			TimeKey:        "ts", | ||||||
|  | 			LevelKey:       "level", | ||||||
|  | 			NameKey:        "logger", | ||||||
|  | 			CallerKey:      "caller", | ||||||
|  | 			FunctionKey:    zapcore.OmitKey, | ||||||
|  | 			MessageKey:     "msg", | ||||||
|  | 			StacktraceKey:  "stacktrace", | ||||||
|  | 			LineEnding:     zapcore.DefaultLineEnding, | ||||||
|  | 			EncodeLevel:    zapcore.CapitalColorLevelEncoder, | ||||||
|  | 			EncodeTime:     timeEncoder, | ||||||
|  | 			EncodeDuration: zapcore.SecondsDurationEncoder, | ||||||
|  | 			EncodeCaller:   zapcore.ShortCallerEncoder, | ||||||
|  | 		} | ||||||
|  | 		cfg := zap.Config{ | ||||||
|  | 			Level:             zap.NewAtomicLevelAt(zap.ErrorLevel), | ||||||
|  | 			Development:       z.debug, | ||||||
|  | 			DisableCaller:     true, | ||||||
|  | 			DisableStacktrace: true, | ||||||
|  | 			Encoding:          z.encoding, | ||||||
|  | 			EncoderConfig:     encoderConfig, | ||||||
|  | 			OutputPaths:       []string{"stderr"}, | ||||||
|  | 			ErrorOutputPaths:  []string{"stderr"}, | ||||||
|  | 		} | ||||||
|  | 		cfg.Level.SetLevel(z.level) | ||||||
|  | 		if z.debug { | ||||||
|  | 			// set the minimal level to debug
 | ||||||
|  | 			cfg.Level.SetLevel(zap.DebugLevel) | ||||||
|  | 		} | ||||||
|  | 		// output
 | ||||||
|  | 		if z.output != "" { | ||||||
|  | 			cfg.OutputPaths = append(cfg.OutputPaths, z.output) | ||||||
|  | 		} | ||||||
|  | 		encoder := zapcore.NewConsoleEncoder(encoderConfig) | ||||||
|  | 		sink, _, err := openSinks(cfg) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 		core := zapcore.NewCore(encoder, sink, cfg.Level) | ||||||
|  | 		// error output
 | ||||||
|  | 		if z.errorOutput != "" { | ||||||
|  | 			rotatedLogger := errorRotatedLogger(z.errorOutput, 10, 30, 7) | ||||||
|  | 			errorOutputOption := zap.Hooks(func(entry zapcore.Entry) error { | ||||||
|  | 				if entry.Level == zap.ErrorLevel { | ||||||
|  | 					msg, err := encoder.EncodeEntry(entry, nil) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 					rotatedLogger.Write(msg.Bytes()) | ||||||
|  | 				} | ||||||
|  | 				return nil | ||||||
|  | 			}) | ||||||
|  | 			z.opts = append(z.opts, errorOutputOption) | ||||||
|  | 		} | ||||||
|  | 		l := zap.New(core, z.opts...) | ||||||
|  | 
 | ||||||
|  | 		z.logger = l | ||||||
|  | 		z.instance = z.logger.Sugar() | ||||||
|  | 	} | ||||||
|  | 	return z.instance | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func errorRotatedLogger(file string, maxSize, maxBacukups, maxAge int) *lumberjack.Logger { | ||||||
|  | 	return &lumberjack.Logger{ | ||||||
|  | 		Filename:   file, | ||||||
|  | 		MaxSize:    maxSize, | ||||||
|  | 		MaxBackups: maxBacukups, | ||||||
|  | 		MaxAge:     maxAge, | ||||||
|  | 		Compress:   false, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendString(t.Format(timeFormat)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type logWriter struct{} | ||||||
|  | 
 | ||||||
|  | func (l logWriter) Write(bytes []byte) (int, error) { | ||||||
|  | 	os.Stderr.WriteString(time.Now().Format(timeFormat)) | ||||||
|  | 	os.Stderr.Write([]byte("\t")) | ||||||
|  | 	return os.Stderr.Write(bytes) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import "git.hpds.cc/Component/network/frame" | ||||||
|  | 
 | ||||||
|  | // Metadata is used for storing extra info of the application
 | ||||||
|  | type Metadata interface { | ||||||
|  | 	// Encode is the serialize method
 | ||||||
|  | 	Encode() []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MetadataBuilder is the builder of Metadata
 | ||||||
|  | type MetadataBuilder interface { | ||||||
|  | 	// Build will return a Metadata instance according to the handshake frame passed in
 | ||||||
|  | 	Build(f *frame.HandshakeFrame) (Metadata, error) | ||||||
|  | 	// Decode is the deserialize method
 | ||||||
|  | 	Decode(buf []byte) (Metadata, error) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	coder "git.hpds.cc/Component/mq_coder" | ||||||
|  | 	"git.hpds.cc/Component/network/frame" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ParseFrame parses the frame from QUIC stream.
 | ||||||
|  | func ParseFrame(stream io.Reader) (frame.Frame, error) { | ||||||
|  | 	buf, err := coder.ReadPacket(stream) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	frameType := buf[0] | ||||||
|  | 	// determine the frame type
 | ||||||
|  | 	switch frameType { | ||||||
|  | 	case 0x80 | byte(frame.TagOfHandshakeFrame): | ||||||
|  | 		handshakeFrame, err := readHandshakeFrame(buf) | ||||||
|  | 		// logger.Debugf("%sHandshakeFrame: name=%s, type=%s", ParseFrameLogPrefix, handshakeFrame.Name, handshakeFrame.Type())
 | ||||||
|  | 		return handshakeFrame, err | ||||||
|  | 	case 0x80 | byte(frame.TagOfDataFrame): | ||||||
|  | 		data, err := readDataFrame(buf) | ||||||
|  | 		// logger.Debugf("%sDataFrame: tid=%s, tag=%#x, len(carriage)=%d", ParseFrameLogPrefix, data.TransactionID(), data.GetDataTag(), len(data.GetCarriage()))
 | ||||||
|  | 		return data, err | ||||||
|  | 	case 0x80 | byte(frame.TagOfAcceptedFrame): | ||||||
|  | 		return frame.DecodeToAcceptedFrame(buf) | ||||||
|  | 	case 0x80 | byte(frame.TagOfRejectedFrame): | ||||||
|  | 		return frame.DecodeToRejectedFrame(buf) | ||||||
|  | 	case 0x80 | byte(frame.TagOfGoawayFrame): | ||||||
|  | 		return frame.DecodeToGoawayFrame(buf) | ||||||
|  | 	case 0x80 | byte(frame.TagOfBackFlowFrame): | ||||||
|  | 		return frame.DecodeToBackFlowFrame(buf) | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("unknown frame type, buf[0]=%#x", buf[0]) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func readHandshakeFrame(buf []byte) (*frame.HandshakeFrame, error) { | ||||||
|  | 	return frame.DecodeToHandshakeFrame(buf) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func readDataFrame(buf []byte) (*frame.DataFrame, error) { | ||||||
|  | 	return frame.DecodeToDataFrame(buf) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | // Router is the interface to manage the routes for applications.
 | ||||||
|  | type Router interface { | ||||||
|  | 	// Route gets the route
 | ||||||
|  | 	Route(metadata Metadata) Route | ||||||
|  | 	// Clean the routes.
 | ||||||
|  | 	Clean() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Route manages data subscribers according to their observed data tags.
 | ||||||
|  | type Route interface { | ||||||
|  | 	// Add a route.
 | ||||||
|  | 	Add(connId string, name string, observeDataTags []byte) error | ||||||
|  | 	// Remove a route.
 | ||||||
|  | 	Remove(connId string) error | ||||||
|  | 	// GetForwardRoutes returns all the subscribers by the given data tag.
 | ||||||
|  | 	GetForwardRoutes(tag byte) []string | ||||||
|  | } | ||||||
|  | @ -0,0 +1,567 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"os" | ||||||
|  | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 
 | ||||||
|  | 	// authentication implements, Currently, only token authentication is implemented
 | ||||||
|  | 	_ "git.hpds.cc/Component/network/auth" | ||||||
|  | 	"git.hpds.cc/Component/network/frame" | ||||||
|  | 	"git.hpds.cc/Component/network/hpds_err" | ||||||
|  | 	"git.hpds.cc/Component/network/log" | ||||||
|  | 	pkgtls "git.hpds.cc/Component/network/tls" | ||||||
|  | 	"github.com/lucas-clemente/quic-go" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// DefaultListenAddr is the default address to listen.
 | ||||||
|  | 	DefaultListenAddr = "0.0.0.0:9000" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ServerOption is the option for server.
 | ||||||
|  | type ServerOption func(*ServerOptions) | ||||||
|  | 
 | ||||||
|  | // FrameHandler is the handler for frame.
 | ||||||
|  | type FrameHandler func(c *Context) error | ||||||
|  | 
 | ||||||
|  | // Server is the underlining server of Message Queue
 | ||||||
|  | type Server struct { | ||||||
|  | 	name               string | ||||||
|  | 	state              string | ||||||
|  | 	connector          Connector | ||||||
|  | 	router             Router | ||||||
|  | 	metadataBuilder    MetadataBuilder | ||||||
|  | 	counterOfDataFrame int64 | ||||||
|  | 	downStreams        map[string]*Client | ||||||
|  | 	mu                 sync.Mutex | ||||||
|  | 	opts               ServerOptions | ||||||
|  | 	beforeHandlers     []FrameHandler | ||||||
|  | 	afterHandlers      []FrameHandler | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewServer create a Server instance.
 | ||||||
|  | func NewServer(name string, opts ...ServerOption) *Server { | ||||||
|  | 	s := &Server{ | ||||||
|  | 		name:        name, | ||||||
|  | 		connector:   newConnector(), | ||||||
|  | 		downStreams: make(map[string]*Client), | ||||||
|  | 	} | ||||||
|  | 	s.Init(opts...) | ||||||
|  | 
 | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Init the options.
 | ||||||
|  | func (s *Server) Init(opts ...ServerOption) error { | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&s.opts) | ||||||
|  | 	} | ||||||
|  | 	// options defaults
 | ||||||
|  | 	s.initOptions() | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ListenAndServe starts the server.
 | ||||||
|  | func (s *Server) ListenAndServe(ctx context.Context, addr string) error { | ||||||
|  | 	if addr == "" { | ||||||
|  | 		addr = DefaultListenAddr | ||||||
|  | 	} | ||||||
|  | 	udpAddr, err := net.ResolveUDPAddr("udp", addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	conn, err := net.ListenUDP("udp", udpAddr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return s.Serve(ctx, conn) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Serve the server with a net.PacketConn.
 | ||||||
|  | func (s *Server) Serve(ctx context.Context, conn net.PacketConn) error { | ||||||
|  | 	if err := s.validateMetadataBuilder(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := s.validateRouter(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// listen the address
 | ||||||
|  | 	listener, err := newListener(conn, s.opts.TLSConfig, s.opts.QuicConfig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Errorf("%slistener.Listen: err=%v", ServerLogPrefix, err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer listener.Close() | ||||||
|  | 	log.Printf("%s [%s][%d] Listening on: %s, MODE: %s, QUIC: %v, AUTH: %s", ServerLogPrefix, s.name, os.Getpid(), listener.Addr(), mode(), listener.Versions(), s.authNames()) | ||||||
|  | 
 | ||||||
|  | 	s.state = ConnStateConnected | ||||||
|  | 	for { | ||||||
|  | 		// create a new connection when new hpds-client connected
 | ||||||
|  | 		sctx, cancel := context.WithCancel(ctx) | ||||||
|  | 		defer cancel() | ||||||
|  | 
 | ||||||
|  | 		connect, e := listener.Accept(sctx) | ||||||
|  | 		if e != nil { | ||||||
|  | 			log.Errorf("%screate connection error: %v", ServerLogPrefix, e) | ||||||
|  | 			return e | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		connID := GetConnId(connect) | ||||||
|  | 		log.Infof("%s1/ new connection: %s", ServerLogPrefix, connID) | ||||||
|  | 
 | ||||||
|  | 		go func(ctx context.Context, qconn quic.Connection) { | ||||||
|  | 			for { | ||||||
|  | 				log.Infof("%s2/ waiting for new stream", ServerLogPrefix) | ||||||
|  | 				stream, err := qconn.AcceptStream(ctx) | ||||||
|  | 				if err != nil { | ||||||
|  | 					// if client close the connection, then we should close the connection
 | ||||||
|  | 					// @CC: when Source close the connection, it won't affect connectors
 | ||||||
|  | 					name := "--" | ||||||
|  | 					if conn := s.connector.Get(connID); conn != nil { | ||||||
|  | 						conn.Close() | ||||||
|  | 						// connector
 | ||||||
|  | 						s.connector.Remove(connID) | ||||||
|  | 						route := s.router.Route(conn.Metadata()) | ||||||
|  | 						if route != nil { | ||||||
|  | 							route.Remove(connID) | ||||||
|  | 						} | ||||||
|  | 						name = conn.Name() | ||||||
|  | 					} | ||||||
|  | 					log.Printf("%s [%s](%s) close the connection: %v", ServerLogPrefix, name, connID, err) | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 				defer stream.Close() | ||||||
|  | 
 | ||||||
|  | 				log.Infof("%s3/ [stream:%d] created, connId=%s", ServerLogPrefix, stream.StreamID(), connID) | ||||||
|  | 				// process frames on stream
 | ||||||
|  | 				// c := newContext(connId, stream)
 | ||||||
|  | 				c := newContext(connect, stream) | ||||||
|  | 				defer c.Clean() | ||||||
|  | 				s.handleConnection(c) | ||||||
|  | 				log.Infof("%s4/ [stream:%d] handleConnection DONE", ServerLogPrefix, stream.StreamID()) | ||||||
|  | 			} | ||||||
|  | 		}(sctx, connect) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close will shut down the server.
 | ||||||
|  | func (s *Server) Close() error { | ||||||
|  | 	if s.router != nil { | ||||||
|  | 		s.router.Clean() | ||||||
|  | 	} | ||||||
|  | 	// connector
 | ||||||
|  | 	if s.connector != nil { | ||||||
|  | 		s.connector.Clean() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // handle streams on a connection
 | ||||||
|  | func (s *Server) handleConnection(c *Context) { | ||||||
|  | 	fs := NewFrameStream(c.Stream) | ||||||
|  | 	// check update for stream
 | ||||||
|  | 	for { | ||||||
|  | 		log.Debugf("%shandleConnection waiting read next...", ServerLogPrefix) | ||||||
|  | 		f, err := fs.ReadFrame() | ||||||
|  | 		if err != nil { | ||||||
|  | 			// if client close connection, will get ApplicationError with code = 0x00
 | ||||||
|  | 			if e, ok := err.(*quic.ApplicationError); ok { | ||||||
|  | 				if hpds_err.Is(e.ErrorCode, hpds_err.ErrorCodeClientAbort) { | ||||||
|  | 					// client abort
 | ||||||
|  | 					log.Infof("%sclient close the connection", ServerLogPrefix) | ||||||
|  | 					break | ||||||
|  | 				} else { | ||||||
|  | 					ye := hpds_err.New(hpds_err.Parse(e.ErrorCode), err) | ||||||
|  | 					log.Errorf("%s[ERR] %s", ServerLogPrefix, ye) | ||||||
|  | 				} | ||||||
|  | 			} else if err == io.EOF { | ||||||
|  | 				log.Infof("%sthe connection is EOF", ServerLogPrefix) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			if errors.Is(err, net.ErrClosed) { | ||||||
|  | 				// if client close the connection, net.ErrClosed will be raised
 | ||||||
|  | 				// by quic-go IdleTimeoutError after connection's KeepAlive config.
 | ||||||
|  | 				log.Warnf("%s[ERR] net.ErrClosed on [handleConnection] %v", ServerLogPrefix, net.ErrClosed) | ||||||
|  | 				c.CloseWithError(hpds_err.ErrorCodeClosed, "net.ErrClosed") | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			// any error occurred, we should close the stream
 | ||||||
|  | 			// after this, conn.AcceptStream() will raise the error
 | ||||||
|  | 			c.CloseWithError(hpds_err.ErrorCodeUnknown, err.Error()) | ||||||
|  | 			log.Warnf("%sconnection.Close()", ServerLogPrefix) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		frameType := f.Type() | ||||||
|  | 		data := f.Encode() | ||||||
|  | 		log.Debugf("%stype=%s, frame[%d]=%# x", ServerLogPrefix, frameType, len(data), frame.Shortly(data)) | ||||||
|  | 		// add frame to context
 | ||||||
|  | 		context := c.WithFrame(f) | ||||||
|  | 
 | ||||||
|  | 		// before frame handlers
 | ||||||
|  | 		for _, handler := range s.beforeHandlers { | ||||||
|  | 			if e := handler(context); e != nil { | ||||||
|  | 				log.Errorf("%safterFrameHandler e: %s", ServerLogPrefix, e) | ||||||
|  | 				context.CloseWithError(hpds_err.ErrorCodeBeforeHandler, e.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// main handler
 | ||||||
|  | 		if e := s.mainFrameHandler(context); e != nil { | ||||||
|  | 			log.Errorf("%smainFrameHandler e: %s", ServerLogPrefix, e) | ||||||
|  | 			context.CloseWithError(hpds_err.ErrorCodeMainHandler, e.Error()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		// after frame handler
 | ||||||
|  | 		for _, handler := range s.afterHandlers { | ||||||
|  | 			if e := handler(context); e != nil { | ||||||
|  | 				log.Errorf("%safterFrameHandler e: %s", ServerLogPrefix, e) | ||||||
|  | 				context.CloseWithError(hpds_err.ErrorCodeAfterHandler, e.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) mainFrameHandler(c *Context) error { | ||||||
|  | 	var err error | ||||||
|  | 	frameType := c.Frame.Type() | ||||||
|  | 
 | ||||||
|  | 	switch frameType { | ||||||
|  | 	case frame.TagOfHandshakeFrame: | ||||||
|  | 		if err = s.handleHandshakeFrame(c); err != nil { | ||||||
|  | 			log.Errorf("%shandleHandshakeFrame err: %s", ServerLogPrefix, err) | ||||||
|  | 			// close connections early to avoid resource consumption
 | ||||||
|  | 			if c.Stream != nil { | ||||||
|  | 				goawayFrame := frame.NewGoawayFrame(err.Error()) | ||||||
|  | 				if _, e := c.Stream.Write(goawayFrame.Encode()); e != nil { | ||||||
|  | 					log.Errorf("%s write to client[%s] GoawayFrame error:%v", ServerLogPrefix, c.ConnId, e) | ||||||
|  | 					return e | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	// case frame.TagOfPingFrame:
 | ||||||
|  | 	// 	s.handlePingFrame(mainStream, connection, f.(*frame.PingFrame))
 | ||||||
|  | 	case frame.TagOfDataFrame: | ||||||
|  | 		if err = s.handleDataFrame(c); err != nil { | ||||||
|  | 			c.CloseWithError(hpds_err.ErrorCodeData, fmt.Sprintf("handleDataFrame err: %v", err)) | ||||||
|  | 		} else { | ||||||
|  | 			conn := s.connector.Get(c.connId) | ||||||
|  | 			if conn != nil && conn.ClientType() == ClientTypeProtocolGateway { | ||||||
|  | 				f := c.Frame.(*frame.DataFrame) | ||||||
|  | 				f.GetMetaFrame().SetMetadata(conn.Metadata().Encode()) | ||||||
|  | 				s.dispatchToDownStreams(f) | ||||||
|  | 			} | ||||||
|  | 			// observe data tags back flow
 | ||||||
|  | 			s.handleBackFlowFrame(c) | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		log.Errorf("%serr=%v, frame=%v", ServerLogPrefix, err, frame.Shortly(c.Frame.Encode())) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // handle HandShakeFrame
 | ||||||
|  | func (s *Server) handleHandshakeFrame(c *Context) error { | ||||||
|  | 	f := c.Frame.(*frame.HandshakeFrame) | ||||||
|  | 
 | ||||||
|  | 	log.Debugf("%sGOT HandshakeFrame : %# x", ServerLogPrefix, f) | ||||||
|  | 	// basic info
 | ||||||
|  | 	connId := c.ConnId() | ||||||
|  | 	clientId := f.ClientId | ||||||
|  | 	clientType := ClientType(f.ClientType) | ||||||
|  | 	stream := c.Stream | ||||||
|  | 	// credential
 | ||||||
|  | 	log.Debugf("%sClientType=%# x is %s, ClientId=%s, Credential=%s", ServerLogPrefix, f.ClientType, ClientType(f.ClientType), clientId, authName(f.AuthName())) | ||||||
|  | 	// authenticate
 | ||||||
|  | 	if !s.authenticate(f) { | ||||||
|  | 		err := fmt.Errorf("handshake authentication fails, client credential name is %s", authName(f.AuthName())) | ||||||
|  | 		// return err
 | ||||||
|  | 		log.Debugf("%s <%s> [%s](%s) is connected!", ServerLogPrefix, clientType, f.Name, connId) | ||||||
|  | 		rejectedFrame := frame.NewRejectedFrame(err.Error()) | ||||||
|  | 		if _, err = stream.Write(rejectedFrame.Encode()); err != nil { | ||||||
|  | 			log.Debugf("%s write to <%s> [%s](%s) RejectedFrame error:%v", ServerLogPrefix, clientType, f.Name, connId, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// client type
 | ||||||
|  | 	var conn Connection | ||||||
|  | 	switch clientType { | ||||||
|  | 	case ClientTypeProtocolGateway, ClientTypeStreamFunction: | ||||||
|  | 		// metadata
 | ||||||
|  | 		metadata, err := s.metadataBuilder.Build(f) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		conn = newConnection(f.Name, f.ClientId, clientType, metadata, stream, f.ObserveDataTags) | ||||||
|  | 
 | ||||||
|  | 		if clientType == ClientTypeStreamFunction { | ||||||
|  | 			// route
 | ||||||
|  | 			route := s.router.Route(metadata) | ||||||
|  | 			if route == nil { | ||||||
|  | 				return errors.New("handleHandshakeFrame route is nil") | ||||||
|  | 			} | ||||||
|  | 			if e1 := route.Add(connId, f.Name, f.ObserveDataTags); e1 != nil { | ||||||
|  | 				// duplicate name
 | ||||||
|  | 				if e2, ok := e1.(hpds_err.DuplicateNameError); ok { | ||||||
|  | 					existsConnID := e2.ConnId() | ||||||
|  | 					if conn = s.connector.Get(existsConnID); conn != nil { | ||||||
|  | 						log.Debugf("%s%s, write to SFN[%s](%s) GoawayFrame", ServerLogPrefix, e2.Error(), f.Name, existsConnID) | ||||||
|  | 						goawayFrame := frame.NewGoawayFrame(e2.Error()) | ||||||
|  | 						if e3 := conn.Write(goawayFrame); e3 != nil { | ||||||
|  | 							log.Errorf("%s write to SFN[%s] GoawayFrame error:%v", ServerLogPrefix, f.Name, e3) | ||||||
|  | 							return e3 | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					return e1 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case ClientTypeMessageQueue: | ||||||
|  | 		conn = newConnection(f.Name, f.ClientId, clientType, nil, stream, f.ObserveDataTags) | ||||||
|  | 	default: | ||||||
|  | 		// unknown client type
 | ||||||
|  | 		s.connector.Remove(connId) | ||||||
|  | 		err := fmt.Errorf("Illegal ClientType: %#x", f.ClientType) | ||||||
|  | 		c.CloseWithError(hpds_err.ErrorCodeUnknownClient, err.Error()) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s.connector.Add(connId, conn) | ||||||
|  | 	log.Printf("%s <%s> [%s][%s](%s) is connected!", ServerLogPrefix, clientType, f.Name, clientId, connId) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // handle handleGoawayFrame
 | ||||||
|  | func (s *Server) handleGoawayFrame(c *Context) error { | ||||||
|  | 	f := c.Frame.(*frame.GoawayFrame) | ||||||
|  | 
 | ||||||
|  | 	log.Debugf("%s GOT GoawayFrame code=%d, message==%s", ServerLogPrefix, hpds_err.ErrorCodeGoaway, f.Message()) | ||||||
|  | 	// c.CloseWithError(f.Code(), f.Message())
 | ||||||
|  | 	_, err := c.Stream.Write(f.Encode()) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // will reuse quic-go's keep-alive feature
 | ||||||
|  | // func (s *Server) handlePingFrame(stream quic.Stream, conn quic.Connection, f *frame.PingFrame) error {
 | ||||||
|  | // 	log.Infof("%s------> GOT PingFrame : %# x", ServerLogPrefix, f)
 | ||||||
|  | // 	return nil
 | ||||||
|  | // }
 | ||||||
|  | 
 | ||||||
|  | func (s *Server) handleDataFrame(c *Context) error { | ||||||
|  | 	// counter +1
 | ||||||
|  | 	atomic.AddInt64(&s.counterOfDataFrame, 1) | ||||||
|  | 	// currentIssuer := f.GetIssuer()
 | ||||||
|  | 	fromId := c.ConnId() | ||||||
|  | 	from := s.connector.Get(fromId) | ||||||
|  | 	if from == nil { | ||||||
|  | 		log.Warnf("%shandleDataFrame connector cannot find %s", ServerLogPrefix, fromId) | ||||||
|  | 		return fmt.Errorf("handleDataFrame connector cannot find %s", fromId) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	f := c.Frame.(*frame.DataFrame) | ||||||
|  | 
 | ||||||
|  | 	var metadata Metadata | ||||||
|  | 	if from.ClientType() == ClientTypeMessageQueue { | ||||||
|  | 		m, err := s.metadataBuilder.Decode(f.GetMetaFrame().Metadata()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		metadata = m | ||||||
|  | 	} else { | ||||||
|  | 		metadata = from.Metadata() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// route
 | ||||||
|  | 	route := s.router.Route(metadata) | ||||||
|  | 	if route == nil { | ||||||
|  | 		log.Warnf("%shandleDataFrame route is nil", ServerLogPrefix) | ||||||
|  | 		return fmt.Errorf("handleDataFrame route is nil") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// get stream function connection ids from route
 | ||||||
|  | 	connIds := route.GetForwardRoutes(f.GetDataTag()) | ||||||
|  | 	for _, toId := range connIds { | ||||||
|  | 		conn := s.connector.Get(toId) | ||||||
|  | 		if conn == nil { | ||||||
|  | 			log.Errorf("%sconn is nil: (%s)", ServerLogPrefix, toId) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		to := conn.Name() | ||||||
|  | 		log.Debugf("%shandleDataFrame tag=%#x tid=%s, counter=%d, from=[%s](%s), to=[%s](%s)", ServerLogPrefix, f.Tag(), f.TransactionId(), s.counterOfDataFrame, from.Name(), fromId, to, toId) | ||||||
|  | 
 | ||||||
|  | 		// write data frame to stream
 | ||||||
|  | 		if err := conn.Write(f); err != nil { | ||||||
|  | 			log.Warnf("%shandleDataFrame conn.Write tag=%#x tid=%s, from=[%s](%s), to=[%s](%s), %v", ServerLogPrefix, f.Tag(), f.TransactionId(), from.Name(), fromId, to, toId, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) handleBackFlowFrame(c *Context) error { | ||||||
|  | 	f := c.Frame.(*frame.DataFrame) | ||||||
|  | 	tag := f.GetDataTag() | ||||||
|  | 	carriage := f.GetCarriage() | ||||||
|  | 	sourceId := f.SourceId() | ||||||
|  | 	// write to Protocol Gateway with BackFlowFrame
 | ||||||
|  | 	bf := frame.NewBackFlowFrame(tag, carriage) | ||||||
|  | 	sourceConns := s.connector.GetProtocolGatewayConnections(sourceId, tag) | ||||||
|  | 	// conn := s.connector.Get(c.connId)
 | ||||||
|  | 	// logger.Printf("%s handleBackFlowFrame tag:%#v --> source:%s, result=%s", ServerLogPrefix, tag, sourceId, carriage)
 | ||||||
|  | 	for _, source := range sourceConns { | ||||||
|  | 		if source != nil { | ||||||
|  | 			log.Debugf("%s handleBackFlowFrame tag:%#v --> Protocol Gateway:%s, result=%# x", ServerLogPrefix, tag, sourceId, frame.Shortly(carriage)) | ||||||
|  | 			if err := source.Write(bf); err != nil { | ||||||
|  | 				log.Errorf("%s handleBackFlowFrame tag:%#v --> Protocol Gateway:%s, error=%v", ServerLogPrefix, tag, sourceId, err) | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StatsFunctions returns the sfn stats of server.
 | ||||||
|  | func (s *Server) StatsFunctions() map[string]string { | ||||||
|  | 	return s.connector.GetSnapshot() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StatsCounter returns how many DataFrames pass through server.
 | ||||||
|  | func (s *Server) StatsCounter() int64 { | ||||||
|  | 	return s.counterOfDataFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DownStreams return all the downstream servers.
 | ||||||
|  | func (s *Server) DownStreams() map[string]*Client { | ||||||
|  | 	return s.downStreams | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ConfigRouter is used to set router by Message Queue
 | ||||||
|  | func (s *Server) ConfigRouter(router Router) { | ||||||
|  | 	s.mu.Lock() | ||||||
|  | 	s.router = router | ||||||
|  | 	log.Debugf("%sconfig router is %#v", ServerLogPrefix, router) | ||||||
|  | 	s.mu.Unlock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ConfigMetadataBuilder is used to set metadataBuilder by Message Queue
 | ||||||
|  | func (s *Server) ConfigMetadataBuilder(builder MetadataBuilder) { | ||||||
|  | 	s.mu.Lock() | ||||||
|  | 	s.metadataBuilder = builder | ||||||
|  | 	log.Debugf("%sconfig metadataBuilder is %#v", ServerLogPrefix, builder) | ||||||
|  | 	s.mu.Unlock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddDownstreamServer add a downstream server to this server. all the DataFrames will be
 | ||||||
|  | // dispatch to all the downStreams.
 | ||||||
|  | func (s *Server) AddDownstreamServer(addr string, c *Client) { | ||||||
|  | 	s.mu.Lock() | ||||||
|  | 	s.downStreams[addr] = c | ||||||
|  | 	s.mu.Unlock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // dispatch every DataFrames to all downStreams
 | ||||||
|  | func (s *Server) dispatchToDownStreams(df *frame.DataFrame) { | ||||||
|  | 	for addr, ds := range s.downStreams { | ||||||
|  | 		log.Debugf("%sdispatching to [%s]: %# x", ServerLogPrefix, addr, df.Tag()) | ||||||
|  | 		ds.WriteFrame(df) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetConnId get quic connection id
 | ||||||
|  | func GetConnId(conn quic.Connection) string { | ||||||
|  | 	return conn.RemoteAddr().String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) initOptions() { | ||||||
|  | 	// defaults
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) validateRouter() error { | ||||||
|  | 	if s.router == nil { | ||||||
|  | 		return errors.New("server's router is nil") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) validateMetadataBuilder() error { | ||||||
|  | 	if s.metadataBuilder == nil { | ||||||
|  | 		return errors.New("server's metadataBuilder is nil") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Options returns the options of server.
 | ||||||
|  | func (s *Server) Options() ServerOptions { | ||||||
|  | 	return s.opts | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Connector returns the connector of server.
 | ||||||
|  | func (s *Server) Connector() Connector { | ||||||
|  | 	return s.connector | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetBeforeHandlers set the before handlers of server.
 | ||||||
|  | func (s *Server) SetBeforeHandlers(handlers ...FrameHandler) { | ||||||
|  | 	s.beforeHandlers = append(s.beforeHandlers, handlers...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetAfterHandlers set the after handlers of server.
 | ||||||
|  | func (s *Server) SetAfterHandlers(handlers ...FrameHandler) { | ||||||
|  | 	s.afterHandlers = append(s.afterHandlers, handlers...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) authNames() []string { | ||||||
|  | 	if len(s.opts.Auths) == 0 { | ||||||
|  | 		return []string{"none"} | ||||||
|  | 	} | ||||||
|  | 	result := make([]string, 0) | ||||||
|  | 	for _, auth := range s.opts.Auths { | ||||||
|  | 		result = append(result, auth.Name()) | ||||||
|  | 	} | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) authenticate(f *frame.HandshakeFrame) bool { | ||||||
|  | 	if len(s.opts.Auths) > 0 { | ||||||
|  | 		for _, auth := range s.opts.Auths { | ||||||
|  | 			if f.AuthName() == auth.Name() { | ||||||
|  | 				isAuthenticated := auth.Authenticate(f.AuthPayload()) | ||||||
|  | 				if isAuthenticated { | ||||||
|  | 					log.Debugf("%sauthenticated==%v", ServerLogPrefix, isAuthenticated) | ||||||
|  | 					return isAuthenticated | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func mode() string { | ||||||
|  | 	if pkgtls.IsDev() { | ||||||
|  | 		return "DEVELOPMENT" | ||||||
|  | 	} | ||||||
|  | 	return "PRODUCTION" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func authName(name string) string { | ||||||
|  | 	if name == "" { | ||||||
|  | 		return "empty" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return name | ||||||
|  | } | ||||||
|  | @ -0,0 +1,56 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"net" | ||||||
|  | 
 | ||||||
|  | 	"git.hpds.cc/Component/network/auth" | ||||||
|  | 	"github.com/lucas-clemente/quic-go" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ServerOptions are the options for HPDS Network server.
 | ||||||
|  | type ServerOptions struct { | ||||||
|  | 	QuicConfig *quic.Config | ||||||
|  | 	TLSConfig  *tls.Config | ||||||
|  | 	Addr       string | ||||||
|  | 	Auths      []auth.Authentication | ||||||
|  | 	Conn       net.PacketConn | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithAddr sets the server address.
 | ||||||
|  | func WithAddr(addr string) ServerOption { | ||||||
|  | 	return func(o *ServerOptions) { | ||||||
|  | 		o.Addr = addr | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithAuth sets the server authentication method.
 | ||||||
|  | func WithAuth(name string, args ...string) ServerOption { | ||||||
|  | 	return func(o *ServerOptions) { | ||||||
|  | 		if auth, ok := auth.GetAuth(name); ok { | ||||||
|  | 			auth.Init(args...) | ||||||
|  | 			o.Auths = append(o.Auths, auth) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithServerTLSConfig sets the TLS configuration for the server.
 | ||||||
|  | func WithServerTLSConfig(tc *tls.Config) ServerOption { | ||||||
|  | 	return func(o *ServerOptions) { | ||||||
|  | 		o.TLSConfig = tc | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithServerQuicConfig sets the QUIC configuration for the server.
 | ||||||
|  | func WithServerQuicConfig(qc *quic.Config) ServerOption { | ||||||
|  | 	return func(o *ServerOptions) { | ||||||
|  | 		o.QuicConfig = qc | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithConn sets the connection for the server.
 | ||||||
|  | func WithConn(conn net.PacketConn) ServerOption { | ||||||
|  | 	return func(o *ServerOptions) { | ||||||
|  | 		o.Conn = conn | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,229 @@ | ||||||
|  | package tls | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
|  | 	"crypto/x509/pkix" | ||||||
|  | 	"encoding/pem" | ||||||
|  | 	"errors" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"math/big" | ||||||
|  | 	"net" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var isDev bool | ||||||
|  | 
 | ||||||
|  | // CreateServerTLSConfig creates server tls config.
 | ||||||
|  | func CreateServerTLSConfig(host string) (*tls.Config, error) { | ||||||
|  | 	// development mode
 | ||||||
|  | 	if isDev { | ||||||
|  | 		tc, err := developmentTLSConfig(host) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return tc, nil | ||||||
|  | 	} | ||||||
|  | 	// production mode
 | ||||||
|  | 	// ca pool
 | ||||||
|  | 	pool, err := getCACertPool() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	// server certificate
 | ||||||
|  | 	tlsCert, err := getCertAndKey() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &tls.Config{ | ||||||
|  | 		Certificates: []tls.Certificate{*tlsCert}, | ||||||
|  | 		ClientCAs:    pool, | ||||||
|  | 		ClientAuth:   tls.RequireAndVerifyClientCert, | ||||||
|  | 		NextProtos:   []string{"hpds"}, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreateClientTLSConfig creates client tls config.
 | ||||||
|  | func CreateClientTLSConfig() (*tls.Config, error) { | ||||||
|  | 	// development mode
 | ||||||
|  | 	if isDev { | ||||||
|  | 		return &tls.Config{ | ||||||
|  | 			InsecureSkipVerify: true, | ||||||
|  | 			NextProtos:         []string{"hpds"}, | ||||||
|  | 			ClientSessionCache: tls.NewLRUClientSessionCache(64), | ||||||
|  | 		}, nil | ||||||
|  | 	} | ||||||
|  | 	// production mode
 | ||||||
|  | 	pool, err := getCACertPool() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tlsCert, err := getCertAndKey() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &tls.Config{ | ||||||
|  | 		InsecureSkipVerify: false, | ||||||
|  | 		Certificates:       []tls.Certificate{*tlsCert}, | ||||||
|  | 		RootCAs:            pool, | ||||||
|  | 		NextProtos:         []string{"hpds"}, | ||||||
|  | 		ClientSessionCache: tls.NewLRUClientSessionCache(0), | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getCACertPool() (*x509.CertPool, error) { | ||||||
|  | 	var err error | ||||||
|  | 	var caCert []byte | ||||||
|  | 
 | ||||||
|  | 	caCertPath := os.Getenv("HPDS_TLS_CACERT_FILE") | ||||||
|  | 	if len(caCertPath) == 0 { | ||||||
|  | 		return nil, errors.New("tls: must provide CA certificate on production mode, you can configure this via environment variables: `HPDS_TLS_CACERT_FILE`") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	caCert, err = ioutil.ReadFile(caCertPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(caCert) == 0 { | ||||||
|  | 		return nil, errors.New("tls: cannot load CA cert") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pool := x509.NewCertPool() | ||||||
|  | 	if ok := pool.AppendCertsFromPEM(caCert); !ok { | ||||||
|  | 		return nil, errors.New("tls: cannot append CA cert to pool") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return pool, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getCertAndKey() (*tls.Certificate, error) { | ||||||
|  | 	var err error | ||||||
|  | 	var cert, key []byte | ||||||
|  | 
 | ||||||
|  | 	certPath := os.Getenv("HPDS_TLS_CERT_FILE") | ||||||
|  | 	keyPath := os.Getenv("HPDS_TLS_KEY_FILE") | ||||||
|  | 	if len(certPath) == 0 || len(keyPath) == 0 { | ||||||
|  | 		return nil, errors.New("tls: must provide certificate on production mode, you can configure this via environment variables: `HPDS_TLS_CERT_FILE` and `HPDS_TLS_KEY_FILE`") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// certificate
 | ||||||
|  | 	cert, err = ioutil.ReadFile(certPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	// private key
 | ||||||
|  | 	key, err = ioutil.ReadFile(keyPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(cert) == 0 || len(key) == 0 { | ||||||
|  | 		return nil, errors.New("tls: cannot load tls cert/key") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tlsCert, err := tls.X509KeyPair(cert, key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &tlsCert, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsDev development mode
 | ||||||
|  | func IsDev() bool { | ||||||
|  | 	return isDev | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // developmentTLSConfig Setup a bare-bones TLS config for the server
 | ||||||
|  | func developmentTLSConfig(host ...string) (*tls.Config, error) { | ||||||
|  | 	tlsCert, err := generateCertificate(host...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &tls.Config{ | ||||||
|  | 		Certificates:       []tls.Certificate{tlsCert}, | ||||||
|  | 		ClientSessionCache: tls.NewLRUClientSessionCache(1), | ||||||
|  | 		NextProtos:         []string{"hpds"}, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func generateCertificate(host ...string) (tls.Certificate, error) { | ||||||
|  | 	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tls.Certificate{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	notBefore := time.Now() | ||||||
|  | 	notAfter := notBefore.Add(time.Hour * 24 * 365) | ||||||
|  | 
 | ||||||
|  | 	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) | ||||||
|  | 	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tls.Certificate{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template := x509.Certificate{ | ||||||
|  | 		SerialNumber: serialNumber, | ||||||
|  | 		Subject: pkix.Name{ | ||||||
|  | 			Organization: []string{"HPDS"}, | ||||||
|  | 		}, | ||||||
|  | 		NotBefore: notBefore, | ||||||
|  | 		NotAfter:  notAfter, | ||||||
|  | 
 | ||||||
|  | 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||||||
|  | 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||||||
|  | 		BasicConstraintsValid: true, | ||||||
|  | 		DNSNames:              []string{"localhost"}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, h := range host { | ||||||
|  | 		if ip := net.ParseIP(h); ip != nil { | ||||||
|  | 			template.IPAddresses = append(template.IPAddresses, ip) | ||||||
|  | 		} else { | ||||||
|  | 			template.DNSNames = append(template.DNSNames, h) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template.IsCA = true | ||||||
|  | 	template.KeyUsage |= x509.KeyUsageCertSign | ||||||
|  | 
 | ||||||
|  | 	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tls.Certificate{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// create public key
 | ||||||
|  | 	certOut := bytes.NewBuffer(nil) | ||||||
|  | 	err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tls.Certificate{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// create private key
 | ||||||
|  | 	keyOut := bytes.NewBuffer(nil) | ||||||
|  | 	b, err := x509.MarshalECPrivateKey(priv) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tls.Certificate{}, err | ||||||
|  | 	} | ||||||
|  | 	err = pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tls.Certificate{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	env := os.Getenv("HPDS_ENV") | ||||||
|  | 	isDev = len(env) == 0 || env != "production" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | package network | ||||||
|  | 
 | ||||||
|  | // Workflow describes stream function workflows.
 | ||||||
|  | type Workflow struct { | ||||||
|  | 	// Seq represents the sequence id when executing workflows.
 | ||||||
|  | 	Seq int | ||||||
|  | 
 | ||||||
|  | 	// Token represents the name of workflow.
 | ||||||
|  | 	Name string | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue