@ -25,7 +25,6 @@ import (
"fmt"
"fmt"
"io"
"io"
"os"
"os"
"sync"
"time"
"time"
"github.com/shamaton/msgpackgen/msgpack"
"github.com/shamaton/msgpackgen/msgpack"
@ -36,124 +35,9 @@ import (
"git.cacert.org/cacert-gosigner/pkg/messages"
"git.cacert.org/cacert-gosigner/pkg/messages"
)
)
type protocolState int8
const (
cmdAnnounce protocolState = iota
cmdData
respAnnounce
respData
)
var validTransitions = map [ protocolState ] protocolState {
cmdAnnounce : cmdData ,
cmdData : respAnnounce ,
respAnnounce : respData ,
respData : cmdAnnounce ,
}
var protocolStateNames = map [ protocolState ] string {
cmdAnnounce : "CMD ANNOUNCE" ,
cmdData : "CMD DATA" ,
respAnnounce : "RESP ANNOUNCE" ,
respData : "RESP DATA" ,
}
func ( p protocolState ) String ( ) string {
if name , ok := protocolStateNames [ p ] ; ok {
return name
}
return fmt . Sprintf ( "unknown %d" , p )
}
type TestCommandGenerator struct {
type TestCommandGenerator struct {
logger * logrus . Logger
logger * logrus . Logger
currentCommand * protocol . Command
commands chan * protocol . Command
currentResponse * protocol . Response
commands chan * protocol . Command
lock sync . Mutex
}
func ( g * TestCommandGenerator ) CmdAnnouncement ( ) ( [ ] byte , error ) {
g . lock . Lock ( )
defer g . lock . Unlock ( )
g . currentCommand = <- g . commands
announceData , err := msgpack . Marshal ( g . currentCommand . Announce )
if err != nil {
return nil , fmt . Errorf ( "could not marshal command annoucement: %w" , err )
}
g . logger . WithField ( "announcement" , & g . currentCommand . Announce ) . Info ( "write command announcement" )
return announceData , nil
}
func ( g * TestCommandGenerator ) CmdData ( ) ( [ ] byte , error ) {
g . lock . Lock ( )
defer g . lock . Unlock ( )
cmdData , err := msgpack . Marshal ( g . currentCommand . Command )
if err != nil {
return nil , fmt . Errorf ( "could not marshal command data: %w" , err )
}
g . logger . WithField ( "command" , & g . currentCommand . Command ) . Info ( "write command data" )
return cmdData , nil
}
func ( g * TestCommandGenerator ) HandleResponseAnnounce ( frame [ ] byte ) error {
g . lock . Lock ( )
defer g . lock . Unlock ( )
var ann messages . ResponseAnnounce
if err := msgpack . Unmarshal ( frame , & ann ) ; err != nil {
return fmt . Errorf ( "could not unmarshal response announcement" )
}
g . logger . WithField ( "announcement" , & ann ) . Info ( "received response announcement" )
g . currentResponse = & protocol . Response { Announce : & ann }
return nil
}
func ( g * TestCommandGenerator ) HandleResponse ( frame [ ] byte ) error {
g . lock . Lock ( )
defer g . lock . Unlock ( )
switch g . currentResponse . Announce . Code {
case messages . RespHealth :
var response messages . HealthResponse
if err := msgpack . Unmarshal ( frame , & response ) ; err != nil {
return fmt . Errorf ( "unmarshal failed: %w" , err )
}
g . currentResponse . Response = & response
case messages . RespFetchCRL :
var response messages . FetchCRLResponse
if err := msgpack . Unmarshal ( frame , & response ) ; err != nil {
return fmt . Errorf ( "unmarshal failed: %w" , err )
}
g . currentResponse . Response = & response
}
g . logger . WithField (
"command" ,
g . currentCommand ,
) . WithField (
"response" ,
g . currentResponse ,
) . Info ( "handled health response" )
return nil
}
}
func ( g * TestCommandGenerator ) GenerateCommands ( ctx context . Context ) error {
func ( g * TestCommandGenerator ) GenerateCommands ( ctx context . Context ) error {
@ -200,7 +84,11 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
case <- ctx . Done ( ) :
case <- ctx . Done ( ) :
_ = healthTimer . Stop ( )
_ = healthTimer . Stop ( )
g . logger . Info ( "stopping health check loop" )
g . logger . Info ( "stopped health check loop" )
_ = crlTimer . Stop ( )
g . logger . Info ( "stopped CRL fetch loop" )
return nil
return nil
case <- healthTimer . C :
case <- healthTimer . C :
@ -225,187 +113,169 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
}
}
type clientSimulator struct {
type clientSimulator struct {
protocolState protocolState
clientHandler protocol . ClientHandler
logger * logrus . Logger
lock sync . Mutex
framesIn chan [ ] byte
framesIn chan [ ] byte
framesOut chan [ ] byte
framesOut chan [ ] byte
framer protocol . Framer
framer protocol . Framer
commandGenerator * TestCommandGenerator
commandGenerator * TestCommandGenerator
logger * logrus . Logger
}
}
func ( c * clientSimulator ) writeCmdAnnouncement ( ) error {
const (
frame , err := c . commandGenerator . CmdAnnouncement ( )
responseAnnounceTimeout = 30 * time . Second
if err != nil {
responseDataTimeout = 2 * time . Second
return fmt . Errorf ( "could not get command annoucement: %w" , err )
)
}
c . logger . Trace ( "writing command announcement" )
func ( c * clientSimulator ) Run ( ctx context . Context ) error {
framerErrors := make ( chan error )
protocolErrors := make ( chan error )
generatorErrors := make ( chan error )
c . framesOut <- frame
go func ( ) {
err := c . framer . ReadFrames ( os . Stdin , c . framesIn )
if err := c . nextState ( ) ; err != nil {
framerErrors <- err
return err
} ( )
}
return nil
go func ( ) {
}
err := c . framer . WriteFrames ( os . Stdout , c . framesOut )
func ( c * clientSimulator ) writeCommand ( ) error {
framerErrors <- err
frame , err := c . commandGenerator . CmdData ( )
} ( )
if err != nil {
return fmt . Errorf ( "could not get command data: %w" , err )
}
c . logger . Trace ( "writing command data" )
go func ( ) {
clientProtocol := protocol . NewClient ( c . clientHandler , c . commandGenerator . commands , c . framesIn , c . framesOut , c . logger )
c . framesOut <- frame
err := clientProtocol . Handle ( )
if err := c . nextState ( ) ; err != nil {
protocolErrors <- err
return err
} ( )
}
return nil
go func ( ) {
}
err := c . commandGenerator . GenerateCommands ( ctx )
const responseAnnounceTimeout = 30 * time . Second
generatorErrors <- err
const responseDataTimeout = 2 * time . Second
} ( )
func ( c * clientSimulator ) handleResponseAnnounce ( ) error {
for {
c . logger . Trace ( "waiting for response announce" )
select {
case <- ctx . Done ( ) :
return nil
case err := <- framerErrors :
if err != nil {
return fmt . Errorf ( "error from framer: %w" , err )
}
select {
case frame := <- c . framesIn :
if frame == nil {
return nil
return nil
}
case err := <- generatorErrors :
if err != nil {
return fmt . Errorf ( "error from command generator: %w" , err )
}
if err := c . commandGenerator . HandleResponseAnnounce ( frame ) ; err != nil {
return nil
return fmt . Errorf ( "response announce handling failed: %w" , err )
case err := <- protocolErrors :
}
if err != nil {
return fmt . Errorf ( "error from protocol handler: %w" , err )
}
if err := c . nextState ( ) ; err != nil {
return nil
return err
}
}
case <- time . After ( responseAnnounceTimeout ) :
c . logger . Warn ( "response announce timeout expired" )
c . protocolState = cmdAnnounce
return nil
}
}
return nil
}
}
func ( c * clientSimulator ) handleResponseData ( ) error {
type ClientHandler struct {
c . logger . Trace ( "waiting for response data" )
logger * logrus . Logger
}
select {
func ( c ClientHandler ) Send ( command * protocol . Command , out chan [ ] byte ) error {
case frame := <- c . framesIn :
var (
if frame == nil {
frame [ ] byte
return nil
err error
}
)
if err := c . commandGenerator . HandleResponse ( frame ) ; err != nil {
frame , err = msgpack . Marshal ( command . Announce )
return fmt . Errorf ( "response handler failed: %w" , err )
if err != nil {
}
return fmt . Errorf ( "could not marshal command annoucement: %w" , err )
}
if err := c . nextState ( ) ; err != nil {
c . logger . WithField ( "announcement" , command . Announce ) . Info ( "write command announcement" )
return err
}
return nil
c . logger . Trace ( "writing command announcement" )
case <- time . After ( responseDataTimeout ) :
c . logger . Warn ( "response data timeout expired" )
c . protocolState = cmdAnnounc e
out <- frame
return nil
frame , err = msgpack . Marshal ( command . Command )
if err != nil {
return fmt . Errorf ( "could not marshal command data: %w" , err )
}
}
}
func ( c * clientSimulator ) Run ( ctx context . Context ) error {
c . logger . WithField ( "command" , command . Command ) . Info ( "write command data" )
c . protocolState = cmdAnnounce
errors := make ( chan error )
go func ( ) {
out <- frame
err := c . framer . ReadFrames ( os . Stdin , c . framesIn )
errors <- err
return nil
} ( )
}
go func ( ) {
func ( c ClientHandler ) ResponseAnnounce ( in chan [ ] byte ) ( * protocol . Response , error ) {
err := c . framer . WriteFrames ( os . Stdout , c . framesOut )
response := & protocol . Response { }
errors <- err
var announce messages . ResponseAnnounce
} ( )
go func ( ) {
select {
err := c . commandGenerator . GenerateCommands ( ctx )
case frame := <- in :
if err := msgpack . Unmarshal ( frame , & announce ) ; err != nil {
return nil , fmt . Errorf ( "could not unmarshal response announcement: %w" , err )
}
errors <- err
response . Announce = & announce
} ( )
for {
c . logger . WithField ( "announcement" , response . Announce ) . Debug ( "received response announcement" )
select {
case <- ctx . Done ( ) :
return nil
case err := <- errors :
if err != nil {
return fmt . Errorf ( "error from handler loop: %w" , err )
}
return nil
return response , nil
default :
case <- time . After ( responseAnnounceTimeout ) :
if err := c . handleProtocolState ( ) ; err != nil {
return nil , protocol . ErrResponseAnnounceTimeoutExpired
return err
}
}
}
}
}
}
func ( c * clientSimulator ) handleProtocolState ( ) error {
func ( c ClientHandler ) ResponseData ( in chan [ ] byte , response * protocol . Response ) error {
c . logger . Tracef ( "handling protocol state %s" , c . protocolState )
select {
case frame := <- in :
switch response . Announce . Code {
case messages . RespHealth :
var resp messages . HealthResponse
if err := msgpack . Unmarshal ( frame , & resp ) ; err != nil {
return fmt . Errorf ( "could not unmarshal health response data: %w" , err )
}
c . lock . Lock ( )
response . Response = & resp
defer c . lock . Unlock ( )
case messages . RespFetchCRL :
var resp messages . FetchCRLResponse
if err := msgpack . Unmarshal ( frame , & resp ) ; err != nil {
return fmt . Errorf ( "could not unmarshal fetch CRL response data: %w" , err )
}
switch c . protocolState {
response . Response = & resp
case cmdAnnounce :
default :
if err := c . writeCmdAnnouncement ( ) ; err != nil {
return fmt . Errorf ( "unhandled response code %s" , response . Announce . Code )
return err
}
case cmdData :
if err := c . writeCommand ( ) ; err != nil {
return err
}
case respAnnounce :
if err := c . handleResponseAnnounce ( ) ; err != nil {
return err
}
case respData :
if err := c . handleResponseData ( ) ; err != nil {
return err
}
}
default :
case <- time . After ( responseDataTimeout ) :
return fmt. Errorf ( "unknown protocol state %s" , c . protocolState )
return protocol . ErrResponseDataTimeoutExpired
}
}
return nil
return nil
}
}
func ( c * clientSimulator ) nextState ( ) error {
func ( c ClientHandler ) HandleResponse ( response * protocol . Response ) error {
next , ok := validTransitions [ c . protocolState ]
c . logger . WithField ( "response" , response . Announce ) . Info ( "handled response" )
if ! ok {
c . logger . WithField ( "response" , response ) . Debug ( "full response" )
return fmt . Errorf ( "illegal protocol state %s" , c . protocolState )
}
c . protocolState = next
return nil
return nil
}
}
func newClientHandler ( logger * logrus . Logger ) * ClientHandler {
return & ClientHandler { logger : logger }
}
func main ( ) {
func main ( ) {
logger := logrus . New ( )
logger := logrus . New ( )
logger . SetOutput ( os . Stderr )
logger . SetOutput ( os . Stderr )
@ -418,10 +288,11 @@ func main() {
logger : logger ,
logger : logger ,
commands : make ( chan * protocol . Command ) ,
commands : make ( chan * protocol . Command ) ,
} ,
} ,
logger : logger ,
logger : logger ,
framesIn : make ( chan [ ] byte ) ,
framesIn : make ( chan [ ] byte ) ,
framesOut : make ( chan [ ] byte ) ,
framesOut : make ( chan [ ] byte ) ,
framer : protocol . NewCOBSFramer ( logger ) ,
framer : protocol . NewCOBSFramer ( logger ) ,
clientHandler : newClientHandler ( logger ) ,
}
}
err := sim . Run ( context . Background ( ) )
err := sim . Run ( context . Background ( ) )