hermes/backend/sshappbackend/datacommands/marshal.go

323 lines
7.8 KiB
Go

package datacommands
import (
"encoding/binary"
"fmt"
"net"
)
// Example size and protocol constants — adjust as needed.
const (
IPv4Size = 4
IPv6Size = 16
TCP = 1
UDP = 2
)
// Marshal takes a command (pointer to one of our structs) and converts it to a byte slice.
func Marshal(command interface{}) ([]byte, error) {
switch cmd := command.(type) {
// ProxyStatusRequest: 1 byte for the command ID + 2 bytes for the ProxyID.
case *ProxyStatusRequest:
buf := make([]byte, 1+2)
buf[0] = ProxyStatusRequestID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
return buf, nil
// ProxyStatusResponse: 1 byte for the command ID, 2 bytes for ProxyID, and 1 byte for IsActive.
case *ProxyStatusResponse:
buf := make([]byte, 1+2+1)
buf[0] = ProxyStatusResponseID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
if cmd.IsActive {
buf[3] = 1
} else {
buf[3] = 0
}
return buf, nil
// RemoveProxy: 1 byte for the command ID + 2 bytes for the ProxyID.
case *RemoveProxy:
buf := make([]byte, 1+2)
buf[0] = RemoveProxyID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
return buf, nil
// ProxyConnectionsRequest: 1 byte for the command ID + 2 bytes for the ProxyID.
case *ProxyConnectionsRequest:
buf := make([]byte, 1+2)
buf[0] = ProxyConnectionsRequestID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
return buf, nil
// ProxyConnectionsResponse: 1 byte for the command ID + 2 bytes length of the Connections + 2 bytes for each
// number in the Connection array.
case *ProxyConnectionsResponse:
buf := make([]byte, 1+((len(cmd.Connections)+1)*2))
buf[0] = ProxyConnectionsResponseID
binary.BigEndian.PutUint16(buf[1:], uint16(len(cmd.Connections)))
for connectionIndex, connection := range cmd.Connections {
binary.BigEndian.PutUint16(buf[3+(connectionIndex*2):], connection)
}
return buf, nil
// ProxyConnectionsResponse: 1 byte for the command ID + 2 bytes length of the Proxies + 2 bytes for each
// number in the Proxies array.
case *ProxyInstanceResponse:
buf := make([]byte, 1+((len(cmd.Proxies)+1)*2))
buf[0] = ProxyInstanceResponseID
binary.BigEndian.PutUint16(buf[1:], uint16(len(cmd.Proxies)))
for connectionIndex, connection := range cmd.Proxies {
binary.BigEndian.PutUint16(buf[3+(connectionIndex*2):], connection)
}
return buf, nil
// TCPConnectionOpened: 1 byte for the command ID + 2 bytes ProxyID + 2 bytes ConnectionID.
case *TCPConnectionOpened:
buf := make([]byte, 1+2+2)
buf[0] = TCPConnectionOpenedID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
return buf, nil
// TCPConnectionClosed: 1 byte for the command ID + 2 bytes ProxyID + 2 bytes ConnectionID.
case *TCPConnectionClosed:
buf := make([]byte, 1+2+2)
buf[0] = TCPConnectionClosedID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
return buf, nil
// TCPProxyData: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID + 2 bytes DataLength.
case *TCPProxyData:
buf := make([]byte, 1+2+2+2)
buf[0] = TCPProxyDataID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
binary.BigEndian.PutUint16(buf[5:], cmd.DataLength)
return buf, nil
// UDPProxyData:
// Format: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID +
// 1 byte IP version + IP bytes + 2 bytes ClientPort + 2 bytes DataLength.
case *UDPProxyData:
ip := net.ParseIP(cmd.ClientIP)
if ip == nil {
return nil, fmt.Errorf("invalid client IP: %v", cmd.ClientIP)
}
var ipVer uint8
var ipBytes []byte
if ip4 := ip.To4(); ip4 != nil {
ipBytes = ip4
ipVer = 4
} else if ip16 := ip.To16(); ip16 != nil {
ipBytes = ip16
ipVer = 6
} else {
return nil, fmt.Errorf("unable to detect IP version for: %v", cmd.ClientIP)
}
totalSize := 1 + // id
2 + // ProxyID
1 + // IP version
len(ipBytes) + // client IP bytes
2 + // ClientPort
2 // DataLength
buf := make([]byte, totalSize)
offset := 0
buf[offset] = UDPProxyDataID
offset++
binary.BigEndian.PutUint16(buf[offset:], cmd.ProxyID)
offset += 2
buf[offset] = ipVer
offset++
copy(buf[offset:], ipBytes)
offset += len(ipBytes)
binary.BigEndian.PutUint16(buf[offset:], cmd.ClientPort)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], cmd.DataLength)
return buf, nil
// ProxyInformationRequest: 1 byte ID + 2 bytes ProxyID.
case *ProxyInformationRequest:
buf := make([]byte, 1+2)
buf[0] = ProxyInformationRequestID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
return buf, nil
// ProxyInformationResponse:
// Format: 1 byte ID + 1 byte Exists + (if exists:)
// 1 byte IP version + IP bytes + 2 bytes SourcePort + 2 bytes DestPort + 1 byte Protocol.
// (For simplicity, this marshaller always writes the IP and port info even if !Exists.)
case *ProxyInformationResponse:
if !cmd.Exists {
buf := make([]byte, 1+1)
buf[0] = ProxyInformationResponseID
buf[1] = 0 /* false */
return buf, nil
}
ip := net.ParseIP(cmd.SourceIP)
if ip == nil {
return nil, fmt.Errorf("invalid source IP: %v", cmd.SourceIP)
}
var ipVer uint8
var ipBytes []byte
if ip4 := ip.To4(); ip4 != nil {
ipBytes = ip4
ipVer = 4
} else if ip16 := ip.To16(); ip16 != nil {
ipBytes = ip16
ipVer = 6
} else {
return nil, fmt.Errorf("unable to detect IP version for: %v", cmd.SourceIP)
}
totalSize := 1 + // id
1 + // Exists flag
1 + // IP version
len(ipBytes) +
2 + // SourcePort
2 + // DestPort
1 // Protocol
buf := make([]byte, totalSize)
offset := 0
buf[offset] = ProxyInformationResponseID
offset++
// We already handle this above
buf[offset] = 1 /* true */
offset++
buf[offset] = ipVer
offset++
copy(buf[offset:], ipBytes)
offset += len(ipBytes)
binary.BigEndian.PutUint16(buf[offset:], cmd.SourcePort)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], cmd.DestPort)
offset += 2
// Encode protocol as 1 byte.
switch cmd.Protocol {
case "tcp":
buf[offset] = TCP
case "udp":
buf[offset] = UDP
default:
return nil, fmt.Errorf("invalid protocol: %v", cmd.Protocol)
}
// offset++ (not needed since we are at the end)
return buf, nil
// ProxyConnectionInformationRequest: 1 byte ID + 2 bytes ProxyID + 2 bytes ConnectionID.
case *ProxyConnectionInformationRequest:
buf := make([]byte, 1+2+2)
buf[0] = ProxyConnectionInformationRequestID
binary.BigEndian.PutUint16(buf[1:], cmd.ProxyID)
binary.BigEndian.PutUint16(buf[3:], cmd.ConnectionID)
return buf, nil
// ProxyConnectionInformationResponse:
// Format: 1 byte ID + 1 byte Exists + (if exists:)
// 1 byte IP version + IP bytes + 2 bytes ClientPort.
// This marshaller only writes the rest of the data if Exists.
case *ProxyConnectionInformationResponse:
if !cmd.Exists {
buf := make([]byte, 1+1)
buf[0] = ProxyConnectionInformationResponseID
buf[1] = 0 /* false */
return buf, nil
}
ip := net.ParseIP(cmd.ClientIP)
if ip == nil {
return nil, fmt.Errorf("invalid client IP: %v", cmd.ClientIP)
}
var ipVer uint8
var ipBytes []byte
if ip4 := ip.To4(); ip4 != nil {
ipBytes = ip4
ipVer = 4
} else if ip16 := ip.To16(); ip16 != nil {
ipBytes = ip16
ipVer = 6
} else {
return nil, fmt.Errorf("unable to detect IP version for: %v", cmd.ClientIP)
}
totalSize := 1 + // id
1 + // Exists flag
1 + // IP version
len(ipBytes) +
2 // ClientPort
buf := make([]byte, totalSize)
offset := 0
buf[offset] = ProxyConnectionInformationResponseID
offset++
// We already handle this above
buf[offset] = 1 /* true */
offset++
buf[offset] = ipVer
offset++
copy(buf[offset:], ipBytes)
offset += len(ipBytes)
binary.BigEndian.PutUint16(buf[offset:], cmd.ClientPort)
return buf, nil
default:
return nil, fmt.Errorf("unsupported command type")
}
}