github.com/simpleiot/simpleiot@v0.18.3/client/cobs-wrapper.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "log" 8 9 "github.com/dim13/cobs" 10 "github.com/simpleiot/simpleiot/test" 11 ) 12 13 // CobsWrapper can be used to wrap an io.ReadWriteCloser to COBS encode/decode data 14 type CobsWrapper struct { 15 dev io.ReadWriteCloser 16 readLeftover bytes.Buffer 17 debug int 18 maxMessageLength int 19 } 20 21 // NewCobsWrapper creates a new cobs wrapper 22 func NewCobsWrapper(dev io.ReadWriteCloser, maxMessageLength int) *CobsWrapper { 23 ret := CobsWrapper{dev: dev, maxMessageLength: maxMessageLength} 24 // grow buffer to minimize allocations 25 ret.readLeftover.Grow(maxMessageLength) 26 return &ret 27 } 28 29 // ErrCobsDecodeError indicates we got an error decoding a COBS packet 30 var ErrCobsDecodeError = errors.New("COBS decode error") 31 32 // ErrCobsTooMuchData indicates we received too much data without a null in it 33 // to delineate packets 34 var ErrCobsTooMuchData = errors.New("COBS decode: too much data without null") 35 36 // ErrCobsLeftoverBufferFull indicates our leftover buffer is too full to process 37 var ErrCobsLeftoverBufferFull = errors.New("COBS leftover buffer too full") 38 39 // SetDebug sets the debug level. If >= 9, then it dumps the raw data 40 // received. 41 func (cw *CobsWrapper) SetDebug(debug int) { 42 cw.debug = debug 43 } 44 45 // Decode a null-terminated cobs frame to a slice of bytes 46 // data is shifted down to start at beginning of buffer 47 func cobsDecodeInplace(b []byte) (int, error) { 48 if len(b) <= 2 { 49 return 0, errors.New("Not enough data for cobs decode") 50 } 51 52 // used to skip leading zeros 53 foundStart := false 54 55 // define input and output indicies 56 var iIn, iOut int 57 var off, iOff uint8 58 59 for iIn = 0; iIn < len(b); iIn++ { 60 bCur := b[iIn] 61 62 if !foundStart { 63 if bCur == 0 { 64 continue 65 } 66 67 foundStart = true 68 off = bCur 69 iOff = 0 70 continue 71 } 72 73 iOff++ 74 75 if iOff == off { 76 if bCur == 0 { 77 // we've reached the end folks 78 return iOut, nil 79 } 80 81 if off != 0xff { 82 b[iOut] = 0 83 iOut++ 84 } 85 off = bCur 86 iOff = 0 87 } else { 88 if bCur == 0 { 89 return 0, ErrCobsDecodeError 90 } 91 b[iOut] = bCur 92 iOut++ 93 } 94 } 95 96 return iOut, nil 97 } 98 99 // Read a COBS encoded data stream. The stream may optionally start with one or more NULL 100 // bytes and must end with a NULL byte. This Read blocks until we 101 // get an entire packet or an error. b must be large enough to hold the entire packet. 102 func (cw *CobsWrapper) Read(b []byte) (int, error) { 103 // we read data until we see a zero or hit the size of the b buffer 104 // current location in read buffer 105 var cur int 106 107 // first, process any leftover bytes looking for packets 108 if cw.readLeftover.Len() > 0 { 109 foundStart := false 110 111 lb := cw.readLeftover.Bytes() 112 for i := 0; i < len(lb); i++ { 113 if !foundStart { 114 if lb[i] == 0 { 115 continue 116 } 117 foundStart = true 118 } 119 if lb[i] == 0 { 120 // found end of packet, copy to read buffer and process 121 _, _ = cw.readLeftover.Read(b[0:i]) 122 return cobsDecodeInplace(b[0:i]) 123 } 124 } 125 126 // write leftover bytes to beginning of buffer 127 bBuf := bytes.NewBuffer(b) 128 c, _ := bBuf.Write(cw.readLeftover.Bytes()) 129 130 cur += c 131 } 132 133 foundStart := false 134 135 for { 136 c, err := cw.dev.Read(b[cur:]) 137 if err != nil { 138 return 0, err 139 } 140 141 if c > 0 { 142 // look for zero in buffer 143 for i := 0; i < c; i++ { 144 if !foundStart { 145 if b[cur+i] == 0 { 146 continue 147 } 148 foundStart = true 149 } 150 if b[cur+i] == 0 { 151 // found end of packet, decode in place 152 // first save off extra bytes 153 cw.readLeftover.Write(b[cur+i+1 : cur+c]) 154 155 return cobsDecodeInplace(b[0 : cur+i+1]) 156 } 157 } 158 } 159 160 cur += c 161 162 if cur >= len(b) || cur > cw.maxMessageLength { 163 return 0, ErrCobsTooMuchData 164 } 165 } 166 } 167 168 func (cw *CobsWrapper) Write(b []byte) (int, error) { 169 if cw.debug >= 8 { 170 log.Println("SER TX RAW:", test.HexDump(b)) 171 } 172 173 w := append([]byte{0}, cobs.Encode(b)...) 174 175 if cw.debug >= 9 { 176 log.Println("SER TX COBS:", test.HexDump(w)) 177 } 178 179 return cw.dev.Write(w) 180 } 181 182 // Close the device wrapped. 183 func (cw *CobsWrapper) Close() error { 184 return cw.dev.Close() 185 }