Compare commits

...

10 Commits

Author SHA1 Message Date
33b412dd85 feat: file as json support 2025-05-16 15:58:17 +02:00
a1cc664f41 feat: remove executable 2025-05-16 15:57:39 +02:00
61de51c0ae feat: git ignore 2025-05-16 15:57:39 +02:00
adda9747be fix: global err 2025-05-11 14:37:41 +02:00
440d9fb579 feat: dynamic data format 2025-05-04 13:44:35 +02:00
ad2d978fb4 feat: base url + auth support 2025-05-04 11:54:42 +02:00
3551d0f4dc feat: packet version 2 2025-04-27 15:44:47 +02:00
baf4e882db feat: new headers + statuses 2025-04-27 15:41:56 +02:00
e8def487d4 feat: accept connection structure 2025-04-27 15:40:07 +02:00
f137795414 feat: exchange keys with connected client 2025-04-27 15:39:47 +02:00
8 changed files with 184 additions and 72 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
*.exe *.exe
hsp-go hsp-go

View File

@ -4,9 +4,10 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"net" "io"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"github.com/LandaMm/hsp-go/hsp" "github.com/LandaMm/hsp-go/hsp"
@ -16,7 +17,7 @@ import (
) )
type Header struct { type Header struct {
Key string Key string
Value string Value string
} }
@ -38,7 +39,7 @@ func (hl *HeaderList) Set(arg string) error {
return err return err
} }
hl.Headers = append(hl.Headers, Header{ hl.Headers = append(hl.Headers, Header{
Key: key, Key: key,
Value: value, Value: value,
}) })
return nil return nil
@ -89,7 +90,7 @@ func PrintPacket(pkt *hsp.Packet) error {
default: default:
fmt.Printf("ERR: Unsupported data format: %s\n", df.String()) fmt.Printf("ERR: Unsupported data format: %s\n", df.String())
} }
return nil return nil
} }
@ -118,7 +119,7 @@ func StartServer(addr *hsp.Adddress) {
srv := server.NewServer(*addr) srv := server.NewServer(*addr)
fmt.Printf("Server created on address: %s\n", srv.Addr.String()) fmt.Printf("Server created on address: %s\n", srv.Addr.String())
handler := make(chan net.Conn, 1) handler := make(chan *hsp.Connection, 1)
router := server.NewRouter() router := server.NewRouter()
@ -154,11 +155,8 @@ func StartServer(addr *hsp.Adddress) {
} }
} }
func StartSession(addr *hsp.Adddress, df *hsp.DataFormat, headerList *HeaderList) { func StartSession(options *client.ClientOptions) {
url := addr.String() + addr.Route c := client.NewClient(options)
fmt.Println("Starting session on", url)
c := client.NewClient(headerList.Map())
rl, err := readline.New("> ") rl, err := readline.New("> ")
if err != nil { if err != nil {
@ -172,32 +170,67 @@ func StartSession(addr *hsp.Adddress, df *hsp.DataFormat, headerList *HeaderList
}() }()
for { for {
rl.SetPrompt("Route > ")
route, err := rl.Readline()
if err != nil {
break
}
rl.SetPrompt("Data > ")
line, err := rl.Readline() line, err := rl.Readline()
if err != nil { if err != nil {
break break
} }
var rsp *hsp.Response var rsp *hsp.Response
var rerr error
switch df.Format { if strings.HasPrefix(line, "/file") {
case hsp.DF_TEXT: what := strings.TrimLeft(line, "/file")
rsp, err = c.SendText(url, line) isJson := false
case hsp.DF_JSON: var filename string
var data any if strings.HasPrefix(what, ":json ") {
err = json.Unmarshal([]byte(line), &data) isJson = true
if err != nil { filename = strings.TrimLeft(what, ":json ")
fmt.Println("ERR: Invalid JSON for request:", err) } else {
filename = strings.TrimLeft(what, " ")
} }
rsp, err = c.SendJson(url, data) file, err := os.Open(filename)
case hsp.DF_BYTES: if err != nil {
rsp, err = c.SendBytes(url, []byte(line)) fmt.Printf("ERR: Failed to open file '%s': %v\n", filename, err)
default: continue
fmt.Println("ERR: Unsupported data format:", df.Format) }
return
if !isJson {
buf, err := io.ReadAll(file)
if err != nil {
fmt.Printf("ERR: Failed to read from file '%s': %v\n", filename, err)
continue
}
rsp, err = c.SendBytes(route, buf)
} else {
var data any
decoder := json.NewDecoder(file)
err = decoder.Decode(&data)
if err == nil {
rsp, err = c.SendJson(route, data)
}
}
} else if strings.HasPrefix(line, "/json ") {
var data any
err = json.Unmarshal([]byte(strings.TrimLeft(line, "/json ")), &data)
if err != nil {
fmt.Println("ERR: Invalid JSON for request:", err)
} else {
rsp, err = c.SendJson(route, data)
}
} else {
rsp, rerr = c.SendText(route, line)
} }
if err != nil { if rerr != nil {
fmt.Println("ERR: Failed to send text to server:", err) fmt.Println("ERR: Failed to send text to server:", err)
continue continue
} }
@ -216,17 +249,16 @@ func main() {
var service string var service string
var address string var address string
var dataFormat string
var headerList HeaderList var headerList HeaderList
var auth string
flag.StringVar(&host, "host", "localhost", "specify server host") flag.StringVar(&host, "host", "localhost", "specify server host")
flag.StringVar(&service, "port", "998", "specify server port") flag.StringVar(&service, "port", "998", "specify server port")
flag.StringVar(&address, "addr", "localhost:998", "specify target address") flag.StringVar(&address, "addr", "localhost:998", "specify target address")
flag.Var(&headerList, "H", "provide additional header") flag.StringVar(&auth, "auth", "", "provide auth credentials")
flag.StringVar(&dataFormat, "format", "text", "specify request's data format") flag.Var(&headerList, "H", "provide additional header")
flag.Parse() flag.Parse()
@ -242,27 +274,11 @@ func main() {
return return
} }
addr, err := hsp.ParseAddress(address) options := &client.ClientOptions{
if err != nil { Headers: headerList.Map(),
fmt.Printf("ERR: Invalid address %s: %v\n", address, err) Auth: auth,
return BaseURL: address,
} }
var df *hsp.DataFormat StartSession(options)
switch dataFormat {
case hsp.DF_TEXT:
df = hsp.TextDataFormat()
case hsp.DF_JSON:
df = hsp.JsonDataFormat()
case hsp.DF_BYTES:
df = hsp.BytesDataFormat()
default:
fmt.Println("ERR: Invalid format selected for requests:", dataFormat)
}
StartSession(addr, df, &headerList)
} }

View File

@ -7,18 +7,18 @@ import (
type Adddress struct { type Adddress struct {
Host string Host string
Port string Port string
Route string Route string
} }
func ParseAddress(address string) (*Adddress, error) { func ParseAddress(address string) (*Adddress, error) {
parts := strings.SplitN(address, "/", 2) parts := strings.Split(address, "/")
var route string var route string
if len(parts) == 1 { if len(parts) == 1 {
route = "/" route = "/"
} else if len(parts) > 1 { } else if len(parts) > 1 {
route = "/" + strings.Join(parts[1:], "") route = "/" + strings.Join(parts[1:], "/")
} else { } else {
return nil, fmt.Errorf("Failed to parse address: %s", address) return nil, fmt.Errorf("Failed to parse address: %s", address)
} }
@ -30,18 +30,29 @@ func ParseAddress(address string) (*Adddress, error) {
if strings.Contains(addr, ":") { if strings.Contains(addr, ":") {
p := strings.Split(addr, ":") p := strings.Split(addr, ":")
if len(p) >= 2 { if len(p) >= 2 {
port = p[len(p) - 1] port = p[len(p)-1]
addr = p[0] addr = p[0]
} }
} }
return &Adddress{ return &Adddress{
Host: addr, Host: addr,
Port: port, Port: port,
Route: route, Route: route,
}, nil }, nil
} }
func (a *Adddress) Extend(extension string) (*Adddress, error) {
route := strings.TrimRight(a.Route, "/")
ext := strings.TrimLeft(extension, "/")
return &Adddress{
Host: a.Host,
Port: a.Port,
Route: route + "/" + ext,
}, nil
}
func (a *Adddress) String() string { func (a *Adddress) String() string {
return fmt.Sprintf("%s:%s", a.Host, a.Port) return fmt.Sprintf("%s:%s", a.Host, a.Port)
} }

View File

@ -4,32 +4,59 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"maps" "maps"
"net" "net"
"github.com/LandaMm/hsp-go/hsp" "github.com/LandaMm/hsp-go/hsp"
) )
type Client struct { type ClientOptions struct {
Headers map[string]string Headers map[string]string
// TODO: in future support multiple types of auth (credentials, key etc.)
Auth string
BaseURL string
} }
func NewClient(headers map[string]string) *Client { type Client struct {
Options *ClientOptions
Base *hsp.Adddress
}
func NewClient(options *ClientOptions) *Client {
if options == nil {
options = &ClientOptions{}
}
var base *hsp.Adddress
if len(options.BaseURL) > 0 {
addr, err := hsp.ParseAddress(options.BaseURL)
if err == nil {
base = addr
}
}
return &Client{ return &Client{
Headers: headers, Options: options,
Base: base,
} }
} }
func (c *Client) BuildHeaders(address *hsp.Adddress, df *hsp.DataFormat) map[string]string { func (c *Client) BuildHeaders(address *hsp.Adddress, df *hsp.DataFormat) map[string]string {
headers := make(map[string]string) headers := make(map[string]string)
if len(c.Headers) > 0 { if len(c.Options.Headers) > 0 {
maps.Copy(headers, c.Headers) maps.Copy(headers, c.Options.Headers)
} }
headers[hsp.H_ROUTE] = address.Route headers[hsp.H_ROUTE] = address.Route
headers[hsp.H_DATA_FORMAT] = df.String() headers[hsp.H_DATA_FORMAT] = df.String()
if len(c.Options.Auth) > 0 {
headers[hsp.H_AUTH] = c.Options.Auth
}
return headers return headers
} }
@ -75,7 +102,14 @@ func (c *Client) SingleHit(addr *hsp.Adddress, pkt *hsp.Packet) (*hsp.Packet, er
} }
func (c *Client) SendText(address, text string) (*hsp.Response, error) { func (c *Client) SendText(address, text string) (*hsp.Response, error) {
addr, err := hsp.ParseAddress(address) var addr *hsp.Adddress
var err error
if c.Base != nil {
addr, err = c.Base.Extend(address)
} else {
addr, err = hsp.ParseAddress(address)
}
if err != nil { if err != nil {
return nil, err return nil, err
@ -96,7 +130,14 @@ func (c *Client) SendText(address, text string) (*hsp.Response, error) {
} }
func (c *Client) SendJson(address string, data any) (*hsp.Response, error) { func (c *Client) SendJson(address string, data any) (*hsp.Response, error) {
addr, err := hsp.ParseAddress(address) var addr *hsp.Adddress
var err error
if c.Base != nil {
addr, err = c.Base.Extend(address)
} else {
addr, err = hsp.ParseAddress(address)
}
if err != nil { if err != nil {
return nil, err return nil, err
@ -120,7 +161,14 @@ func (c *Client) SendJson(address string, data any) (*hsp.Response, error) {
} }
func (c *Client) SendBytes(address string, data []byte) (*hsp.Response, error) { func (c *Client) SendBytes(address string, data []byte) (*hsp.Response, error) {
addr, err := hsp.ParseAddress(address) var addr *hsp.Adddress
var err error
if c.Base != nil {
addr, err = c.Base.Extend(address)
} else {
addr, err = hsp.ParseAddress(address)
}
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -12,6 +12,7 @@ const HSP_PORT = "998"
const ( const (
H_STATUS = "status" H_STATUS = "status"
H_DATA_FORMAT = "data-format" H_DATA_FORMAT = "data-format"
H_AUTH = "auth"
H_ROUTE = "route" H_ROUTE = "route"
) )
@ -26,10 +27,11 @@ const (
) )
const ( const (
STATUS_SUCCESS = 0 STATUS_SUCCESS = 0
STATUS_NOTFOUND = 69 STATUS_NOTFOUND = 69
STATUS_INTERNALERR = 129 STATUS_INTERNALERR = 129
STATUS_RECEIVED = 1 STATUS_UNAUTHORIZED = 49
STATUS_RECEIVED = 1
) )
var DATA_FORMATS map[string]string = map[string]string{ var DATA_FORMATS map[string]string = map[string]string{

View File

@ -11,7 +11,7 @@ const (
) )
const ( const (
PacketVersion int = 1 PacketVersion int = 2
) )
type RawPacket struct { type RawPacket struct {

View File

@ -1,6 +1,8 @@
package server package server
import ( import (
"fmt"
"io"
"net" "net"
"sync" "sync"
@ -11,7 +13,7 @@ type Server struct {
Addr hsp.Adddress Addr hsp.Adddress
routePrefix string // TODO: Support route prefix, e.g listening on localhost/api routePrefix string // TODO: Support route prefix, e.g listening on localhost/api
Running bool Running bool
ConnChan chan net.Conn ConnChan chan *hsp.Connection
listener net.Listener listener net.Listener
mu sync.Mutex mu sync.Mutex
} }
@ -24,7 +26,7 @@ func NewServer(addr hsp.Adddress) *Server {
} }
} }
func (s *Server) SetListener(ln chan net.Conn) { func (s *Server) SetListener(ln chan *hsp.Connection) {
s.ConnChan = ln s.ConnChan = ln
} }
@ -48,8 +50,42 @@ func (s *Server) Start() error {
return err return err
} }
keys, err := hsp.GenerateKeyPair()
if err != nil {
return err
}
// Receive client's public key
clientKey := make([]byte, 32)
n, err := io.ReadFull(conn, clientKey)
if err != nil {
return err
}
if n != 32 {
return fmt.Errorf("Received invalid client's key with %d bytes (expected 32 bytes)", n)
}
// Send our public key to client
n, err = conn.Write(keys.Public[:])
if err != nil {
return err
}
if n != 32 {
return fmt.Errorf("Couldn't send 32 bytes of public key (%d sent instead)", n)
}
sharedKey, err := hsp.DeriveSharedKey(keys.Private, [32]byte(clientKey))
if err != nil {
return err
}
if s.ConnChan != nil { if s.ConnChan != nil {
s.ConnChan <- conn connection := hsp.NewConnection(conn, keys, sharedKey)
s.ConnChan <- connection
} else {
conn.Close()
} }
} }

BIN
hspnet

Binary file not shown.