This commit is contained in:
commit
a258da3678
10 changed files with 609 additions and 0 deletions
17
.drone.yml
Normal file
17
.drone.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: kubernetes
|
||||||
|
name: default
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: echo-server
|
||||||
|
image: ghcr.io/will-scargill/echo:latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: golang:1.18
|
||||||
|
commands:
|
||||||
|
- curl -L https://vh7.uk/9kiw > waitfor.sh && chmod +x waitfor.sh
|
||||||
|
- ./waitfor.sh 127.0.0.1:16000 -t 60
|
||||||
|
- go test -v ./...
|
||||||
|
|
139
.gitignore
vendored
Normal file
139
.gitignore
vendored
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
### Go ###
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
### Go Patch ###
|
||||||
|
/vendor/
|
||||||
|
/Godeps/
|
||||||
|
|
||||||
|
### Intellij+all ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Intellij+all Patch ###
|
||||||
|
# Ignore everything but code style settings and run configurations
|
||||||
|
# that are supposed to be shared within teams.
|
||||||
|
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
!.idea/codeStyles
|
||||||
|
!.idea/runConfigurations
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
|
||||||
|
# Support for Project snippet scope
|
||||||
|
.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Ignore code-workspaces
|
||||||
|
*.code-workspace
|
78
crypto/aes.go
Normal file
78
crypto/aes.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func packCipherData(cipherText, iv []byte) ([]byte, error) {
|
||||||
|
return json.Marshal([]string{
|
||||||
|
base64.StdEncoding.EncodeToString(cipherText),
|
||||||
|
base64.StdEncoding.EncodeToString(iv),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackCipherData(data []byte) ([]byte, []byte, error) {
|
||||||
|
var cipherData []string
|
||||||
|
err := json.Unmarshal(data, &cipherData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(cipherData) != 2 {
|
||||||
|
return nil, nil, fmt.Errorf("invalid cipher data")
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherText, err := base64.StdEncoding.DecodeString(cipherData[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv, err := base64.StdEncoding.DecodeString(cipherData[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cipherText, iv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncryptAesCbc(aes cipher.Block, plainTextBytes []byte) ([]byte, error) {
|
||||||
|
iv := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypter := cipher.NewCBCEncrypter(aes, iv)
|
||||||
|
|
||||||
|
plainTextBytes, err := pkcs7Pad(plainTextBytes, encrypter.BlockSize())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherText := make([]byte, len(plainTextBytes))
|
||||||
|
encrypter.CryptBlocks(cipherText, plainTextBytes)
|
||||||
|
|
||||||
|
return packCipherData(cipherText, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecryptAesCbc(aes cipher.Block, data []byte) ([]byte, error) {
|
||||||
|
encrypted, iv, err := unpackCipherData(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptor := cipher.NewCBCDecrypter(aes, iv)
|
||||||
|
|
||||||
|
decryptedBytes := make([]byte, len(encrypted))
|
||||||
|
decryptor.CryptBlocks(decryptedBytes, encrypted)
|
||||||
|
|
||||||
|
decryptedBytes, err = pkcs7Unpad(decryptedBytes, decryptor.BlockSize())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedBytes, nil
|
||||||
|
}
|
44
crypto/padding.go
Normal file
44
crypto/padding.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ref: https://golang-examples.tumblr.com/post/98350728789/pkcs7-padding
|
||||||
|
// Appends padding.
|
||||||
|
func pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
|
||||||
|
if blocklen <= 0 {
|
||||||
|
return nil, fmt.Errorf("Invalid block length %d", blocklen)
|
||||||
|
}
|
||||||
|
padlen := 1
|
||||||
|
for ((len(data) + padlen) % blocklen) != 0 {
|
||||||
|
padlen = padlen + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
|
||||||
|
return append(data, pad...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns slice of the original data without padding.
|
||||||
|
func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
|
||||||
|
if blocklen <= 0 {
|
||||||
|
return nil, fmt.Errorf("Invalid block length %d", blocklen)
|
||||||
|
}
|
||||||
|
if len(data)%blocklen != 0 || len(data) == 0 {
|
||||||
|
return nil, fmt.Errorf("Invalid data length %d", len(data))
|
||||||
|
}
|
||||||
|
padlen := int(data[len(data)-1])
|
||||||
|
if padlen > blocklen || padlen == 0 {
|
||||||
|
return nil, fmt.Errorf("Invalid padding")
|
||||||
|
}
|
||||||
|
// check padding
|
||||||
|
pad := data[len(data)-padlen:]
|
||||||
|
for i := 0; i < padlen; i++ {
|
||||||
|
if pad[i] != byte(padlen) {
|
||||||
|
return nil, fmt.Errorf("Invalid padding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data[:len(data)-padlen], nil
|
||||||
|
}
|
24
crypto/rsa.go
Normal file
24
crypto/rsa.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RsaEncrypt(pubKey []byte, text []byte) (string, error) {
|
||||||
|
block, _ := pem.Decode(pubKey)
|
||||||
|
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
k := key.(*rsa.PublicKey)
|
||||||
|
ciphertext, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, k, text, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||||
|
}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module git.vh7.uk/jakew/echo-go
|
||||||
|
|
||||||
|
go 1.18
|
0
go.sum
Normal file
0
go.sum
Normal file
232
main.go
Normal file
232
main.go
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
package echo_go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.vh7.uk/jakew/echo-go/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const EchoVersion = "3.17"
|
||||||
|
|
||||||
|
func randomHex(n int) (string, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendPlain(msgType MessageType, data *string, subType *string, metadata []string) error {
|
||||||
|
metadataBytes, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg := RawMessage{
|
||||||
|
UserId: c.UserId,
|
||||||
|
MessageType: msgType,
|
||||||
|
SubType: subType,
|
||||||
|
Data: data,
|
||||||
|
Metadata: string(metadataBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(&msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Printf("sending message %v", msg)
|
||||||
|
_, err = c.Con.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Send(msgType MessageType, data *string, subType *string, metadata []string) error {
|
||||||
|
metadataBytes, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg := RawMessage{
|
||||||
|
UserId: c.UserId,
|
||||||
|
MessageType: msgType,
|
||||||
|
SubType: subType,
|
||||||
|
Data: data,
|
||||||
|
Metadata: string(metadataBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
plainBytes, err := json.Marshal(&msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
aesCipher, err := aes.NewCipher([]byte(c.SessionKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedBytes, err := crypto.EncryptAesCbc(aesCipher, plainBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Con.Write(encryptedBytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ReceivePlain() ([]RawMessage, error) {
|
||||||
|
data := make([]byte, 20480)
|
||||||
|
_, err := c.Con.Read(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []RawMessage{}
|
||||||
|
|
||||||
|
for _, message := range strings.Split(string(bytes.Trim(data, "\x00")), "}") {
|
||||||
|
if strings.TrimSpace(message) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg RawMessage
|
||||||
|
err = json.Unmarshal([]byte(message+"}"), &msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = append(messages, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Receive() ([]RawMessage, error) {
|
||||||
|
data := make([]byte, 20480)
|
||||||
|
_, err := c.Con.Read(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []RawMessage{}
|
||||||
|
|
||||||
|
for _, message := range strings.Split(string(bytes.Trim(data, "\x00")), "]") {
|
||||||
|
if strings.TrimSpace(message) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
aesCipher, err := aes.NewCipher([]byte(c.SessionKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted, err := crypto.DecryptAesCbc(aesCipher, []byte(message+"]"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg RawMessage
|
||||||
|
err = json.Unmarshal(decrypted, &msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = append(messages, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) handshakeLoop(password string) error {
|
||||||
|
log.Println("sending server info request")
|
||||||
|
err := c.SendPlain(ReqServerInfo, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted := false
|
||||||
|
|
||||||
|
for {
|
||||||
|
var msgs []RawMessage
|
||||||
|
var err error
|
||||||
|
if encrypted {
|
||||||
|
msgs, err = c.Receive()
|
||||||
|
} else {
|
||||||
|
msgs, err = c.ReceivePlain()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
switch msg.MessageType {
|
||||||
|
case ResServerInfo:
|
||||||
|
ciphertext, err := crypto.RsaEncrypt([]byte(*msg.Data), []byte(c.SessionKey))
|
||||||
|
err = c.SendPlain(ReqClientSecret, &ciphertext, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
encrypted = true
|
||||||
|
case ResClientSecret:
|
||||||
|
data, err := json.Marshal([]string{
|
||||||
|
c.Username,
|
||||||
|
password,
|
||||||
|
EchoVersion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataStr := string(data)
|
||||||
|
err = c.Send(ReqConnection, &dataStr, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case ResConnectionAccepted:
|
||||||
|
log.Println("handshake accepted")
|
||||||
|
return nil
|
||||||
|
case ResConnectionDenied:
|
||||||
|
return fmt.Errorf("handshake failed - %v", *msg.Data)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected handshake message type %v", msg.MessageType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Disconnect() {
|
||||||
|
log.Println("gracefully disconnecting")
|
||||||
|
_ = c.Send(ReqDisconnect, nil, nil, nil)
|
||||||
|
_ = c.Con.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(addr string, username string) (*Client, error) {
|
||||||
|
userId, err := randomHex(32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKey, err := randomHex(8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("connecting to %v", addr)
|
||||||
|
|
||||||
|
con, err := net.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := Client{
|
||||||
|
Con: con,
|
||||||
|
UserId: userId,
|
||||||
|
SessionKey: sessionKey,
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client, nil
|
||||||
|
}
|
17
main_test.go
Normal file
17
main_test.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package echo_go
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCanConnect(t *testing.T) {
|
||||||
|
client, err := New("127.0.0.1:16000", "EchoGoTest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.handshakeLoop("mypassword")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to run handshake loop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Disconnect()
|
||||||
|
}
|
55
types.go
Normal file
55
types.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package echo_go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MessageType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReqServerInfo MessageType = "serverInfoRequest"
|
||||||
|
ReqClientSecret = "clientSecret"
|
||||||
|
ReqConnection = "connectionRequest"
|
||||||
|
ReqDisconnect = "disconnect"
|
||||||
|
ReqInfo = "requestInfo"
|
||||||
|
ReqUserMessage = "userMessage"
|
||||||
|
ReqChangeChannel = "changeChannel"
|
||||||
|
ReqHistory = "historyRequest"
|
||||||
|
ReqLeaveChannel = "leaveChannel"
|
||||||
|
|
||||||
|
ResServerInfo = "serverInfo"
|
||||||
|
ResClientSecret = "gotSecret"
|
||||||
|
ResConnectionAccepted = "CRAccepted"
|
||||||
|
ResConnectionDenied = "CRDenied"
|
||||||
|
ResOutboundMessage = "outboundMessage"
|
||||||
|
ResConnectionTerminated = "connectionTerminated"
|
||||||
|
ResChannelUpdate = "channelUpdate"
|
||||||
|
ResCommandData = "commandData"
|
||||||
|
ResChannelHistory = "channelHistory"
|
||||||
|
ResErrorOccurred = "errorOccured"
|
||||||
|
ResAdditionalHistory = "additionalHistory"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Con net.Conn
|
||||||
|
UserId string
|
||||||
|
SessionKey string
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RawMessage struct {
|
||||||
|
UserId string `json:"userid"`
|
||||||
|
MessageType MessageType `json:"messagetype"`
|
||||||
|
SubType *string `json:"subtype"`
|
||||||
|
Data *string `json:"data"`
|
||||||
|
Metadata string `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RawMessage) String() string {
|
||||||
|
return fmt.Sprintf("\n"+
|
||||||
|
" UserId: %v\n"+
|
||||||
|
" Type: %v - %v\n"+
|
||||||
|
" Data: %v\n"+
|
||||||
|
" Metadata: %v", m.UserId, m.MessageType, *m.SubType, *m.Data, m.Metadata)
|
||||||
|
}
|
Loading…
Reference in a new issue