lsps0: lsps0 server implementation

This commit is contained in:
Jesse de Wit
2023-08-11 08:49:57 +02:00
parent c23dc64df5
commit ef19a67e7a
7 changed files with 689 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
package lightning
type CustomMessage struct {
PeerId string
Type uint32
Data []byte
}
type CustomMsgClient interface {
Recv() (*CustomMessage, error)
Send(*CustomMessage) error
}

14
lsps0/codes/code.go Normal file
View File

@@ -0,0 +1,14 @@
package codes
type Code int32
const (
OK Code = 0
Canceled Code = 1
Unknown Code = 2
ParseError Code = -32700
InvalidRequest Code = -32600
MethodNotFound Code = -32601
InvalidParams Code = -32602
InternalError Code = -32603
)

30
lsps0/jsonrpc/jsonrpc.go Normal file
View File

@@ -0,0 +1,30 @@
package jsonrpc
import "encoding/json"
var Version = "2.0"
type Request struct {
JsonRpc string `json:"jsonrpc"`
Method string `json:"method"`
Id string `json:"id"`
Params json.RawMessage `json:"params"`
}
type Response struct {
JsonRpc string `json:"jsonrpc"`
Id string `json:"id"`
Result json.RawMessage `json:"result"`
}
type Error struct {
JsonRpc string `json:"jsonrpc"`
Id *string `json:"id"`
Error ErrorBody `json:"error"`
}
type ErrorBody struct {
Code int32 `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data,omitempty"`
}

55
lsps0/protocol_server.go Normal file
View File

@@ -0,0 +1,55 @@
package lsps0
import "context"
type ProtocolServer interface {
ListProtocols(
ctx context.Context,
req *ListProtocolsRequest,
) (*ListProtocolsResponse, error)
}
type protocolServer struct {
protocols []uint32
}
type ListProtocolsRequest struct{}
type ListProtocolsResponse struct {
Protocols []uint32 `json:"protocols"`
}
func NewProtocolServer(supportedProtocols []uint32) ProtocolServer {
return &protocolServer{
protocols: supportedProtocols,
}
}
func (s *protocolServer) ListProtocols(
ctx context.Context,
req *ListProtocolsRequest,
) (*ListProtocolsResponse, error) {
return &ListProtocolsResponse{
Protocols: s.protocols,
}, nil
}
func RegisterProtocolServer(s ServiceRegistrar, p ProtocolServer) {
s.RegisterService(
&ServiceDesc{
ServiceName: "lsps0",
HandlerType: (*ProtocolServer)(nil),
Methods: []MethodDesc{
{
MethodName: "lsps0.list_protocols",
Handler: func(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(ListProtocolsRequest)
if err := dec(in); err != nil {
return nil, err
}
return srv.(ProtocolServer).ListProtocols(ctx, in)
},
},
},
},
p,
)
}

273
lsps0/server.go Normal file
View File

@@ -0,0 +1,273 @@
package lsps0
import (
"context"
"encoding/json"
"errors"
"log"
"sync"
"time"
"github.com/breez/lspd/lightning"
"github.com/breez/lspd/lsps0/codes"
"github.com/breez/lspd/lsps0/jsonrpc"
"github.com/breez/lspd/lsps0/status"
"golang.org/x/exp/slices"
)
var ErrAlreadyServing = errors.New("lsps0: already serving")
var ErrServerStopped = errors.New("lsps0: the server has been stopped")
var Lsps0MessageType uint32 = 37913
var BadMessageFormatError string = "bad message format"
var InternalError string = "internal error"
// ServiceDesc and is constructed from it for internal purposes.
type serviceInfo struct {
// Contains the implementation for the methods in this service.
serviceImpl interface{}
methods map[string]*MethodDesc
}
type methodInfo struct {
service *serviceInfo
method *MethodDesc
}
type Server struct {
mu sync.Mutex
serve bool
services map[string]*serviceInfo
methods map[string]*methodInfo
}
func NewServer() *Server {
return &Server{
services: make(map[string]*serviceInfo),
methods: make(map[string]*methodInfo),
}
}
func (s *Server) Serve(lis lightning.CustomMsgClient) error {
s.mu.Lock()
if s.serve {
return ErrAlreadyServing
}
s.serve = true
s.mu.Unlock()
defer func() {
s.mu.Lock()
s.serve = false
s.mu.Unlock()
}()
for {
msg, err := lis.Recv()
if err != nil {
if err == context.Canceled {
log.Printf("lsps0: lis got canceled, stopping.")
return err
}
log.Printf("lsps0 Serve(): Recv() err != nil: %v", err)
<-time.After(time.Second)
continue
}
// Ignore any message that is not an lsps0 message
if msg.Type != Lsps0MessageType {
continue
}
// Make sure there are no 0 bytes
if slices.Contains(msg.Data, 0x00) {
log.Printf("UNUSUAL: Got custom message containing 0 bytes from peer '%s'.", msg.PeerId)
go sendError(lis, msg, nil, status.New(codes.ParseError, BadMessageFormatError))
continue
}
req := new(jsonrpc.Request)
err = json.Unmarshal(msg.Data, req)
if err != nil {
log.Printf("UNUSUAL: Failed to unmarshal custom message from peer '%s': %v", msg.PeerId, err)
go sendError(lis, msg, nil, status.New(codes.ParseError, BadMessageFormatError))
continue
}
if req == nil {
log.Printf("UNUSUAL: req == nil after unmarshal custom message from peer '%s': %v", msg.PeerId, err)
go sendError(lis, msg, nil, status.New(codes.ParseError, BadMessageFormatError))
continue
}
if req.JsonRpc != jsonrpc.Version {
log.Printf("UNUSUAL: jsonrpc version is '%s' in custom message from peer '%s': %v", req.JsonRpc, msg.PeerId, err)
go sendError(lis, msg, req, status.Newf(codes.InvalidRequest, "Expected jsonrpc %s, found %s", jsonrpc.Version, req.JsonRpc))
continue
}
m, ok := s.methods[req.Method]
if !ok {
log.Printf("UNUSUAL: peer '%s' requested method '%s', but it does not exist.", msg.PeerId, req.Method)
go sendError(lis, msg, req, status.New(codes.MethodNotFound, "method not found"))
continue
}
df := func(v interface{}) error {
if err := json.Unmarshal(req.Params, v); err != nil {
return status.Newf(codes.InvalidParams, "invalid params").Err()
}
return nil
}
// NOTE: The handler is being called synchonously. There's an option to
// do this in a goroutine instead. Also, there's the option to put the
// goroutine in the method desc for specific methods instead.
r, err := m.method.Handler(m.service.serviceImpl, context.TODO(), df)
if err != nil {
s, ok := status.FromError(err)
if !ok {
log.Printf("Internal error when processing custom message '%s' from peer '%s': %v", string(msg.Data), msg.PeerId, err)
s = status.New(codes.InternalError, InternalError)
}
go sendError(lis, msg, req, s)
continue
}
go sendResponse(lis, msg, req, r)
}
}
func sendResponse(
lis lightning.CustomMsgClient,
in *lightning.CustomMessage,
req *jsonrpc.Request,
params interface{},
) {
rd, err := json.Marshal(params)
if err != nil {
log.Printf("Failed to mashal response params '%+v'", params)
sendError(lis, in, req, status.New(codes.InternalError, InternalError))
return
}
resp := &jsonrpc.Response{
JsonRpc: jsonrpc.Version,
Id: req.Id,
Result: rd,
}
res, err := json.Marshal(resp)
if err != nil {
log.Printf("Failed to mashal response '%+v'", resp)
sendError(lis, in, req, status.New(codes.InternalError, InternalError))
return
}
msg := &lightning.CustomMessage{
PeerId: in.PeerId,
Type: Lsps0MessageType,
Data: res,
}
err = lis.Send(msg)
if err != nil {
log.Printf("Failed to send response message '%s' to request '%s' to peer '%s': %v", string(msg.Data), string(in.Data), msg.PeerId, err)
return
}
}
func sendError(
lis lightning.CustomMsgClient,
in *lightning.CustomMessage,
req *jsonrpc.Request,
status *status.Status,
) {
var id *string
if req != nil && req.Id != "" {
id = &req.Id
}
resp := &jsonrpc.Error{
JsonRpc: jsonrpc.Version,
Id: id,
Error: jsonrpc.ErrorBody{
Code: int32(status.Code),
Message: status.Message,
Data: nil,
},
}
res, err := json.Marshal(resp)
if err != nil {
log.Printf("Failed to mashal response '%+v'", resp)
return
}
msg := &lightning.CustomMessage{
PeerId: in.PeerId,
Type: Lsps0MessageType,
Data: res,
}
err = lis.Send(msg)
if err != nil {
log.Printf("Failed to send message '%s' to peer '%s': %v", string(msg.Data), msg.PeerId, err)
return
}
}
func (s *Server) RegisterService(desc *ServiceDesc, impl interface{}) {
s.mu.Lock()
defer s.mu.Unlock()
log.Printf("RegisterService(%q)", desc.ServiceName)
if s.serve {
log.Fatalf("lsps0: Server.RegisterService after Server.Serve for %q", desc.ServiceName)
}
if _, ok := s.services[desc.ServiceName]; ok {
log.Fatalf("lsps0: Server.RegisterService found duplicate service registration for %q", desc.ServiceName)
}
info := &serviceInfo{
serviceImpl: impl,
methods: make(map[string]*MethodDesc),
}
for i := range desc.Methods {
d := &desc.Methods[i]
if _, ok := s.methods[d.MethodName]; ok {
log.Fatalf("lsps0: Server.RegisterService found duplicate method registration for %q", d.MethodName)
}
info.methods[d.MethodName] = d
s.methods[d.MethodName] = &methodInfo{
service: info,
method: d,
}
}
s.services[desc.ServiceName] = info
}
type ServiceDesc struct {
ServiceName string
// The pointer to the service interface. Used to check whether the user
// provided implementation satisfies the interface requirements.
HandlerType interface{}
Methods []MethodDesc
}
type MethodDesc struct {
MethodName string
Handler methodHandler
}
type methodHandler func(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error)
// ServiceRegistrar wraps a single method that supports service registration. It
// enables users to pass concrete types other than grpc.Server to the service
// registration methods exported by the IDL generated code.
type ServiceRegistrar interface {
// RegisterService registers a service and its implementation to the
// concrete type implementing this interface. It may not be called
// once the server has started serving.
// desc describes the service and its methods and handlers. impl is the
// service implementation which is passed to the method handlers.
RegisterService(desc *ServiceDesc, impl interface{})
}

243
lsps0/server_test.go Normal file
View File

@@ -0,0 +1,243 @@
package lsps0
import (
"encoding/json"
"testing"
"github.com/breez/lspd/lightning"
"github.com/breez/lspd/lsps0/jsonrpc"
"github.com/stretchr/testify/assert"
)
type MockLightningImpl struct {
lightning.CustomMsgClient
req chan *lightning.CustomMessage
err error
resp chan *lightning.CustomMessage
}
func newMock(err error) *MockLightningImpl {
return &MockLightningImpl{
req: make(chan *lightning.CustomMessage, 100),
err: err,
resp: make(chan *lightning.CustomMessage, 100),
}
}
func (m *MockLightningImpl) Recv() (*lightning.CustomMessage, error) {
msg := <-m.req
return msg, m.err
}
func (m *MockLightningImpl) Send(c *lightning.CustomMessage) error {
m.resp <- c
return nil
}
func TestSuccess(t *testing.T) {
srv := NewServer()
pSrv := &protocolServer{
protocols: []uint32{1, 2},
}
RegisterProtocolServer(srv, pSrv)
rawMsg := `{
"method": "lsps0.list_protocols",
"jsonrpc": "2.0",
"id": "example#3cad6a54d302edba4c9ade2f7ffac098",
"params": {}
}`
mock := newMock(nil)
mock.req <- &lightning.CustomMessage{
PeerId: "AAA",
Type: 37913,
Data: []byte(rawMsg),
}
go srv.Serve(mock)
resp := <-mock.resp
r := new(jsonrpc.Response)
err := json.Unmarshal(resp.Data, r)
assert.NoError(t, err)
assert.Equal(t, uint32(37913), resp.Type)
assert.Equal(t, "AAA", resp.PeerId)
assert.Equal(t, "example#3cad6a54d302edba4c9ade2f7ffac098", r.Id)
assert.Equal(t, "2.0", r.JsonRpc)
result := new(ListProtocolsResponse)
err = json.Unmarshal(r.Result, result)
assert.NoError(t, err)
assert.Equal(t, []uint32{1, 2}, result.Protocols)
}
func TestInvalidRequest(t *testing.T) {
srv := NewServer()
pSrv := &protocolServer{
protocols: []uint32{1, 2},
}
RegisterProtocolServer(srv, pSrv)
rawMsg := `{
"method": "lsps0.list_protocols",
"jsonrpc": "2.0",
"id": "example#3cad6a54d302edba4c9ade2f7ffac098",
"params": {}
`
mock := newMock(nil)
mock.req <- &lightning.CustomMessage{
PeerId: "AAA",
Type: 37913,
Data: []byte(rawMsg),
}
go srv.Serve(mock)
resp := <-mock.resp
r := new(jsonrpc.Error)
err := json.Unmarshal(resp.Data, r)
assert.NoError(t, err)
assert.Equal(t, uint32(37913), resp.Type)
assert.Equal(t, "AAA", resp.PeerId)
assert.Nil(t, r.Id)
assert.Equal(t, "2.0", r.JsonRpc)
assert.Equal(t, r.Error.Code, int32(-32700))
assert.Equal(t, r.Error.Message, "bad message format")
}
func TestInvalidJsonRpcVersion(t *testing.T) {
srv := NewServer()
pSrv := &protocolServer{
protocols: []uint32{1, 2},
}
RegisterProtocolServer(srv, pSrv)
rawMsg := `{
"method": "lsps0.list_protocols",
"jsonrpc": "1.0",
"id": "example#3cad6a54d302edba4c9ade2f7ffac098",
"params": {}
}`
mock := newMock(nil)
mock.req <- &lightning.CustomMessage{
PeerId: "AAA",
Type: 37913,
Data: []byte(rawMsg),
}
go srv.Serve(mock)
resp := <-mock.resp
r := new(jsonrpc.Error)
err := json.Unmarshal(resp.Data, r)
assert.NoError(t, err)
assert.Equal(t, uint32(37913), resp.Type)
assert.Equal(t, "AAA", resp.PeerId)
assert.Equal(t, "example#3cad6a54d302edba4c9ade2f7ffac098", *r.Id)
assert.Equal(t, "2.0", r.JsonRpc)
assert.Equal(t, r.Error.Code, int32(-32600))
assert.Equal(t, r.Error.Message, "Expected jsonrpc 2.0, found 1.0")
}
func TestInvalidJsonRpcVersionNoId(t *testing.T) {
srv := NewServer()
pSrv := &protocolServer{
protocols: []uint32{1, 2},
}
RegisterProtocolServer(srv, pSrv)
rawMsg := `{
"method": "lsps0.list_protocols",
"jsonrpc": "1.0",
"params": {}
}`
mock := newMock(nil)
mock.req <- &lightning.CustomMessage{
PeerId: "AAA",
Type: 37913,
Data: []byte(rawMsg),
}
go srv.Serve(mock)
resp := <-mock.resp
r := new(jsonrpc.Error)
err := json.Unmarshal(resp.Data, r)
assert.NoError(t, err)
assert.Equal(t, uint32(37913), resp.Type)
assert.Equal(t, "AAA", resp.PeerId)
assert.Nil(t, r.Id)
assert.Equal(t, "2.0", r.JsonRpc)
assert.Equal(t, r.Error.Code, int32(-32600))
assert.Equal(t, r.Error.Message, "Expected jsonrpc 2.0, found 1.0")
}
func TestUnknownMethod(t *testing.T) {
srv := NewServer()
pSrv := &protocolServer{
protocols: []uint32{1, 2},
}
RegisterProtocolServer(srv, pSrv)
rawMsg := `{
"method": "lsps0.unknown",
"jsonrpc": "2.0",
"id": "example#3cad6a54d302edba4c9ade2f7ffac098",
"params": {}
}`
mock := newMock(nil)
mock.req <- &lightning.CustomMessage{
PeerId: "AAA",
Type: 37913,
Data: []byte(rawMsg),
}
go srv.Serve(mock)
resp := <-mock.resp
r := new(jsonrpc.Error)
err := json.Unmarshal(resp.Data, r)
assert.NoError(t, err)
assert.Equal(t, uint32(37913), resp.Type)
assert.Equal(t, "AAA", resp.PeerId)
assert.Equal(t, "example#3cad6a54d302edba4c9ade2f7ffac098", *r.Id)
assert.Equal(t, "2.0", r.JsonRpc)
assert.Equal(t, r.Error.Code, int32(-32601))
assert.Equal(t, r.Error.Message, "method not found")
}
func TestInvalidParams(t *testing.T) {
srv := NewServer()
pSrv := &protocolServer{
protocols: []uint32{1, 2},
}
RegisterProtocolServer(srv, pSrv)
rawMsg := `{
"method": "lsps0.list_protocols",
"jsonrpc": "2.0",
"id": "example#3cad6a54d302edba4c9ade2f7ffac098",
"params": []
}`
mock := newMock(nil)
mock.req <- &lightning.CustomMessage{
PeerId: "AAA",
Type: 37913,
Data: []byte(rawMsg),
}
go srv.Serve(mock)
resp := <-mock.resp
r := new(jsonrpc.Error)
err := json.Unmarshal(resp.Data, r)
assert.NoError(t, err)
assert.Equal(t, uint32(37913), resp.Type)
assert.Equal(t, "AAA", resp.PeerId)
assert.Equal(t, "example#3cad6a54d302edba4c9ade2f7ffac098", *r.Id)
assert.Equal(t, "2.0", r.JsonRpc)
assert.Equal(t, r.Error.Code, int32(-32602))
assert.Equal(t, r.Error.Message, "invalid params")
}

62
lsps0/status/status.go Normal file
View File

@@ -0,0 +1,62 @@
package status
import (
"fmt"
"github.com/breez/lspd/lsps0/codes"
)
type Status struct {
Code codes.Code
Message string
}
func New(c codes.Code, msg string) *Status {
return &Status{Code: c, Message: msg}
}
func Newf(c codes.Code, format string, a ...interface{}) *Status {
return New(c, fmt.Sprintf(format, a...))
}
func FromError(err error) (s *Status, ok bool) {
if err == nil {
return nil, true
}
if se, ok := err.(interface {
Lsps0Status() *Status
}); ok {
return se.Lsps0Status(), true
}
return New(codes.Unknown, err.Error()), false
}
// Convert is a convenience function which removes the need to handle the
// boolean return value from FromError.
func Convert(err error) *Status {
s, _ := FromError(err)
return s
}
func (s *Status) Err() error {
if s.Code == codes.OK {
return nil
}
return &Error{s: s}
}
func (s *Status) String() string {
return fmt.Sprintf("lsps0 error: code = %d desc = %s", int32(s.Code), s.Message)
}
type Error struct {
s *Status
}
func (e *Error) Error() string {
return e.s.String()
}
func (e *Error) Lsps0Status() *Status {
return e.s
}