diff options
Diffstat (limited to 'vendor/github.com/klauspost/compress/s2/index.go')
| -rw-r--r-- | vendor/github.com/klauspost/compress/s2/index.go | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/vendor/github.com/klauspost/compress/s2/index.go b/vendor/github.com/klauspost/compress/s2/index.go new file mode 100644 index 0000000..18a4f7a --- /dev/null +++ b/vendor/github.com/klauspost/compress/s2/index.go | |||
| @@ -0,0 +1,596 @@ | |||
| 1 | // Copyright (c) 2022+ Klaus Post. All rights reserved. | ||
| 2 | // Use of this source code is governed by a BSD-style | ||
| 3 | // license that can be found in the LICENSE file. | ||
| 4 | |||
| 5 | package s2 | ||
| 6 | |||
| 7 | import ( | ||
| 8 | "bytes" | ||
| 9 | "encoding/binary" | ||
| 10 | "encoding/json" | ||
| 11 | "fmt" | ||
| 12 | "io" | ||
| 13 | "sort" | ||
| 14 | ) | ||
| 15 | |||
| 16 | const ( | ||
| 17 | S2IndexHeader = "s2idx\x00" | ||
| 18 | S2IndexTrailer = "\x00xdi2s" | ||
| 19 | maxIndexEntries = 1 << 16 | ||
| 20 | ) | ||
| 21 | |||
| 22 | // Index represents an S2/Snappy index. | ||
| 23 | type Index struct { | ||
| 24 | TotalUncompressed int64 // Total Uncompressed size if known. Will be -1 if unknown. | ||
| 25 | TotalCompressed int64 // Total Compressed size if known. Will be -1 if unknown. | ||
| 26 | info []struct { | ||
| 27 | compressedOffset int64 | ||
| 28 | uncompressedOffset int64 | ||
| 29 | } | ||
| 30 | estBlockUncomp int64 | ||
| 31 | } | ||
| 32 | |||
| 33 | func (i *Index) reset(maxBlock int) { | ||
| 34 | i.estBlockUncomp = int64(maxBlock) | ||
| 35 | i.TotalCompressed = -1 | ||
| 36 | i.TotalUncompressed = -1 | ||
| 37 | if len(i.info) > 0 { | ||
| 38 | i.info = i.info[:0] | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | // allocInfos will allocate an empty slice of infos. | ||
| 43 | func (i *Index) allocInfos(n int) { | ||
| 44 | if n > maxIndexEntries { | ||
| 45 | panic("n > maxIndexEntries") | ||
| 46 | } | ||
| 47 | i.info = make([]struct { | ||
| 48 | compressedOffset int64 | ||
| 49 | uncompressedOffset int64 | ||
| 50 | }, 0, n) | ||
| 51 | } | ||
| 52 | |||
| 53 | // add an uncompressed and compressed pair. | ||
| 54 | // Entries must be sent in order. | ||
| 55 | func (i *Index) add(compressedOffset, uncompressedOffset int64) error { | ||
| 56 | if i == nil { | ||
| 57 | return nil | ||
| 58 | } | ||
| 59 | lastIdx := len(i.info) - 1 | ||
| 60 | if lastIdx >= 0 { | ||
| 61 | latest := i.info[lastIdx] | ||
| 62 | if latest.uncompressedOffset == uncompressedOffset { | ||
| 63 | // Uncompressed didn't change, don't add entry, | ||
| 64 | // but update start index. | ||
| 65 | latest.compressedOffset = compressedOffset | ||
| 66 | i.info[lastIdx] = latest | ||
| 67 | return nil | ||
| 68 | } | ||
| 69 | if latest.uncompressedOffset > uncompressedOffset { | ||
| 70 | return fmt.Errorf("internal error: Earlier uncompressed received (%d > %d)", latest.uncompressedOffset, uncompressedOffset) | ||
| 71 | } | ||
| 72 | if latest.compressedOffset > compressedOffset { | ||
| 73 | return fmt.Errorf("internal error: Earlier compressed received (%d > %d)", latest.uncompressedOffset, uncompressedOffset) | ||
| 74 | } | ||
| 75 | } | ||
| 76 | i.info = append(i.info, struct { | ||
| 77 | compressedOffset int64 | ||
| 78 | uncompressedOffset int64 | ||
| 79 | }{compressedOffset: compressedOffset, uncompressedOffset: uncompressedOffset}) | ||
| 80 | return nil | ||
| 81 | } | ||
| 82 | |||
| 83 | // Find the offset at or before the wanted (uncompressed) offset. | ||
| 84 | // If offset is 0 or positive it is the offset from the beginning of the file. | ||
| 85 | // If the uncompressed size is known, the offset must be within the file. | ||
| 86 | // If an offset outside the file is requested io.ErrUnexpectedEOF is returned. | ||
| 87 | // If the offset is negative, it is interpreted as the distance from the end of the file, | ||
| 88 | // where -1 represents the last byte. | ||
| 89 | // If offset from the end of the file is requested, but size is unknown, | ||
| 90 | // ErrUnsupported will be returned. | ||
| 91 | func (i *Index) Find(offset int64) (compressedOff, uncompressedOff int64, err error) { | ||
| 92 | if i.TotalUncompressed < 0 { | ||
| 93 | return 0, 0, ErrCorrupt | ||
| 94 | } | ||
| 95 | if offset < 0 { | ||
| 96 | offset = i.TotalUncompressed + offset | ||
| 97 | if offset < 0 { | ||
| 98 | return 0, 0, io.ErrUnexpectedEOF | ||
| 99 | } | ||
| 100 | } | ||
| 101 | if offset > i.TotalUncompressed { | ||
| 102 | return 0, 0, io.ErrUnexpectedEOF | ||
| 103 | } | ||
| 104 | if len(i.info) > 200 { | ||
| 105 | n := sort.Search(len(i.info), func(n int) bool { | ||
| 106 | return i.info[n].uncompressedOffset > offset | ||
| 107 | }) | ||
| 108 | if n == 0 { | ||
| 109 | n = 1 | ||
| 110 | } | ||
| 111 | return i.info[n-1].compressedOffset, i.info[n-1].uncompressedOffset, nil | ||
| 112 | } | ||
| 113 | for _, info := range i.info { | ||
| 114 | if info.uncompressedOffset > offset { | ||
| 115 | break | ||
| 116 | } | ||
| 117 | compressedOff = info.compressedOffset | ||
| 118 | uncompressedOff = info.uncompressedOffset | ||
| 119 | } | ||
| 120 | return compressedOff, uncompressedOff, nil | ||
| 121 | } | ||
| 122 | |||
| 123 | // reduce to stay below maxIndexEntries | ||
| 124 | func (i *Index) reduce() { | ||
| 125 | if len(i.info) < maxIndexEntries && i.estBlockUncomp >= 1<<20 { | ||
| 126 | return | ||
| 127 | } | ||
| 128 | |||
| 129 | // Algorithm, keep 1, remove removeN entries... | ||
| 130 | removeN := (len(i.info) + 1) / maxIndexEntries | ||
| 131 | src := i.info | ||
| 132 | j := 0 | ||
| 133 | |||
| 134 | // Each block should be at least 1MB, but don't reduce below 1000 entries. | ||
| 135 | for i.estBlockUncomp*(int64(removeN)+1) < 1<<20 && len(i.info)/(removeN+1) > 1000 { | ||
| 136 | removeN++ | ||
| 137 | } | ||
| 138 | for idx := 0; idx < len(src); idx++ { | ||
| 139 | i.info[j] = src[idx] | ||
| 140 | j++ | ||
| 141 | idx += removeN | ||
| 142 | } | ||
| 143 | i.info = i.info[:j] | ||
| 144 | // Update maxblock estimate. | ||
| 145 | i.estBlockUncomp += i.estBlockUncomp * int64(removeN) | ||
| 146 | } | ||
| 147 | |||
| 148 | func (i *Index) appendTo(b []byte, uncompTotal, compTotal int64) []byte { | ||
| 149 | i.reduce() | ||
| 150 | var tmp [binary.MaxVarintLen64]byte | ||
| 151 | |||
| 152 | initSize := len(b) | ||
| 153 | // We make the start a skippable header+size. | ||
| 154 | b = append(b, ChunkTypeIndex, 0, 0, 0) | ||
| 155 | b = append(b, []byte(S2IndexHeader)...) | ||
| 156 | // Total Uncompressed size | ||
| 157 | n := binary.PutVarint(tmp[:], uncompTotal) | ||
| 158 | b = append(b, tmp[:n]...) | ||
| 159 | // Total Compressed size | ||
| 160 | n = binary.PutVarint(tmp[:], compTotal) | ||
| 161 | b = append(b, tmp[:n]...) | ||
| 162 | // Put EstBlockUncomp size | ||
| 163 | n = binary.PutVarint(tmp[:], i.estBlockUncomp) | ||
| 164 | b = append(b, tmp[:n]...) | ||
| 165 | // Put length | ||
| 166 | n = binary.PutVarint(tmp[:], int64(len(i.info))) | ||
| 167 | b = append(b, tmp[:n]...) | ||
| 168 | |||
| 169 | // Check if we should add uncompressed offsets | ||
| 170 | var hasUncompressed byte | ||
| 171 | for idx, info := range i.info { | ||
| 172 | if idx == 0 { | ||
| 173 | if info.uncompressedOffset != 0 { | ||
| 174 | hasUncompressed = 1 | ||
| 175 | break | ||
| 176 | } | ||
| 177 | continue | ||
| 178 | } | ||
| 179 | if info.uncompressedOffset != i.info[idx-1].uncompressedOffset+i.estBlockUncomp { | ||
| 180 | hasUncompressed = 1 | ||
| 181 | break | ||
| 182 | } | ||
| 183 | } | ||
| 184 | b = append(b, hasUncompressed) | ||
| 185 | |||
| 186 | // Add each entry | ||
| 187 | if hasUncompressed == 1 { | ||
| 188 | for idx, info := range i.info { | ||
| 189 | uOff := info.uncompressedOffset | ||
| 190 | if idx > 0 { | ||
| 191 | prev := i.info[idx-1] | ||
| 192 | uOff -= prev.uncompressedOffset + (i.estBlockUncomp) | ||
| 193 | } | ||
| 194 | n = binary.PutVarint(tmp[:], uOff) | ||
| 195 | b = append(b, tmp[:n]...) | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | // Initial compressed size estimate. | ||
| 200 | cPredict := i.estBlockUncomp / 2 | ||
| 201 | |||
| 202 | for idx, info := range i.info { | ||
| 203 | cOff := info.compressedOffset | ||
| 204 | if idx > 0 { | ||
| 205 | prev := i.info[idx-1] | ||
| 206 | cOff -= prev.compressedOffset + cPredict | ||
| 207 | // Update compressed size prediction, with half the error. | ||
| 208 | cPredict += cOff / 2 | ||
| 209 | } | ||
| 210 | n = binary.PutVarint(tmp[:], cOff) | ||
| 211 | b = append(b, tmp[:n]...) | ||
| 212 | } | ||
| 213 | |||
| 214 | // Add Total Size. | ||
| 215 | // Stored as fixed size for easier reading. | ||
| 216 | binary.LittleEndian.PutUint32(tmp[:], uint32(len(b)-initSize+4+len(S2IndexTrailer))) | ||
| 217 | b = append(b, tmp[:4]...) | ||
| 218 | // Trailer | ||
| 219 | b = append(b, []byte(S2IndexTrailer)...) | ||
| 220 | |||
| 221 | // Update size | ||
| 222 | chunkLen := len(b) - initSize - skippableFrameHeader | ||
| 223 | b[initSize+1] = uint8(chunkLen >> 0) | ||
| 224 | b[initSize+2] = uint8(chunkLen >> 8) | ||
| 225 | b[initSize+3] = uint8(chunkLen >> 16) | ||
| 226 | //fmt.Printf("chunklen: 0x%x Uncomp:%d, Comp:%d\n", chunkLen, uncompTotal, compTotal) | ||
| 227 | return b | ||
| 228 | } | ||
| 229 | |||
| 230 | // Load a binary index. | ||
| 231 | // A zero value Index can be used or a previous one can be reused. | ||
| 232 | func (i *Index) Load(b []byte) ([]byte, error) { | ||
| 233 | if len(b) <= 4+len(S2IndexHeader)+len(S2IndexTrailer) { | ||
| 234 | return b, io.ErrUnexpectedEOF | ||
| 235 | } | ||
| 236 | if b[0] != ChunkTypeIndex { | ||
| 237 | return b, ErrCorrupt | ||
| 238 | } | ||
| 239 | chunkLen := int(b[1]) | int(b[2])<<8 | int(b[3])<<16 | ||
| 240 | b = b[4:] | ||
| 241 | |||
| 242 | // Validate we have enough... | ||
| 243 | if len(b) < chunkLen { | ||
| 244 | return b, io.ErrUnexpectedEOF | ||
| 245 | } | ||
| 246 | if !bytes.Equal(b[:len(S2IndexHeader)], []byte(S2IndexHeader)) { | ||
| 247 | return b, ErrUnsupported | ||
| 248 | } | ||
| 249 | b = b[len(S2IndexHeader):] | ||
| 250 | |||
| 251 | // Total Uncompressed | ||
| 252 | if v, n := binary.Varint(b); n <= 0 || v < 0 { | ||
| 253 | return b, ErrCorrupt | ||
| 254 | } else { | ||
| 255 | i.TotalUncompressed = v | ||
| 256 | b = b[n:] | ||
| 257 | } | ||
| 258 | |||
| 259 | // Total Compressed | ||
| 260 | if v, n := binary.Varint(b); n <= 0 { | ||
| 261 | return b, ErrCorrupt | ||
| 262 | } else { | ||
| 263 | i.TotalCompressed = v | ||
| 264 | b = b[n:] | ||
| 265 | } | ||
| 266 | |||
| 267 | // Read EstBlockUncomp | ||
| 268 | if v, n := binary.Varint(b); n <= 0 { | ||
| 269 | return b, ErrCorrupt | ||
| 270 | } else { | ||
| 271 | if v < 0 { | ||
| 272 | return b, ErrCorrupt | ||
| 273 | } | ||
| 274 | i.estBlockUncomp = v | ||
| 275 | b = b[n:] | ||
| 276 | } | ||
| 277 | |||
| 278 | var entries int | ||
| 279 | if v, n := binary.Varint(b); n <= 0 { | ||
| 280 | return b, ErrCorrupt | ||
| 281 | } else { | ||
| 282 | if v < 0 || v > maxIndexEntries { | ||
| 283 | return b, ErrCorrupt | ||
| 284 | } | ||
| 285 | entries = int(v) | ||
| 286 | b = b[n:] | ||
| 287 | } | ||
| 288 | if cap(i.info) < entries { | ||
| 289 | i.allocInfos(entries) | ||
| 290 | } | ||
| 291 | i.info = i.info[:entries] | ||
| 292 | |||
| 293 | if len(b) < 1 { | ||
| 294 | return b, io.ErrUnexpectedEOF | ||
| 295 | } | ||
| 296 | hasUncompressed := b[0] | ||
| 297 | b = b[1:] | ||
| 298 | if hasUncompressed&1 != hasUncompressed { | ||
| 299 | return b, ErrCorrupt | ||
| 300 | } | ||
| 301 | |||
| 302 | // Add each uncompressed entry | ||
| 303 | for idx := range i.info { | ||
| 304 | var uOff int64 | ||
| 305 | if hasUncompressed != 0 { | ||
| 306 | // Load delta | ||
| 307 | if v, n := binary.Varint(b); n <= 0 { | ||
| 308 | return b, ErrCorrupt | ||
| 309 | } else { | ||
| 310 | uOff = v | ||
| 311 | b = b[n:] | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | if idx > 0 { | ||
| 316 | prev := i.info[idx-1].uncompressedOffset | ||
| 317 | uOff += prev + (i.estBlockUncomp) | ||
| 318 | if uOff <= prev { | ||
| 319 | return b, ErrCorrupt | ||
| 320 | } | ||
| 321 | } | ||
| 322 | if uOff < 0 { | ||
| 323 | return b, ErrCorrupt | ||
| 324 | } | ||
| 325 | i.info[idx].uncompressedOffset = uOff | ||
| 326 | } | ||
| 327 | |||
| 328 | // Initial compressed size estimate. | ||
| 329 | cPredict := i.estBlockUncomp / 2 | ||
| 330 | |||
| 331 | // Add each compressed entry | ||
| 332 | for idx := range i.info { | ||
| 333 | var cOff int64 | ||
| 334 | if v, n := binary.Varint(b); n <= 0 { | ||
| 335 | return b, ErrCorrupt | ||
| 336 | } else { | ||
| 337 | cOff = v | ||
| 338 | b = b[n:] | ||
| 339 | } | ||
| 340 | |||
| 341 | if idx > 0 { | ||
| 342 | // Update compressed size prediction, with half the error. | ||
| 343 | cPredictNew := cPredict + cOff/2 | ||
| 344 | |||
| 345 | prev := i.info[idx-1].compressedOffset | ||
| 346 | cOff += prev + cPredict | ||
| 347 | if cOff <= prev { | ||
| 348 | return b, ErrCorrupt | ||
| 349 | } | ||
| 350 | cPredict = cPredictNew | ||
| 351 | } | ||
| 352 | if cOff < 0 { | ||
| 353 | return b, ErrCorrupt | ||
| 354 | } | ||
| 355 | i.info[idx].compressedOffset = cOff | ||
| 356 | } | ||
| 357 | if len(b) < 4+len(S2IndexTrailer) { | ||
| 358 | return b, io.ErrUnexpectedEOF | ||
| 359 | } | ||
| 360 | // Skip size... | ||
| 361 | b = b[4:] | ||
| 362 | |||
| 363 | // Check trailer... | ||
| 364 | if !bytes.Equal(b[:len(S2IndexTrailer)], []byte(S2IndexTrailer)) { | ||
| 365 | return b, ErrCorrupt | ||
| 366 | } | ||
| 367 | return b[len(S2IndexTrailer):], nil | ||
| 368 | } | ||
| 369 | |||
| 370 | // LoadStream will load an index from the end of the supplied stream. | ||
| 371 | // ErrUnsupported will be returned if the signature cannot be found. | ||
| 372 | // ErrCorrupt will be returned if unexpected values are found. | ||
| 373 | // io.ErrUnexpectedEOF is returned if there are too few bytes. | ||
| 374 | // IO errors are returned as-is. | ||
| 375 | func (i *Index) LoadStream(rs io.ReadSeeker) error { | ||
| 376 | // Go to end. | ||
| 377 | _, err := rs.Seek(-10, io.SeekEnd) | ||
| 378 | if err != nil { | ||
| 379 | return err | ||
| 380 | } | ||
| 381 | var tmp [10]byte | ||
| 382 | _, err = io.ReadFull(rs, tmp[:]) | ||
| 383 | if err != nil { | ||
| 384 | return err | ||
| 385 | } | ||
| 386 | // Check trailer... | ||
| 387 | if !bytes.Equal(tmp[4:4+len(S2IndexTrailer)], []byte(S2IndexTrailer)) { | ||
| 388 | return ErrUnsupported | ||
| 389 | } | ||
| 390 | sz := binary.LittleEndian.Uint32(tmp[:4]) | ||
| 391 | if sz > maxChunkSize+skippableFrameHeader { | ||
| 392 | return ErrCorrupt | ||
| 393 | } | ||
| 394 | _, err = rs.Seek(-int64(sz), io.SeekEnd) | ||
| 395 | if err != nil { | ||
| 396 | return err | ||
| 397 | } | ||
| 398 | |||
| 399 | // Read index. | ||
| 400 | buf := make([]byte, sz) | ||
| 401 | _, err = io.ReadFull(rs, buf) | ||
| 402 | if err != nil { | ||
| 403 | return err | ||
| 404 | } | ||
| 405 | _, err = i.Load(buf) | ||
| 406 | return err | ||
| 407 | } | ||
| 408 | |||
| 409 | // IndexStream will return an index for a stream. | ||
| 410 | // The stream structure will be checked, but | ||
| 411 | // data within blocks is not verified. | ||
| 412 | // The returned index can either be appended to the end of the stream | ||
| 413 | // or stored separately. | ||
| 414 | func IndexStream(r io.Reader) ([]byte, error) { | ||
| 415 | var i Index | ||
| 416 | var buf [maxChunkSize]byte | ||
| 417 | var readHeader bool | ||
| 418 | for { | ||
| 419 | _, err := io.ReadFull(r, buf[:4]) | ||
| 420 | if err != nil { | ||
| 421 | if err == io.EOF { | ||
| 422 | return i.appendTo(nil, i.TotalUncompressed, i.TotalCompressed), nil | ||
| 423 | } | ||
| 424 | return nil, err | ||
| 425 | } | ||
| 426 | // Start of this chunk. | ||
| 427 | startChunk := i.TotalCompressed | ||
| 428 | i.TotalCompressed += 4 | ||
| 429 | |||
| 430 | chunkType := buf[0] | ||
| 431 | if !readHeader { | ||
| 432 | if chunkType != chunkTypeStreamIdentifier { | ||
| 433 | return nil, ErrCorrupt | ||
| 434 | } | ||
| 435 | readHeader = true | ||
| 436 | } | ||
| 437 | chunkLen := int(buf[1]) | int(buf[2])<<8 | int(buf[3])<<16 | ||
| 438 | if chunkLen < checksumSize { | ||
| 439 | return nil, ErrCorrupt | ||
| 440 | } | ||
| 441 | |||
| 442 | i.TotalCompressed += int64(chunkLen) | ||
| 443 | _, err = io.ReadFull(r, buf[:chunkLen]) | ||
| 444 | if err != nil { | ||
| 445 | return nil, io.ErrUnexpectedEOF | ||
| 446 | } | ||
| 447 | // The chunk types are specified at | ||
| 448 | // https://github.com/google/snappy/blob/master/framing_format.txt | ||
| 449 | switch chunkType { | ||
| 450 | case chunkTypeCompressedData: | ||
| 451 | // Section 4.2. Compressed data (chunk type 0x00). | ||
| 452 | // Skip checksum. | ||
| 453 | dLen, err := DecodedLen(buf[checksumSize:]) | ||
| 454 | if err != nil { | ||
| 455 | return nil, err | ||
| 456 | } | ||
| 457 | if dLen > maxBlockSize { | ||
| 458 | return nil, ErrCorrupt | ||
| 459 | } | ||
| 460 | if i.estBlockUncomp == 0 { | ||
| 461 | // Use first block for estimate... | ||
| 462 | i.estBlockUncomp = int64(dLen) | ||
| 463 | } | ||
| 464 | err = i.add(startChunk, i.TotalUncompressed) | ||
| 465 | if err != nil { | ||
| 466 | return nil, err | ||
| 467 | } | ||
| 468 | i.TotalUncompressed += int64(dLen) | ||
| 469 | continue | ||
| 470 | case chunkTypeUncompressedData: | ||
| 471 | n2 := chunkLen - checksumSize | ||
| 472 | if n2 > maxBlockSize { | ||
| 473 | return nil, ErrCorrupt | ||
| 474 | } | ||
| 475 | if i.estBlockUncomp == 0 { | ||
| 476 | // Use first block for estimate... | ||
| 477 | i.estBlockUncomp = int64(n2) | ||
| 478 | } | ||
| 479 | err = i.add(startChunk, i.TotalUncompressed) | ||
| 480 | if err != nil { | ||
| 481 | return nil, err | ||
| 482 | } | ||
| 483 | i.TotalUncompressed += int64(n2) | ||
| 484 | continue | ||
| 485 | case chunkTypeStreamIdentifier: | ||
| 486 | // Section 4.1. Stream identifier (chunk type 0xff). | ||
| 487 | if chunkLen != len(magicBody) { | ||
| 488 | return nil, ErrCorrupt | ||
| 489 | } | ||
| 490 | |||
| 491 | if string(buf[:len(magicBody)]) != magicBody { | ||
| 492 | if string(buf[:len(magicBody)]) != magicBodySnappy { | ||
| 493 | return nil, ErrCorrupt | ||
| 494 | } | ||
| 495 | } | ||
| 496 | |||
| 497 | continue | ||
| 498 | } | ||
| 499 | |||
| 500 | if chunkType <= 0x7f { | ||
| 501 | // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). | ||
| 502 | return nil, ErrUnsupported | ||
| 503 | } | ||
| 504 | if chunkLen > maxChunkSize { | ||
| 505 | return nil, ErrUnsupported | ||
| 506 | } | ||
| 507 | // Section 4.4 Padding (chunk type 0xfe). | ||
| 508 | // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | // JSON returns the index as JSON text. | ||
| 513 | func (i *Index) JSON() []byte { | ||
| 514 | type offset struct { | ||
| 515 | CompressedOffset int64 `json:"compressed"` | ||
| 516 | UncompressedOffset int64 `json:"uncompressed"` | ||
| 517 | } | ||
| 518 | x := struct { | ||
| 519 | TotalUncompressed int64 `json:"total_uncompressed"` // Total Uncompressed size if known. Will be -1 if unknown. | ||
| 520 | TotalCompressed int64 `json:"total_compressed"` // Total Compressed size if known. Will be -1 if unknown. | ||
| 521 | Offsets []offset `json:"offsets"` | ||
| 522 | EstBlockUncomp int64 `json:"est_block_uncompressed"` | ||
| 523 | }{ | ||
| 524 | TotalUncompressed: i.TotalUncompressed, | ||
| 525 | TotalCompressed: i.TotalCompressed, | ||
| 526 | EstBlockUncomp: i.estBlockUncomp, | ||
| 527 | } | ||
| 528 | for _, v := range i.info { | ||
| 529 | x.Offsets = append(x.Offsets, offset{CompressedOffset: v.compressedOffset, UncompressedOffset: v.uncompressedOffset}) | ||
| 530 | } | ||
| 531 | b, _ := json.MarshalIndent(x, "", " ") | ||
| 532 | return b | ||
| 533 | } | ||
| 534 | |||
| 535 | // RemoveIndexHeaders will trim all headers and trailers from a given index. | ||
| 536 | // This is expected to save 20 bytes. | ||
| 537 | // These can be restored using RestoreIndexHeaders. | ||
| 538 | // This removes a layer of security, but is the most compact representation. | ||
| 539 | // Returns nil if headers contains errors. | ||
| 540 | // The returned slice references the provided slice. | ||
| 541 | func RemoveIndexHeaders(b []byte) []byte { | ||
| 542 | const save = 4 + len(S2IndexHeader) + len(S2IndexTrailer) + 4 | ||
| 543 | if len(b) <= save { | ||
| 544 | return nil | ||
| 545 | } | ||
| 546 | if b[0] != ChunkTypeIndex { | ||
| 547 | return nil | ||
| 548 | } | ||
| 549 | chunkLen := int(b[1]) | int(b[2])<<8 | int(b[3])<<16 | ||
| 550 | b = b[4:] | ||
| 551 | |||
| 552 | // Validate we have enough... | ||
| 553 | if len(b) < chunkLen { | ||
| 554 | return nil | ||
| 555 | } | ||
| 556 | b = b[:chunkLen] | ||
| 557 | |||
| 558 | if !bytes.Equal(b[:len(S2IndexHeader)], []byte(S2IndexHeader)) { | ||
| 559 | return nil | ||
| 560 | } | ||
| 561 | b = b[len(S2IndexHeader):] | ||
| 562 | if !bytes.HasSuffix(b, []byte(S2IndexTrailer)) { | ||
| 563 | return nil | ||
| 564 | } | ||
| 565 | b = bytes.TrimSuffix(b, []byte(S2IndexTrailer)) | ||
| 566 | |||
| 567 | if len(b) < 4 { | ||
| 568 | return nil | ||
| 569 | } | ||
| 570 | return b[:len(b)-4] | ||
| 571 | } | ||
| 572 | |||
| 573 | // RestoreIndexHeaders will index restore headers removed by RemoveIndexHeaders. | ||
| 574 | // No error checking is performed on the input. | ||
| 575 | // If a 0 length slice is sent, it is returned without modification. | ||
| 576 | func RestoreIndexHeaders(in []byte) []byte { | ||
| 577 | if len(in) == 0 { | ||
| 578 | return in | ||
| 579 | } | ||
| 580 | b := make([]byte, 0, 4+len(S2IndexHeader)+len(in)+len(S2IndexTrailer)+4) | ||
| 581 | b = append(b, ChunkTypeIndex, 0, 0, 0) | ||
| 582 | b = append(b, []byte(S2IndexHeader)...) | ||
| 583 | b = append(b, in...) | ||
| 584 | |||
| 585 | var tmp [4]byte | ||
| 586 | binary.LittleEndian.PutUint32(tmp[:], uint32(len(b)+4+len(S2IndexTrailer))) | ||
| 587 | b = append(b, tmp[:4]...) | ||
| 588 | // Trailer | ||
| 589 | b = append(b, []byte(S2IndexTrailer)...) | ||
| 590 | |||
| 591 | chunkLen := len(b) - skippableFrameHeader | ||
| 592 | b[1] = uint8(chunkLen >> 0) | ||
| 593 | b[2] = uint8(chunkLen >> 8) | ||
| 594 | b[3] = uint8(chunkLen >> 16) | ||
| 595 | return b | ||
| 596 | } | ||