github.com/Laplace-Game-Development/Laplace-Entangled-Environment@v0.0.3/internal/route/listener.go (about)

     1  // Route Represents the routing and listening to connections.
     2  // This module takes care of the communication links to users
     3  // and clients. They will forward commands to data driven modules
     4  // in `/data`. The Listener module makes sure to listen to
     5  // connections over TCP and HTTP (maybe Websocket too later.)
     6  // We also have the parser which uses the policy directives to
     7  // break apart the user payloads into understandable commands.
     8  // Secure takes care of any encryption necessary over the wire.
     9  package route
    10  
    11  import (
    12  	"context"
    13  	"crypto/tls"
    14  	"encoding/json"
    15  	"errors"
    16  	"io/ioutil"
    17  	"log"
    18  	"net"
    19  	"net/http"
    20  	"time"
    21  
    22  	"github.com/Laplace-Game-Development/Laplace-Entangled-Environment/internal/policy"
    23  	"github.com/Laplace-Game-Development/Laplace-Entangled-Environment/internal/util"
    24  )
    25  
    26  //// Configurables
    27  
    28  // Time for shutdown. Quitting Mid Handle is really bad. This should be longer than any duration
    29  const ShutdownDuration time.Duration = 10 * time.Second
    30  
    31  /* Client TCP Settings */
    32  
    33  // Time spent waiting for incomming connections before checking for control signals/shutoff/etc
    34  const IoDeadline time.Duration = 5 * time.Second
    35  
    36  // TCP IP Mask to listen for connections on
    37  const ListeningTCPIpAddress string = "127.0.0.1"
    38  
    39  // TCP Port number to listen for connections on
    40  const ListeningTCPPortNumber string = ":26005"
    41  
    42  // SSL Port Number to Listen for connections on
    43  const ListeningSSLPortNumber string = ":26006"
    44  
    45  // Size of Packet Header for TCP commands
    46  // Byte 1 :: Metadata/Parsing info
    47  // Byte 2 :: More Significant byte for Command
    48  // Byte 3 :: Lesser Significant byte for Command
    49  const CommandBytes = 3
    50  
    51  // Limit of Goroutines used for listening on TCP.
    52  // 5 is a good number for testing, but a better number would be much higher.
    53  const NumberOfTCPThreads = 10
    54  
    55  // Limit of Goroutines used for listening on SSL.
    56  // 5 is a good number for testing, but a better number would be much higher.
    57  const NumberOfSSLThreads = 5
    58  
    59  // Constant byte string of JSON representing a data malformed error
    60  // May be moved to Policy
    61  var MalformedDataMsg []byte = []byte("{\"success\": false, \"error\": \"Data Was Malformed!\"}")
    62  
    63  // Constant integer length of a JSON byte string representing a data malformed error
    64  // May be moved to Policy
    65  var MalformedDataMsgLen int = len([]byte("{\"success\": false, \"error\": \"Data Was Malformed!\"}"))
    66  
    67  // Constant byte string of JSON representing a Secured Connection
    68  // May be moved to Policy
    69  var SecureConnectionMsg []byte = []byte("{\"success\": true, \"message\": \"SECURED!\"}")
    70  
    71  // Constant integer length of a JSON byte string representing a Secured Connection
    72  // May be moved to Policy
    73  var SecureConnectionMsgLen int = len([]byte("{\"success\": true, \"message\": \"SECURED!\"}"))
    74  
    75  // HTTP Listening Host IP
    76  const HttpHost string = "127.0.0.1"
    77  
    78  // HTTP Host Listening Port Number
    79  const HttpPort string = ":80"
    80  
    81  //// Global Variables | Singletons
    82  
    83  // Thread Pool for Connection Listening. This stores the threads and context
    84  // for all listening for connections.
    85  // The goroutines themselves will spawn other goroutines so we use more than
    86  // 4 "threads" for the full application.
    87  //
    88  // 1 for TCP
    89  // 1 for SSL
    90  // 1 For HTTP
    91  // 1 For WebSocket?
    92  var listenerThreadPool util.ThreadPool = util.NewThreadPool(4)
    93  
    94  // ServerTask Startup Function for Conneciton Listening. Takes care of initialization.
    95  func StartListener() (func(), error) {
    96  	err := listenerThreadPool.SubmitFuncUnsafe(startTCPListening)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	err = listenerThreadPool.SubmitFuncUnsafe(startSSLListening)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	err = listenerThreadPool.SubmitFuncUnsafe(startHTTPListening)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return cleanUpListener, nil
   112  }
   113  
   114  // CleanUp Function returned by Startup function. Stops all listeners by "Finish"ing the
   115  // threadpool. This means that within the given "ShutdownDuration" the listeners should
   116  // be closed.
   117  func cleanUpListener() {
   118  	log.Println("Cleaning Up Listener Logic")
   119  	listenerThreadPool.Finish(time.Now().Add(ShutdownDuration))
   120  }
   121  
   122  ///////////////////////////////////////////////////////////////////////////////////////////////////
   123  ////
   124  //// General Listening Functions
   125  ////
   126  ///////////////////////////////////////////////////////////////////////////////////////////////////
   127  
   128  // takes the required parameters and ensures any listener that is ready to send their command
   129  // to data has the required components. This will parse and perform the commmand requested. It
   130  // will then return the calculated byte slice response.
   131  //
   132  // requestHeader :: Common Fields for all requests including authentication and endpoint selection
   133  // bodyFactories :: Arguments for the commands in the form of first order functions
   134  // isSecured     :: Whether the request came over an encrypted connection (i.e. SSL/SSH/HTTPS)
   135  //
   136  // returns []byte :: byte slice response for user
   137  //          error :: non-nil when an invalid command is sent or an error occurred when processing
   138  //             typically means request was rejected.
   139  func calculateResponse(requestHeader policy.RequestHeader, bodyFactories policy.RequestBodyFactories, isSecured bool) ([]byte, error) {
   140  	// parse.go
   141  	return switchOnCommand(requestHeader, bodyFactories, isSecured)
   142  }
   143  
   144  ///////////////////////////////////////////////////////////////////////////////////////////////////
   145  ////
   146  //// HTTP Listening Functions
   147  ////
   148  ///////////////////////////////////////////////////////////////////////////////////////////////////
   149  
   150  // A Constant map of what commands should be made with a POST HTTP Request
   151  // rather than a GET Request. This is for semantic and API design reasons.
   152  //
   153  // This should never change during runtime!
   154  var postOnlyCmdMap map[policy.ClientCmd]bool = map[policy.ClientCmd]bool{
   155  	policy.CmdError:      false,
   156  	policy.CmdEmpty:      false,
   157  	policy.CmdRegister:   true,
   158  	policy.CmdLogin:      true,
   159  	policy.CmdAction:     true,
   160  	policy.CmdObserve:    true,
   161  	policy.CmdGetUser:    true,
   162  	policy.CmdGameCreate: true,
   163  	policy.CmdGameJoin:   true,
   164  	policy.CmdGameLeave:  true,
   165  	policy.CmdGameDelete: true,
   166  }
   167  
   168  // Attaches Path Handlers for HTTP Web Server. Uses Paths to
   169  // communicate command used. i.e. /user/ -> CmdGetUser
   170  //
   171  // ctx :: Owning Context for HTTP Listener
   172  func startHTTPListening(ctx context.Context) {
   173  	http.HandleFunc("/empty/", getHttpHandler(policy.CmdEmpty))
   174  	http.HandleFunc("/error/", getHttpHandler(policy.CmdError))
   175  	http.HandleFunc("/register/", getHttpHandler(policy.CmdRegister))
   176  	http.HandleFunc("/login/", getHttpHandler(policy.CmdLogin))
   177  	http.HandleFunc("/action/", getHttpHandler(policy.CmdAction))
   178  	http.HandleFunc("/observe/", getHttpHandler(policy.CmdObserve))
   179  	http.HandleFunc("/user/", getHttpHandler(policy.CmdGetUser))
   180  	http.HandleFunc("/game/create/", getHttpHandler(policy.CmdGameCreate))
   181  	http.HandleFunc("/game/join/", getHttpHandler(policy.CmdGameJoin))
   182  	http.HandleFunc("/game/leave/", getHttpHandler(policy.CmdGameLeave))
   183  	http.HandleFunc("/game/delete/", getHttpHandler(policy.CmdGameDelete))
   184  
   185  	http.HandleFunc("*", http.NotFound)
   186  
   187  	serverConfig := http.Server{
   188  		Addr:        HttpHost + HttpPort,
   189  		TLSConfig:   &tlsConfig, // Found in secure.go
   190  		BaseContext: func(l net.Listener) context.Context { return ctx },
   191  	}
   192  
   193  	// Error will always be Non-Nil Here!
   194  	err := serverConfig.ListenAndServe()
   195  	if err != nil {
   196  		log.Printf("HTTP Error: %v\n", err)
   197  	}
   198  }
   199  
   200  // Creates a First Order Function for the given command.
   201  // Useful for when adding handlers in the initialization function
   202  // for HTTP.
   203  func getHttpHandler(command policy.ClientCmd) func(writer http.ResponseWriter, req *http.Request) {
   204  	return func(writer http.ResponseWriter, req *http.Request) {
   205  		handleHttp(command, writer, req)
   206  	}
   207  }
   208  
   209  // Handles a given HTTP request with the given Client Command (endpoint), and data
   210  //
   211  // clientCmd :: Selected Endpoint/Command
   212  // writer    :: writer to be written to with response data for user
   213  // req       :: Given HTTP Request with associated non-command data (args and authentication)
   214  func handleHttp(clientCmd policy.ClientCmd, writer http.ResponseWriter, req *http.Request) {
   215  	if checkPost(clientCmd, writer, req) {
   216  		return
   217  	}
   218  
   219  	body, err := ioutil.ReadAll(req.Body)
   220  	if err != nil {
   221  		log.Printf("Error Reading Body: %v\n", err)
   222  	}
   223  
   224  	requestAttachment := parseHeaderInfo(req, &body)
   225  
   226  	requestHeader := policy.RequestHeader{
   227  		Command: clientCmd,
   228  		UserID:  requestAttachment.UserID,
   229  		Sig:     requestAttachment.Sig,
   230  	}
   231  
   232  	bodyFactories := policy.RequestBodyFactories{
   233  		ParseFactory: func(ptr interface{}) error {
   234  			return json.Unmarshal(body, ptr)
   235  		},
   236  		SigVerify: func(userID string, userSig string) error {
   237  			return SigVerification(userID, userSig, &body)
   238  		},
   239  	}
   240  
   241  	calculateResponse(requestHeader, bodyFactories, req.TLS != nil)
   242  }
   243  
   244  // Returns whether the given HTTP request is a POST request if needed.
   245  // see "postOnlyCmdMap"
   246  //
   247  // clientCmd :: Selected Endpoint/Command
   248  // writer    :: writer to be written to with response data for user
   249  // req       :: Given HTTP Request with associated non-command data (args and authentication)
   250  // returns   -> bool
   251  //              true  | continue processing the request
   252  //              false | ignore the request. An error was already given to the user
   253  func checkPost(clientCmd policy.ClientCmd, writer http.ResponseWriter, req *http.Request) bool {
   254  	needsPost, exists := postOnlyCmdMap[clientCmd]
   255  	if exists && needsPost && req.Method != http.MethodPost {
   256  		output := policy.UnSuccessfulResponse("Post Required!")
   257  		writeable, err := output.Digest(output.Data)
   258  		if err != nil {
   259  			log.Fatal("handleHttp: Could Not Write Utility Response to User!")
   260  		}
   261  
   262  		writer.Write(writeable)
   263  		return true
   264  	}
   265  
   266  	return false
   267  }
   268  
   269  // Creates the Request Attachment (Authentication Portion) of the request
   270  //
   271  // req :: HTTP Request with associated data
   272  // body :: slice of data representing the request body. We use a parameter
   273  //    rather than grabbing it all to optimize the process. It needs to be
   274  //    used by other functions, so passing it in is better than creating
   275  //    a variable just to be garbage collected
   276  //
   277  // returns -> policy.RequestAttachment :: the authentication components found
   278  //        or empty components if none are found in header/cookies/or body
   279  func parseHeaderInfo(req *http.Request, body *[]byte) policy.RequestAttachment {
   280  	requestAttachment := policy.RequestAttachment{}
   281  	userIDFound := false
   282  	sigFound := false
   283  
   284  	possibleUserIDs := make([]string, 3)
   285  	possibleSigs := make([]string, 3)
   286  
   287  	// Check Header
   288  	possibleUserIDs[0] = req.Header.Get("laplace-user-id")
   289  	possibleSigs[0] = req.Header.Get("laplace-signature")
   290  
   291  	// Check Cookies
   292  	userIDCookie, cookieErr := req.Cookie("laplaceUserId")
   293  	if cookieErr != nil {
   294  		log.Println("UserID Cookie Could Not Be Parsed")
   295  	} else {
   296  		possibleUserIDs[1] = userIDCookie.Value
   297  	}
   298  
   299  	sigCookie, cookieErr := req.Cookie("laplaceSig")
   300  	if cookieErr == nil {
   301  		log.Println("Signature Cookie Could Not Be Parsed")
   302  	} else {
   303  		possibleSigs[1] = sigCookie.Value
   304  	}
   305  
   306  	// Check Body
   307  	if req.Body != nil {
   308  		userSigObj := policy.RequestAttachment{}
   309  
   310  		err := json.Unmarshal(*body, &userSigObj)
   311  		if err == nil {
   312  			possibleUserIDs[2] = userSigObj.UserID
   313  			possibleSigs[2] = userSigObj.Sig
   314  		} else {
   315  			log.Println("Illformatted JSON sent to HTTP Header")
   316  		}
   317  	}
   318  
   319  	for i := 0; !userIDFound && !sigFound && i < 3; i++ {
   320  		if !userIDFound && len(possibleUserIDs[i]) > 0 {
   321  			requestAttachment.UserID = possibleUserIDs[i]
   322  			userIDFound = true
   323  		}
   324  
   325  		if !sigFound && len(possibleSigs[i]) > 0 {
   326  			requestAttachment.Sig = possibleSigs[i]
   327  			sigFound = true
   328  		}
   329  	}
   330  
   331  	return requestAttachment
   332  }
   333  
   334  ///////////////////////////////////////////////////////////////////////////////////////////////////
   335  ////
   336  //// TCP Listening Functions
   337  ////
   338  ///////////////////////////////////////////////////////////////////////////////////////////////////
   339  
   340  // Wrapper Structure with boolean fields for a TCP Connection.
   341  // used to easily differentiate between secure and insecure
   342  // connections. It also helps in deciding if the TCP connection
   343  // needs to parse more requests (HTTP requests close connections
   344  // after one requests, but TCP connections do not.)
   345  type TCPClientConn struct {
   346  	conn         net.Conn
   347  	isSecured    bool
   348  	isReadNeeded bool
   349  }
   350  
   351  // First byte of a TCP request. This is a struct of booleans
   352  // about how the request is structured over TCP.
   353  type TCPRequestPrefix struct {
   354  	IsBase64Enc bool // First Most Sig Bit
   355  	IsJSON      bool // Second Most Sig Bit
   356  }
   357  
   358  // Creates TCP Connection Listener(s) with a designated threadpool and addressing.
   359  // It submits goroutine each time it finds a connection. If no goroutine is available
   360  // in the threadpool the listener blocks until one is found.
   361  //
   362  // ctx :: Owning Context
   363  func startTCPListening(ctx context.Context) {
   364  	log.Println("TCP Listening on " + ListeningTCPIpAddress + ListeningTCPPortNumber + "!")
   365  	ln, err := net.Listen("tcp", ListeningTCPIpAddress+ListeningTCPPortNumber)
   366  	if err != nil {
   367  		log.Fatal(err)
   368  	}
   369  
   370  	pool := util.NewThreadPoolWithContext(NumberOfTCPThreads, ctx)
   371  
   372  	for {
   373  		select {
   374  		case <-ctx.Done():
   375  			return
   376  		default:
   377  		}
   378  
   379  		// SYN + ACK
   380  		conn, err := ln.Accept()
   381  		if err != nil {
   382  			log.Println("Error Occurred In TCP Handshake!")
   383  			log.Println(err)
   384  			continue
   385  		}
   386  		pool.SubmitFuncBlock(func(ctx context.Context) {
   387  			handleTCPConnection(ctx, TCPClientConn{conn: conn, isSecured: false, isReadNeeded: false})
   388  		})
   389  	}
   390  }
   391  
   392  // Creates SSL Connection Listener(s) with a designated threadpool and addressing.
   393  // See "startTCPListening"
   394  //
   395  // ctx :: Owning Context
   396  func startSSLListening(ctx context.Context) {
   397  	log.Println("SSL Listening on " + ListeningTCPIpAddress + ListeningSSLPortNumber + "!")
   398  	ln, err := tls.Listen("tcp", ListeningTCPIpAddress+ListeningSSLPortNumber, &tlsConfig)
   399  	if err != nil {
   400  		log.Fatal(err)
   401  	}
   402  
   403  	pool := util.NewThreadPoolWithContext(NumberOfSSLThreads, ctx)
   404  
   405  	for {
   406  		select {
   407  		case <-ctx.Done():
   408  			return
   409  		default:
   410  		}
   411  
   412  		// SYN + ACK
   413  		conn, err := ln.Accept()
   414  		if err != nil {
   415  			log.Println("Error Occurred In SSL Handshake!")
   416  			log.Println(err)
   417  			continue
   418  		}
   419  		pool.SubmitFuncBlock(func(ctx context.Context) {
   420  			handleTCPConnection(ctx, TCPClientConn{conn: conn, isSecured: true, isReadNeeded: false})
   421  		})
   422  	}
   423  }
   424  
   425  // TCP goroutine function, using the prepackaged information to serve the
   426  // connected client. It parses the data it receives to compile a response.
   427  // Function will loop until the connection is closed.
   428  //
   429  // ctx :: Owning Context
   430  // clientConn :: Metadata and reference to TCP Connection
   431  func handleTCPConnection(ctx context.Context, clientConn TCPClientConn) {
   432  	log.Println("New Connection!")
   433  	defer clientConn.conn.Close()
   434  	defer log.Println("Connection Closed!")
   435  
   436  	// Read Bytes
   437  	// Bytes need to be instantiated otherwise golang will not read to them
   438  	dataIn := make([]byte, 2048)
   439  
   440  	// In the off chance we start and shutdown straight after without handling
   441  	select {
   442  	case <-ctx.Done():
   443  		return
   444  	default:
   445  	}
   446  
   447  	keepAlive := true
   448  
   449  	for keepAlive {
   450  		keepAlive = false
   451  
   452  		// If We need to shutdown
   453  		select {
   454  		case <-ctx.Done():
   455  			return
   456  		default:
   457  		}
   458  
   459  		keepAlive = readAndRespondTCP(clientConn, &dataIn) && computeTCPKeepAlive(clientConn)
   460  
   461  		if keepAlive {
   462  			util.Clear(&dataIn)
   463  		}
   464  	}
   465  }
   466  
   467  // Read and Gather Byte Response for a TCP Client Connection
   468  //
   469  // clientConn :: Metadata and reference to TCP Connection
   470  // dataIn     :: byte slice data read in for
   471  //      Command, Args, Authentication, etc.
   472  //
   473  // returns -> bool
   474  //            true | command was successful
   475  //           false | command was unsuccessful
   476  func readAndRespondTCP(clientConn TCPClientConn, dataIn *[]byte) bool {
   477  	// Set Timeout
   478  	clientConn.conn.SetReadDeadline(time.Now().Add(IoDeadline))
   479  
   480  	n, err := clientConn.conn.Read(*dataIn)
   481  	if err != nil {
   482  		log.Printf("Error Reading TCP Data! Bytes Received: %d! Bytes: %s | Err: %s", n, dataIn, err)
   483  		return false
   484  	}
   485  
   486  	prefix, err := parseTCPPrefix(n, dataIn)
   487  	if err != nil {
   488  		log.Printf("Error Parsing TCP Request! Err: %s\n", err)
   489  		return false
   490  	}
   491  
   492  	header, bodyFactory, err := generateRequestFromSocket(n, dataIn, prefix)
   493  	if err != nil {
   494  		log.Printf("Error Generating Request Command Payloads! Err: %s\n", err)
   495  		clientConn.conn.SetWriteDeadline(time.Now().Add(IoDeadline))
   496  		err = writeTCPResponse(clientConn, &MalformedDataMsg, MalformedDataMsgLen)
   497  		if err != nil {
   498  			log.Printf("Error Writing TCP Response For Malformed Data! Err: %s\n", err)
   499  		}
   500  
   501  		return false
   502  	}
   503  
   504  	response, err := calculateResponse(header, bodyFactory, clientConn.isSecured)
   505  
   506  	// Tokenize and Encrypt Response Here
   507  	clientConn.conn.SetWriteDeadline(time.Now().Add(IoDeadline))
   508  	err = writeTCPResponse(clientConn, &response, len(response))
   509  	if err != nil {
   510  		log.Printf("Error Writing TCP Response! Err: %s\n", err)
   511  	}
   512  
   513  	return true
   514  }
   515  
   516  // Gather TCP Prefix.
   517  // Prefix is the first Byte of a TCP Request.
   518  // It instructs us how the data is structured.
   519  //
   520  // length :: number of bytes in data
   521  // data   :: payload/data for request i.e. Command, Auth, and Args
   522  //
   523  // returns -> TCPRequestPrefix :: Boolean struct for Structuring metadata
   524  func parseTCPPrefix(length int, data *[]byte) (TCPRequestPrefix, error) {
   525  	if length < 1 {
   526  		return TCPRequestPrefix{}, errors.New("Packet Has No Prefix")
   527  	}
   528  
   529  	firstByte := (*data)[0]
   530  
   531  	prefix := TCPRequestPrefix{
   532  		IsBase64Enc: (firstByte & 0b1000_0000) != 0,
   533  		IsJSON:      (firstByte & 0b0100_0000) != 0,
   534  	}
   535  
   536  	return prefix, nil
   537  }
   538  
   539  // Using the structuring metadata
   540  // and the rest of the payload data, the function generates
   541  // a request to the server and returns the information
   542  // needed to "calculateResponse"
   543  //
   544  // length :: number of bytes in data
   545  // data   :: payload/data for request i.e. Command, Auth, and Args
   546  // prefix :: Structuring Metadata
   547  //
   548  // returns {
   549  //	    RequestHeader :: header data used for all request (Command and Authentication)
   550  //      RequestBodyFactories ::	Transform functions for getting request arguments
   551  //      error :: If parsing goes wrong and the request is illformed an error is returned
   552  // }
   553  func generateRequestFromSocket(length int, data *[]byte, prefix TCPRequestPrefix) (policy.RequestHeader, policy.RequestBodyFactories, error) {
   554  	header := policy.RequestHeader{}
   555  	factories := policy.RequestBodyFactories{}
   556  
   557  	if length < 3 {
   558  		return header, factories, errors.New("No Command In Request")
   559  	}
   560  
   561  	// Get Command
   562  	cmd, err := ParseCommand((*data)[1], (*data)[2])
   563  	if err != nil {
   564  		return header, factories, err
   565  	}
   566  
   567  	header.Command = cmd
   568  
   569  	// Add Attachment to Header
   570  	// Also Snip Off Trailing Characters
   571  	bodyAttachmentAndPayload := (*data)[3:length]
   572  	attachment, bodyStart, err := parseRequestAttachment(prefix.IsJSON, &bodyAttachmentAndPayload)
   573  	if err != nil {
   574  		return header, factories, err
   575  	}
   576  	header.Sig = attachment.Sig
   577  	header.UserID = attachment.UserID
   578  
   579  	bodyPayload := bodyAttachmentAndPayload[bodyStart:]
   580  	factories.ParseFactory = func(ptr interface{}) error {
   581  		return parseBody(ptr, prefix, &bodyPayload)
   582  	}
   583  
   584  	factories.SigVerify = func(userID string, userSig string) error {
   585  		return SigVerification(userID, userSig, &bodyPayload)
   586  	}
   587  
   588  	return header, factories, nil
   589  }
   590  
   591  // After successful Read->Response should we continue communications?
   592  //
   593  // clientConn :: Metadata and reference to TCP Connection
   594  //
   595  // returns -> true | keep the connection alive OR
   596  // false | close the connection
   597  func computeTCPKeepAlive(clientConn TCPClientConn) bool {
   598  	// Add Logic for Connection Overhead
   599  	return clientConn.isReadNeeded
   600  }
   601  
   602  // Write byte slice to client
   603  //
   604  // clientConn :: Metadata and reference to TCP Connection
   605  // response   :: byte slice of what needs to be sent to client
   606  // length     :: number of bytes in byte slice
   607  //
   608  // returns -> error if an error occurs and nil otherwise.
   609  func writeTCPResponse(clientConn TCPClientConn, response *[]byte, length int) error {
   610  	numSent := 0
   611  
   612  	for numSent < length {
   613  		n, err := clientConn.conn.Write((*response)[numSent:])
   614  		if err != nil {
   615  			return err
   616  		}
   617  
   618  		numSent += n
   619  	}
   620  
   621  	clientConn.conn.Write([]byte{4})
   622  
   623  	return nil
   624  }