diff options
Diffstat (limited to 'recv.go')
-rw-r--r-- | recv.go | 256 |
1 files changed, 256 insertions, 0 deletions
@@ -0,0 +1,256 @@ +package binproto + +import ( + "encoding/binary" + "github.com/kch42/kagus" + "io" + "sync" +) + +// UnitReader is an interface with the ReadUnit function, which ist the basic reading function of the binproto. +// ReadUnit reads the next binproto unit from the reader. The second output has a different meaning for each unit type: +// +// UTRequest, UTAnswer, UTEvent - uint16 +// UTBin - []byte +// UTNumber - int64 +// UTUKey, UTByte - byte +// UTBinStream - *BinStreamReader +// UTBool - bool +// +// A UnitReader implementation should usually wrap the SimpleUnitReader implementation. +type UnitReader interface { + ReadUnit() (UnitType, interface{}, error) +} + +// SimpleUnitReader is a UnitReader implementation that gets its data from an io.Reader. +type SimpleUnitReader struct { + r io.Reader + mu *sync.Mutex +} + +func NewSimpleUnitReader(r io.Reader) *SimpleUnitReader { + return &SimpleUnitReader{ + r: r, + mu: new(sync.Mutex)} +} + +func (sur *SimpleUnitReader) ReadUnit() (UnitType, interface{}, error) { + r := sur.r + + sur.mu.Lock() + doUnlock := true + defer func() { + if doUnlock { + sur.mu.Unlock() + } + }() + + _ut, err := kagus.ReadByte(r) + if err != nil { + return 0, nil, err + } + + ut := UnitType(_ut) + switch ut { + case UTNil: + return ut, nil, nil + case UTRequest, UTAnswer, UTEvent: + var code uint16 + if err := binary.Read(r, binary.LittleEndian, &code); err != nil { + return ut, nil, err + } + return ut, code, nil + case UTBin: + var l uint32 + if err := binary.Read(r, binary.LittleEndian, &l); err != nil { + return ut, nil, err + } + buf := make([]byte, l) + _, err := io.ReadFull(r, buf) + if err != nil { + return ut, nil, err + } + return ut, buf, nil + case UTNumber: + var n int64 + if err := binary.Read(r, binary.LittleEndian, &n); err != nil { + return ut, nil, err + } + return ut, n, nil + case UTList, UTTextKVMap, UTIdKVMap: + return ut, nil, nil + case UTUKey, UTByte: + k, err := kagus.ReadByte(r) + return ut, k, err + case UTBinStream: + doUnlock = false + return ut, &BinstreamReader{r: r, surMu: sur.mu}, nil + case UTTerm: + return ut, nil, nil + case UTBool: + _b, err := kagus.ReadByte(r) + b := true + if _b == 0 { + b = false + } + return ut, b, err + } + + return ut, nil, UnknownUnit +} + +// IdKVPair will be returned from ReadIdKVPair. ValueType and ValuePayload are the first two outputs of ReadUnit for the value. +type IdKVPair struct { + Key uint8 + ValueType UnitType + ValuePayload interface{} +} + +// ReadIdKVPair reads a UKey + any unit pair. err will be Terminated, if this was the last KVPair. +func ReadIdKVPair(ur UnitReader) (kvp IdKVPair, err error) { + var ut UnitType + var data interface{} + ut, data, err = ur.ReadUnit() + if err != nil { + return + } + + switch ut { + case UTUKey: + kvp.Key = data.(byte) + case UTTerm: + err = Terminated + return + default: + err = UnexpectedUnit + return + } + + kvp.ValueType, kvp.ValuePayload, err = ur.ReadUnit() + return +} + +// TextKVPair will be returned from ReadTextKVPair. ValueType and ValuePayload are the first two outputs of ReadUnit for the value. +type TextKVPair struct { + Key string + ValueType UnitType + ValuePayload interface{} +} + +// ReadTextKVPair reads a Bin(as string) + any unit pair. err will be Terminated, if this was the last KVPair. +func ReadTextKVPair(ur UnitReader) (kvp TextKVPair, err error) { + var ut UnitType + var data interface{} + ut, data, err = ur.ReadUnit() + if err != nil { + return + } + + switch ut { + case UTBin: + kvp.Key = string(data.([]byte)) + case UTTerm: + err = Terminated + return + default: + err = UnexpectedUnit + return + } + + kvp.ValueType, kvp.ValuePayload, err = ur.ReadUnit() + return +} + +const maxSkipDepth = 16 + +func skipUnit(ur UnitReader, ut UnitType, data interface{}, revDepth int) error { + if revDepth == 0 { + return TooDeeplyNested + } + + switch ut { + case UTNil, UTRequest, UTAnswer, UTEvent, UTBin, UTNumber, UTUKey, UTTerm, UTBool, UTByte: + return nil + case UTList: + for { + nUt, nData, nErr := ur.ReadUnit() + if nErr != nil { + return nErr + } + + if nUt == UTTerm { + return nil + } + + if err := skipUnit(ur, nUt, nData, revDepth-1); err != nil { + return err + } + } + case UTTextKVMap: + for { + switch kvp, err := ReadTextKVPair(ur); err { + case nil: + if err := skipUnit(ur, kvp.ValueType, kvp.ValuePayload, revDepth-1); err != nil { + return err + } + case Terminated: + return nil + default: + return err + } + } + case UTIdKVMap: + for { + switch kvp, err := ReadIdKVPair(ur); err { + case nil: + if err := skipUnit(ur, kvp.ValueType, kvp.ValuePayload, revDepth-1); err != nil { + return err + } + case Terminated: + return nil + default: + return err + } + } + case UTBinStream: + bsr := data.(*BinstreamReader) + return bsr.FastForward() + } + + return UnexpectedUnit +} + +// SkipUnit skips the current unit recursively. ut and data are the first two outputs of ReadUnit. +// If the structure is nested too deeply, this function will abort with TooDeeplyNested. +func SkipUnit(ur UnitReader, ut UnitType, data interface{}) error { + return skipUnit(ur, ut, data, maxSkipDepth) +} + +// SkipNext is ReadNext + SkipUnit. +func SkipNext(ur UnitReader) error { + ut, data, err := ur.ReadUnit() + if err != nil { + return err + } + return SkipUnit(ur, ut, data) +} + +// ReadExpect reads a unit and tests, if the type is te expected type. +// If not, UnexpectedUnit is returned and the read unit will be skipped +// (if this fails, the reason for that is returned instead of UnexpectedError). +func ReadExpect(ur UnitReader, expected UnitType) (interface{}, error) { + ut, data, err := ur.ReadUnit() + if err != nil { + return nil, err + } + + if ut == expected { + return data, nil + } + + if err := SkipUnit(ur, ut, data); err != nil { + return nil, err + } + + return nil, UnexpectedUnit +} |