Compare commits

...

6 commits

Author SHA1 Message Date
Augusto Dwenger J. 7b8e1bbe41
Add a minecraft client
Minecraft (spigot) behaves differently. It does not support empty
requests. This is a deal breaker for the SimpleClient implementation.
The implementation is mutch simpler but in limits the exec support to
not support multi packet responses.
2022-03-04 21:52:24 +01:00
Augusto Dwenger J. d0ed6d814c
Add general client interface 2022-03-04 21:51:29 +01:00
Augusto Dwenger J. 24ed78a39e
Rename GrconUtilError to GrconClientError
The clients where originaly created in the util package and after
the migrating to its own package it was forgotten to update the error.
2022-03-04 14:01:54 +01:00
Augusto Dwenger J. aaf3f727df
Fix error message of the ResponseTooLongError
It is probably the body beeing to long but when this error is thrown we
don't know the real cause why it is to big.
2022-03-04 13:51:50 +01:00
Augusto Dwenger J. ece0a5f4af
Fix documentation of the RequestTooLongError 2022-03-04 13:51:21 +01:00
Augusto Dwenger J. a05d58813c
Add documentation to the UnexpectedFormatError 2022-03-04 13:50:20 +01:00
4 changed files with 114 additions and 21 deletions

View file

@ -1,2 +1,10 @@
// A package with client implementations to interact over RCON with different servers.
package client
// Client is the interface that should be implemented by ever client type.
type Client interface {
// Auth authenticates the client against the server.
Auth(password string) error
// Exec executes a command on the remote console.
Exec(cmd string) ([]byte, error)
}

View file

@ -7,68 +7,68 @@ import (
"github.com/hamburghammer/grcon"
)
func newGrconUtilError(act grcon.Action, err error) GrconUtilError {
return GrconUtilError{
func newGrconClientError(act grcon.Action, err error) GrconClientError {
return GrconClientError{
Act: act,
Err: err,
}
}
// GrconUtilError is a generic error that provides default implementations for the GrconError interface in the util module.
type GrconUtilError struct {
// GrconClientError is a generic error that provides default implementations for the GrconError interface in the util module.
type GrconClientError struct {
Err error
Act grcon.Action
}
func (grue GrconUtilError) Error() string {
func (grue GrconClientError) Error() string {
return fmt.Sprintf("grcon-client: on %s: %s", grue.Action(), grue.Err.Error())
}
func (grue GrconUtilError) Action() grcon.Action {
func (grue GrconClientError) Action() grcon.Action {
return grue.Act
}
func newInvalidResponseTypeError(expected, actual grcon.PacketType) InvalidResponseTypeError {
return InvalidResponseTypeError{
GrconUtilError: newGrconUtilError(grcon.Read, fmt.Errorf("invalid response type: expected %d but got %d", expected, actual)),
Expected: expected,
Actual: actual,
GrconClientError: newGrconClientError(grcon.Read, fmt.Errorf("invalid response type: expected %d but got %d", expected, actual)),
Expected: expected,
Actual: actual,
}
}
type InvalidResponseTypeError struct {
GrconUtilError
GrconClientError
Expected grcon.PacketType
Actual grcon.PacketType
}
func newAuthFailedError() AuthFailedError {
return AuthFailedError{
newGrconUtilError(grcon.Read, errors.New("authentication failed")),
newGrconClientError(grcon.Read, errors.New("authentication failed")),
}
}
type AuthFailedError struct {
GrconUtilError
GrconClientError
}
func newResponseIdMismatchError(expected, actual grcon.PacketId) ResponseIdMismatchError {
return ResponseIdMismatchError{
GrconUtilError: newGrconUtilError(grcon.Read, errors.New("invalid response type")),
Expected: expected,
Actual: actual,
GrconClientError: newGrconClientError(grcon.Read, errors.New("invalid response type")),
Expected: expected,
Actual: actual,
}
}
type ResponseIdMismatchError struct {
GrconUtilError
GrconClientError
Expected grcon.PacketId
Actual grcon.PacketId
}
func newResponseBodyError(expected, actual string) ResponseBodyError {
return ResponseBodyError{
newGrconUtilError(
newGrconClientError(
grcon.Read,
fmt.Errorf("response body error: expected '%s' got '%s'", expected, actual),
),
@ -76,5 +76,5 @@ func newResponseBodyError(expected, actual string) ResponseBodyError {
}
type ResponseBodyError struct {
GrconUtilError
GrconClientError
}

View file

@ -0,0 +1,84 @@
package client
import (
"github.com/hamburghammer/grcon"
"github.com/hamburghammer/grcon/util"
)
// NewMinecraftClient is a constructor for the MinecraftClient struct.
// The util.GenerateNewId can be used as idGenFunc.
func NewMinecraftClient(r util.RemoteConsole, idGenFunc func() grcon.PacketId) MinecraftClient {
return MinecraftClient{RemoteConsole: r, IdGenFunc: idGenFunc}
}
// MinecraftClient is a wrapper for a RemoteConsole that provides some utility functions.
// It simplifies the interaction with a remote console of a minecraft server.
type MinecraftClient struct {
// RemoteConsole is the console to use for the interactions.
util.RemoteConsole
// IdGenFunc is the function to use to generate ids.
IdGenFunc func() grcon.PacketId
}
// Auth should be used to authenticate the connection.
//
// It can return following errors:
// - InvalidResponseTypeError
// - ResponseIdMismatchError
// - AuthFailedError
func (sc MinecraftClient) Auth(password string) error {
reqID := sc.IdGenFunc()
err := sc.Write(grcon.Packet{Id: reqID, Type: grcon.SERVERDATA_AUTH, Body: []byte(password)})
if err != nil {
return err
}
packet, err := sc.Read()
if err != nil {
return err
}
if packet.Type != grcon.SERVERDATA_AUTH_RESPONSE {
return newInvalidResponseTypeError(grcon.SERVERDATA_AUTH_RESPONSE, packet.Type)
}
if packet.Id == -1 {
return newAuthFailedError()
}
if packet.Id != reqID {
return newResponseIdMismatchError(reqID, packet.Id)
}
return nil
}
// Exec executes the command on the given RemoteConsole implementation and
// waits till the response is read returns it.
//
// Errors:
// Returns all errors returned from the Write and Read methode from the RemoteConsole implementation.
// Can also return an InvalidResponseTypeError if the response is not of the type
// grcon.SERVERDATA_RESPONSE_VALUE.
func (sc MinecraftClient) Exec(cmd string) ([]byte, error) {
cmdPacket := grcon.Packet{
Id: sc.IdGenFunc(),
Type: grcon.SERVERDATA_EXECCOMMAND,
Body: []byte(cmd),
}
err := sc.Write(cmdPacket)
if err != nil {
return []byte{}, err
}
packet, err := sc.Read()
if err != nil {
return []byte{}, err
}
if packet.Type != grcon.SERVERDATA_RESPONSE_VALUE {
return []byte{}, newInvalidResponseTypeError(grcon.SERVERDATA_RESPONSE_VALUE, packet.Type)
}
if packet.Id != cmdPacket.Id {
return []byte{}, newResponseIdMismatchError(cmdPacket.Id, packet.Id)
}
return packet.Body, nil
}

View file

@ -59,6 +59,7 @@ func newUnexpectedFormatError() UnexpectedFormatError {
}
// UnexpectedFormatError occurres when the packet size is smaller than the minimum size.
// This indicates a wrongly composed/formatted packet.
type UnexpectedFormatError struct {
GrconGenericError
}
@ -72,8 +73,8 @@ func newRequestTooLongError() RequestTooLongError {
}
}
// RequestTooLongError occurres when the body of a packet is to big.
// Can also be an indicator that the package is malformed/corrupted.
// RequestTooLongError occurres when the length of a packet is to big.
// This indicates that the body is too long.
type RequestTooLongError struct {
GrconGenericError
}
@ -82,7 +83,7 @@ func newResponseTooLongError() ResponseTooLongError {
return ResponseTooLongError{
newGrconGenericError(
Read,
errors.New("response body is too long"),
errors.New("response packet is too long"),
),
}
}