cacert-gosigner/pkg/protocol/protocol.go
Jan Dittberner 8e443bd8b4 Implement protocol improvements
This commit implements a client and server side state machine
for the serial protocol.
2022-11-28 11:56:32 +01:00

268 lines
6.3 KiB
Go

/*
Copyright 2022 CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package protocol handles the protocol message marshaling and unmarshalling.
package protocol
import (
"errors"
"fmt"
"sync"
"github.com/shamaton/msgpackgen/msgpack"
"github.com/sirupsen/logrus"
"git.cacert.org/cacert-gosigner/pkg/x509/revoking"
"git.cacert.org/cacert-gosigner/pkg/health"
"git.cacert.org/cacert-gosigner/pkg/messages"
)
type Command struct {
Announce *messages.CommandAnnounce
Command interface{}
}
type Response struct {
Announce *messages.ResponseAnnounce
Response interface{}
}
func (r *Response) String() string {
return fmt.Sprintf("Response[Code=%s] created=%s data=%s", r.Announce.Code, r.Announce.Created, r.Response)
}
// Handler is responsible for parsing incoming frames and calling commands
type Handler interface {
HandleCommandAnnounce([]byte) error
HandleCommand([]byte) error
ResponseAnnounce() ([]byte, error)
ResponseData() ([]byte, error)
}
type MsgPackHandler struct {
logger *logrus.Logger
healthHandler *health.Handler
fetchCRLHandler *revoking.FetchCRLHandler
currentCommand *Command
currentResponse *Response
lock sync.Mutex
}
func (m *MsgPackHandler) HandleCommandAnnounce(frame []byte) error {
m.lock.Lock()
defer m.lock.Unlock()
var ann messages.CommandAnnounce
if err := msgpack.Unmarshal(frame, &ann); err != nil {
return fmt.Errorf("could not unmarshal command announcement: %w", err)
}
m.logger.WithField("announcement", &ann).Info("received command announcement")
m.currentCommand = &Command{Announce: &ann}
return nil
}
func (m *MsgPackHandler) HandleCommand(frame []byte) error {
m.lock.Lock()
defer m.lock.Unlock()
var clientError error
switch m.currentCommand.Announce.Code {
case messages.CmdHealth:
// health has no payload, ignore the frame
response, err := m.handleCommand()
if err != nil {
m.logger.WithError(err).Error("health handling failed")
clientError = errors.New("could not handle request")
break
}
m.currentResponse = response
case messages.CmdFetchCRL:
var command messages.FetchCRLCommand
if err := msgpack.Unmarshal(frame, &command); err != nil {
m.logger.WithError(err).Error("unmarshal failed")
clientError = errors.New("could not unmarshal fetch crl command")
break
}
m.currentCommand.Command = command
response, err := m.handleCommand()
if err != nil {
m.logger.WithError(err).Error("fetch CRL handling failed")
clientError = errors.New("could not handle request")
break
}
m.currentResponse = response
}
if clientError != nil {
m.currentResponse = buildErrorResponse(clientError.Error())
}
m.logger.WithField(
"command",
m.currentCommand,
).WithField(
"response",
m.currentResponse,
).Info("handled command")
m.currentCommand = nil
return nil
}
func (m *MsgPackHandler) ResponseAnnounce() ([]byte, error) {
m.lock.Lock()
defer m.lock.Unlock()
announceData, err := msgpack.Marshal(m.currentResponse.Announce)
if err != nil {
return nil, fmt.Errorf("could not marshal response announcement: %w", err)
}
m.logger.WithField("announcement", &m.currentResponse.Announce).Info("write response announcement")
return announceData, nil
}
func (m *MsgPackHandler) ResponseData() ([]byte, error) {
m.lock.Lock()
defer m.lock.Unlock()
responseData, err := msgpack.Marshal(m.currentResponse.Response)
if err != nil {
return nil, fmt.Errorf("could not marshal response: %w", err)
}
m.logger.WithField("response", &m.currentResponse.Response).Info("write response")
return responseData, nil
}
func (m *MsgPackHandler) handleCommand() (*Response, error) {
var (
err error
responseData interface{}
responseCode messages.ResponseCode
)
switch m.currentCommand.Announce.Code {
case messages.CmdHealth:
var res *health.Result
res, err = m.healthHandler.CheckHealth()
if err != nil {
return nil, err
}
response := &messages.HealthResponse{
Version: res.Version,
Healthy: res.Healthy,
}
for _, info := range res.Info {
response.Info = append(response.Info, &messages.HealthInfo{
Source: info.Source,
Healthy: info.Healthy,
MoreInfo: info.MoreInfo,
})
}
responseCode, responseData = messages.RespHealth, response
case messages.CmdFetchCRL:
var res *revoking.Result
fetchCRLPayload, ok := m.currentCommand.Command.(messages.FetchCRLCommand)
if !ok {
return nil, fmt.Errorf("could not use payload as FetchCRLPayload")
}
res, err = m.fetchCRLHandler.FetchCRL(fetchCRLPayload.IssuerID)
if err != nil {
return nil, err
}
response := &messages.FetchCRLResponse{
IsDelta: false,
CRLData: res.Data,
}
responseCode, responseData = messages.RespFetchCRL, response
default:
return nil, fmt.Errorf("unhandled command %s", m.currentCommand.Announce)
}
if err != nil {
return nil, fmt.Errorf("error from command handler: %w", err)
}
return &Response{
Announce: messages.BuildResponseAnnounce(responseCode),
Response: responseData,
}, nil
}
func buildErrorResponse(errMsg string) *Response {
return &Response{
Announce: messages.BuildResponseAnnounce(messages.RespError),
Response: &messages.ErrorResponse{Message: errMsg},
}
}
func New(logger *logrus.Logger, handlers ...RegisterHandler) (Handler, error) {
messages.RegisterGeneratedResolver()
h := &MsgPackHandler{
logger: logger,
}
for _, reg := range handlers {
reg(h)
}
return h, nil
}
type RegisterHandler func(handler *MsgPackHandler)
func RegisterHealthHandler(healthHandler *health.Handler) func(*MsgPackHandler) {
return func(h *MsgPackHandler) {
h.healthHandler = healthHandler
}
}
func RegisterFetchCRLHandler(fetchCRLHandler *revoking.FetchCRLHandler) func(handler *MsgPackHandler) {
return func(h *MsgPackHandler) {
h.fetchCRLHandler = fetchCRLHandler
}
}