github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/stringlib/packer.go (about) 1 package stringlib 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "math" 7 "strings" 8 9 rt "github.com/arnodel/golua/runtime" 10 ) 11 12 type packer struct { 13 packFormatReader 14 values []rt.Value // Lua values to be packed 15 j int // Current index in the values above 16 val rt.Value // Current value 17 intVal int64 // Current integral value (if applicable) 18 floatVal float64 // Current floating point value (if applicable) 19 strVal string // Current string value (if applicable) 20 w bytes.Buffer // Where the output is written 21 22 budget uint64 // Number of bytes we are allowed to write before stopping (0 means unbounded) 23 used uint64 // Number of bytes written so far 24 } 25 26 func PackValues(format string, values []rt.Value, budget uint64) (string, uint64, error) { 27 p := &packer{ 28 packFormatReader: packFormatReader{ 29 format: format, 30 byteOrder: nativeEndian, 31 maxAlignment: defaultMaxAlignement, 32 }, 33 values: values, 34 budget: budget, 35 } 36 for p.hasNext() { 37 switch c := p.nextOption(); c { 38 case '<': 39 p.byteOrder = binary.LittleEndian 40 case '>': 41 p.byteOrder = binary.BigEndian 42 case '=': 43 p.byteOrder = nativeEndian 44 case '!': 45 if p.smallOptSize(defaultMaxAlignement) { 46 p.maxAlignment = p.optSize 47 } 48 case 'b': 49 _ = p.align(0) && 50 p.nextIntValue() && 51 p.checkBounds(math.MinInt8, math.MaxInt8) && 52 p.write(1, int8(p.intVal)) 53 case 'B': 54 _ = p.align(0) && 55 p.nextIntValue() && 56 p.checkBounds(0, math.MaxUint8) && 57 p.write(1, uint8(p.intVal)) 58 case 'h': 59 _ = p.align(2) && 60 p.nextIntValue() && 61 p.checkBounds(math.MinInt16, math.MaxInt16) && 62 p.write(2, int16(p.intVal)) 63 case 'H': 64 _ = p.align(2) && 65 p.nextIntValue() && 66 p.checkBounds(0, math.MaxUint16) && 67 p.write(2, uint16(p.intVal)) 68 case 'l', 'j': 69 _ = p.align(8) && 70 p.nextIntValue() && 71 p.write(8, p.intVal) 72 case 'L', 'J', 'T': 73 _ = p.align(8) && 74 p.nextIntValue() && 75 p.checkBounds(0, math.MaxInt64) && 76 p.write(8, uint64(p.intVal)) 77 case 'i': 78 _ = p.smallOptSize(8) && 79 p.align(p.optSize) && 80 p.nextIntValue() && 81 p.packInt() 82 case 'I': 83 _ = p.smallOptSize(8) && 84 p.align(p.optSize) && 85 p.nextIntValue() && 86 p.packUint() 87 case 'f': 88 _ = p.align(4) && 89 p.nextFloatValue() && 90 p.checkFloatSize(math.MaxFloat32) && 91 p.write(4, float32(p.floatVal)) 92 case 'd', 'n': 93 _ = p.align(8) && 94 p.nextFloatValue() && 95 p.write(8, p.floatVal) 96 case 'c': 97 _ = p.align(0) && 98 p.mustGetOptSize() && 99 p.nextStringValue() && 100 p.writeStr(p.optSize) 101 case 'z': 102 if p.align(0) && p.nextStringValue() { 103 if strings.IndexByte(p.strVal, 0) >= 0 { 104 p.err = errStringContainsZeros 105 } else { 106 107 _ = p.writeStr(0) && 108 p.writeByte(0) 109 } 110 } 111 case 's': 112 _ = p.smallOptSize(8) && 113 p.align(p.optSize) && 114 p.nextStringValue() && 115 p.packUint() && 116 p.writeStr(0) 117 if p.err == errOutOfBounds { 118 p.err = errStringDoesNotFit 119 } 120 case 'x': 121 _ = p.align(0) && 122 p.writeByte(0) 123 case 'X': 124 p.alignOnly = true 125 case ' ': 126 // ignored 127 default: 128 p.err = errBadFormatString(c) 129 } 130 if p.err != nil { 131 return "", p.used, p.err 132 } 133 } 134 if p.alignOnly { 135 return "", p.used, errExpectedOption 136 } 137 return p.w.String(), p.used, nil 138 } 139 140 func (p *packer) nextValue() bool { 141 if len(p.values) > p.j { 142 p.val = p.values[p.j] 143 p.j++ 144 return true 145 } 146 p.err = errNotEnoughValues 147 return false 148 } 149 150 func (p *packer) nextIntValue() bool { 151 if !p.nextValue() { 152 return false 153 } 154 n, ok := rt.ToInt(p.val) 155 if !ok { 156 p.err = errBadType 157 return false 158 } 159 p.intVal = int64(n) 160 return true 161 } 162 163 func (p *packer) nextFloatValue() bool { 164 if !p.nextValue() { 165 return false 166 } 167 f, ok := rt.ToFloat(p.val) 168 if !ok { 169 p.err = errBadType 170 return false 171 } 172 p.floatVal = float64(f) 173 return true 174 } 175 176 func (p *packer) nextStringValue() bool { 177 if !p.nextValue() { 178 return false 179 } 180 s, ok := p.val.ToString() 181 if !ok { 182 p.err = errBadType 183 return false 184 } 185 p.strVal = string(s) 186 p.intVal = int64(len(s)) 187 return true 188 } 189 190 func (p *packer) checkBounds(min, max int64) bool { 191 ok := p.intVal >= min && p.intVal <= max 192 if !ok { 193 p.err = errOutOfBounds 194 } 195 return ok 196 } 197 198 func (p *packer) checkFloatSize(max float64) bool { 199 ok := (p.floatVal >= -max && p.floatVal <= max) || math.IsInf(p.floatVal, 0) 200 if !ok { 201 p.err = errOutOfBounds 202 } 203 return ok 204 } 205 206 func (p *packer) writeByte(b byte) bool { 207 p.w.WriteByte(b) 208 return true 209 } 210 211 func (p *packer) write(amount uint64, x interface{}) bool { 212 if !p.consumeBudget(amount) { 213 return false 214 } 215 p.err = binary.Write(&p.w, p.byteOrder, x) 216 return p.err == nil 217 } 218 219 func (p *packer) consumeBudget(amount uint64) bool { 220 if p.budget == 0 { 221 return true 222 } 223 p.used += amount 224 if p.used > p.budget { 225 p.err = errBudgetConsumed 226 p.used = p.budget 227 return false 228 } 229 return true 230 } 231 232 func (p *packer) writeStr(maxLen uint) bool { 233 diff := 0 234 if maxLen > 0 { 235 diff = int(maxLen) - len(p.strVal) 236 } 237 if diff < 0 { 238 p.err = errStringLongerThanFormat 239 return false 240 } 241 if !p.consumeBudget(uint64(len(p.strVal))) { 242 return false 243 } 244 p.w.Write([]byte(p.strVal)) 245 if diff > 0 { 246 return p.fill(uint(diff), 0) 247 } 248 return true 249 } 250 251 func (p *packer) align(n uint) bool { 252 if n != 0 { 253 if n > p.maxAlignment { 254 n = p.maxAlignment 255 } 256 if (n-1)&n != 0 { // (n-1)&n == 0 iff n is a power of 2 (or 0) 257 p.err = errBadAlignment 258 return false 259 } 260 if r := uint(p.w.Len()) % n; r != 0 { 261 if !p.fill(n-r, 0) { 262 return false 263 } 264 } 265 } 266 if p.alignOnly { 267 p.alignOnly = false 268 return false 269 } 270 return true 271 } 272 273 func (p *packer) fill(n uint, c byte) bool { 274 if !p.consumeBudget(uint64(n)) { 275 return false 276 } 277 for ; n > 0; n-- { 278 p.w.WriteByte(c) 279 } 280 return true 281 } 282 283 func (p *packer) packInt() bool { 284 switch n := p.optSize; { 285 case n == 4: 286 // It's an int32 287 return p.checkBounds(math.MinInt32, math.MaxInt32) && p.write(4, int32(p.intVal)) 288 case n == 8: 289 // It's an int64 290 return p.write(8, p.intVal) 291 case n >= 8: 292 // Pad to make up the length 293 var fill byte 294 if p.intVal < 0 { 295 fill = 255 296 } 297 if p.byteOrder == binary.BigEndian { 298 if !p.fill(n-8, fill) { 299 return false 300 } 301 } 302 if !p.write(8, p.intVal) { 303 return false 304 } 305 if p.byteOrder == binary.LittleEndian { 306 if !p.fill(n-8, fill) { 307 return false 308 } 309 } 310 default: 311 // n < 8 so truncate 312 max := int64(1) << (n<<3 - 1) 313 if !p.checkBounds(-max, max-1) { 314 return false 315 } 316 var ww bytes.Buffer 317 if err := binary.Write(&ww, p.byteOrder, p.intVal); err != nil { 318 p.err = err 319 return false 320 } 321 switch p.byteOrder { 322 case binary.LittleEndian: 323 p.w.Write(ww.Bytes()[:n]) 324 default: 325 p.w.Write(ww.Bytes()[8-n:]) 326 } 327 } 328 return true 329 } 330 331 func (p *packer) packUint() bool { 332 switch n := p.optSize; { 333 case n == 4: 334 // It's an uint32 335 return p.checkBounds(0, math.MaxUint32) && p.write(4, uint32(p.intVal)) 336 case n == 8: 337 // It's an uint64 338 return p.checkBounds(0, math.MaxInt64) && p.write(8, uint64(p.intVal)) 339 case n > 8: 340 // Pad to make up the length 341 if p.byteOrder == binary.BigEndian { 342 if !p.fill(n-8, 0) { 343 return false 344 } 345 } 346 if !p.write(8, uint64(p.intVal)) { 347 return false 348 } 349 if p.byteOrder == binary.LittleEndian { 350 if !p.fill(n-8, 0) { 351 return false 352 } 353 } 354 default: 355 // n < 8 so truncate 356 max := int64(1) << (n << 3) 357 if !p.checkBounds(0, max-1) { 358 return false 359 } 360 var ww bytes.Buffer 361 if err := binary.Write(&ww, p.byteOrder, uint64(p.intVal)); err != nil { 362 p.err = err 363 return false 364 } 365 switch p.byteOrder { 366 case binary.LittleEndian: 367 p.w.Write(ww.Bytes()[:n]) 368 default: 369 p.w.Write(ww.Bytes()[8-n:]) 370 } 371 } 372 return true 373 }