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