github.com/coyove/sdss@v0.0.0-20231129015646-c2ec58cca6a2/contrib/roaring/serialization_littleendian.go (about) 1 //go:build (386 && !appengine) || (amd64 && !appengine) || (arm && !appengine) || (arm64 && !appengine) || (ppc64le && !appengine) || (mipsle && !appengine) || (mips64le && !appengine) || (mips64p32le && !appengine) || (wasm && !appengine) 2 // +build 386,!appengine amd64,!appengine arm,!appengine arm64,!appengine ppc64le,!appengine mipsle,!appengine mips64le,!appengine mips64p32le,!appengine wasm,!appengine 3 4 package roaring 5 6 import ( 7 "encoding/binary" 8 "errors" 9 "io" 10 "reflect" 11 "runtime" 12 "unsafe" 13 ) 14 15 func (ac *arrayContainer) writeTo(stream io.Writer) (int, error) { 16 buf := uint16SliceAsByteSlice(ac.content) 17 return stream.Write(buf) 18 } 19 20 func (bc *bitmapContainer) writeTo(stream io.Writer) (int, error) { 21 if bc.cardinality <= arrayDefaultMaxSize { 22 return 0, errors.New("refusing to write bitmap container with cardinality of array container") 23 } 24 buf := uint64SliceAsByteSlice(bc.bitmap) 25 return stream.Write(buf) 26 } 27 28 func uint64SliceAsByteSlice(slice []uint64) []byte { 29 // make a new slice header 30 header := *(*reflect.SliceHeader)(unsafe.Pointer(&slice)) 31 32 // update its capacity and length 33 header.Len *= 8 34 header.Cap *= 8 35 36 // instantiate result and use KeepAlive so data isn't unmapped. 37 result := *(*[]byte)(unsafe.Pointer(&header)) 38 runtime.KeepAlive(&slice) 39 40 // return it 41 return result 42 } 43 44 func uint16SliceAsByteSlice(slice []uint16) []byte { 45 // make a new slice header 46 header := *(*reflect.SliceHeader)(unsafe.Pointer(&slice)) 47 48 // update its capacity and length 49 header.Len *= 2 50 header.Cap *= 2 51 52 // instantiate result and use KeepAlive so data isn't unmapped. 53 result := *(*[]byte)(unsafe.Pointer(&header)) 54 runtime.KeepAlive(&slice) 55 56 // return it 57 return result 58 } 59 60 func interval16SliceAsByteSlice(slice []interval16) []byte { 61 // make a new slice header 62 header := *(*reflect.SliceHeader)(unsafe.Pointer(&slice)) 63 64 // update its capacity and length 65 header.Len *= 4 66 header.Cap *= 4 67 68 // instantiate result and use KeepAlive so data isn't unmapped. 69 result := *(*[]byte)(unsafe.Pointer(&header)) 70 runtime.KeepAlive(&slice) 71 72 // return it 73 return result 74 } 75 76 func (bc *bitmapContainer) asLittleEndianByteSlice() []byte { 77 return uint64SliceAsByteSlice(bc.bitmap) 78 } 79 80 // Deserialization code follows 81 82 //// 83 // These methods (byteSliceAsUint16Slice,...) do not make copies, 84 // they are pointer-based (unsafe). The caller is responsible to 85 // ensure that the input slice does not get garbage collected, deleted 86 // or modified while you hold the returned slince. 87 //// 88 func byteSliceAsUint16Slice(slice []byte) (result []uint16) { // here we create a new slice holder 89 if len(slice)%2 != 0 { 90 panic("Slice size should be divisible by 2") 91 } 92 // reference: https://go101.org/article/unsafe.html 93 94 // make a new slice header 95 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 96 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 97 98 // transfer the data from the given slice to a new variable (our result) 99 rHeader.Data = bHeader.Data 100 rHeader.Len = bHeader.Len / 2 101 rHeader.Cap = bHeader.Cap / 2 102 103 // instantiate result and use KeepAlive so data isn't unmapped. 104 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 105 106 // return result 107 return 108 } 109 110 func byteSliceAsUint64Slice(slice []byte) (result []uint64) { 111 if len(slice)%8 != 0 { 112 panic("Slice size should be divisible by 8") 113 } 114 // reference: https://go101.org/article/unsafe.html 115 116 // make a new slice header 117 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 118 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 119 120 // transfer the data from the given slice to a new variable (our result) 121 rHeader.Data = bHeader.Data 122 rHeader.Len = bHeader.Len / 8 123 rHeader.Cap = bHeader.Cap / 8 124 125 // instantiate result and use KeepAlive so data isn't unmapped. 126 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 127 128 // return result 129 return 130 } 131 132 func byteSliceAsInterval16Slice(slice []byte) (result []interval16) { 133 if len(slice)%4 != 0 { 134 panic("Slice size should be divisible by 4") 135 } 136 // reference: https://go101.org/article/unsafe.html 137 138 // make a new slice header 139 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 140 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 141 142 // transfer the data from the given slice to a new variable (our result) 143 rHeader.Data = bHeader.Data 144 rHeader.Len = bHeader.Len / 4 145 rHeader.Cap = bHeader.Cap / 4 146 147 // instantiate result and use KeepAlive so data isn't unmapped. 148 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 149 150 // return result 151 return 152 } 153 154 func byteSliceAsContainerSlice(slice []byte) (result []container) { 155 var c container 156 containerSize := int(unsafe.Sizeof(c)) 157 158 if len(slice)%containerSize != 0 { 159 panic("Slice size should be divisible by unsafe.Sizeof(container)") 160 } 161 // reference: https://go101.org/article/unsafe.html 162 163 // make a new slice header 164 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 165 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 166 167 // transfer the data from the given slice to a new variable (our result) 168 rHeader.Data = bHeader.Data 169 rHeader.Len = bHeader.Len / containerSize 170 rHeader.Cap = bHeader.Cap / containerSize 171 172 // instantiate result and use KeepAlive so data isn't unmapped. 173 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 174 175 // return result 176 return 177 } 178 179 func byteSliceAsBitsetSlice(slice []byte) (result []bitmapContainer) { 180 bitsetSize := int(unsafe.Sizeof(bitmapContainer{})) 181 if len(slice)%bitsetSize != 0 { 182 panic("Slice size should be divisible by unsafe.Sizeof(bitmapContainer)") 183 } 184 // reference: https://go101.org/article/unsafe.html 185 186 // make a new slice header 187 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 188 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 189 190 // transfer the data from the given slice to a new variable (our result) 191 rHeader.Data = bHeader.Data 192 rHeader.Len = bHeader.Len / bitsetSize 193 rHeader.Cap = bHeader.Cap / bitsetSize 194 195 // instantiate result and use KeepAlive so data isn't unmapped. 196 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 197 198 // return result 199 return 200 } 201 202 func byteSliceAsArraySlice(slice []byte) (result []arrayContainer) { 203 arraySize := int(unsafe.Sizeof(arrayContainer{})) 204 if len(slice)%arraySize != 0 { 205 panic("Slice size should be divisible by unsafe.Sizeof(arrayContainer)") 206 } 207 // reference: https://go101.org/article/unsafe.html 208 209 // make a new slice header 210 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 211 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 212 213 // transfer the data from the given slice to a new variable (our result) 214 rHeader.Data = bHeader.Data 215 rHeader.Len = bHeader.Len / arraySize 216 rHeader.Cap = bHeader.Cap / arraySize 217 218 // instantiate result and use KeepAlive so data isn't unmapped. 219 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 220 221 // return result 222 return 223 } 224 225 func byteSliceAsRun16Slice(slice []byte) (result []runContainer16) { 226 run16Size := int(unsafe.Sizeof(runContainer16{})) 227 if len(slice)%run16Size != 0 { 228 panic("Slice size should be divisible by unsafe.Sizeof(runContainer16)") 229 } 230 // reference: https://go101.org/article/unsafe.html 231 232 // make a new slice header 233 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 234 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 235 236 // transfer the data from the given slice to a new variable (our result) 237 rHeader.Data = bHeader.Data 238 rHeader.Len = bHeader.Len / run16Size 239 rHeader.Cap = bHeader.Cap / run16Size 240 241 // instantiate result and use KeepAlive so data isn't unmapped. 242 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 243 244 // return result 245 return 246 } 247 248 func byteSliceAsBoolSlice(slice []byte) (result []bool) { 249 boolSize := int(unsafe.Sizeof(true)) 250 if len(slice)%boolSize != 0 { 251 panic("Slice size should be divisible by unsafe.Sizeof(bool)") 252 } 253 // reference: https://go101.org/article/unsafe.html 254 255 // make a new slice header 256 bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) 257 rHeader := (*reflect.SliceHeader)(unsafe.Pointer(&result)) 258 259 // transfer the data from the given slice to a new variable (our result) 260 rHeader.Data = bHeader.Data 261 rHeader.Len = bHeader.Len / boolSize 262 rHeader.Cap = bHeader.Cap / boolSize 263 264 // instantiate result and use KeepAlive so data isn't unmapped. 265 runtime.KeepAlive(&slice) // it is still crucial, GC can free it) 266 267 // return result 268 return 269 } 270 271 // FrozenView creates a static view of a serialized bitmap stored in buf. 272 // It uses CRoaring's frozen bitmap format. 273 // 274 // The format specification is available here: 275 // https://github.com/RoaringBitmap/CRoaring/blob/2c867e9f9c9e2a3a7032791f94c4c7ae3013f6e0/src/roaring.c#L2756-L2783 276 // 277 // The provided byte array (buf) is expected to be a constant. 278 // The function makes the best effort attempt not to copy data. 279 // Only little endian is supported. The function will err if it detects a big 280 // endian serialized file. 281 // You should take care not to modify buff as it will likely result in 282 // unexpected program behavior. 283 // If said buffer comes from a memory map, it's advisable to give it read 284 // only permissions, either at creation or by calling Mprotect from the 285 // golang.org/x/sys/unix package. 286 // 287 // Resulting bitmaps are effectively immutable in the following sense: 288 // a copy-on-write marker is used so that when you modify the resulting 289 // bitmap, copies of selected data (containers) are made. 290 // You should *not* change the copy-on-write status of the resulting 291 // bitmaps (SetCopyOnWrite). 292 // 293 // If buf becomes unavailable, then a bitmap created with 294 // FromBuffer would be effectively broken. Furthermore, any 295 // bitmap derived from this bitmap (e.g., via Or, And) might 296 // also be broken. Thus, before making buf unavailable, you should 297 // call CloneCopyOnWriteContainers on all such bitmaps. 298 // 299 func (rb *Bitmap) FrozenView(buf []byte) error { 300 return rb.highlowcontainer.frozenView(buf) 301 } 302 303 /* Verbatim specification from CRoaring. 304 * 305 * FROZEN SERIALIZATION FORMAT DESCRIPTION 306 * 307 * -- (beginning must be aligned by 32 bytes) -- 308 * <bitset_data> uint64_t[BITSET_CONTAINER_SIZE_IN_WORDS * num_bitset_containers] 309 * <run_data> rle16_t[total number of rle elements in all run containers] 310 * <array_data> uint16_t[total number of array elements in all array containers] 311 * <keys> uint16_t[num_containers] 312 * <counts> uint16_t[num_containers] 313 * <typecodes> uint8_t[num_containers] 314 * <header> uint32_t 315 * 316 * <header> is a 4-byte value which is a bit union of FROZEN_COOKIE (15 bits) 317 * and the number of containers (17 bits). 318 * 319 * <counts> stores number of elements for every container. 320 * Its meaning depends on container type. 321 * For array and bitset containers, this value is the container cardinality minus one. 322 * For run container, it is the number of rle_t elements (n_runs). 323 * 324 * <bitset_data>,<array_data>,<run_data> are flat arrays of elements of 325 * all containers of respective type. 326 * 327 * <*_data> and <keys> are kept close together because they are not accessed 328 * during deserilization. This may reduce IO in case of large mmaped bitmaps. 329 * All members have their native alignments during deserilization except <header>, 330 * which is not guaranteed to be aligned by 4 bytes. 331 */ 332 const FROZEN_COOKIE = 13766 333 334 var ( 335 FrozenBitmapInvalidCookie = errors.New("header does not contain the FROZEN_COOKIE") 336 FrozenBitmapBigEndian = errors.New("loading big endian frozen bitmaps is not supported") 337 FrozenBitmapIncomplete = errors.New("input buffer too small to contain a frozen bitmap") 338 FrozenBitmapOverpopulated = errors.New("too many containers") 339 FrozenBitmapUnexpectedData = errors.New("spurious data in input") 340 FrozenBitmapInvalidTypecode = errors.New("unrecognized typecode") 341 FrozenBitmapBufferTooSmall = errors.New("buffer too small") 342 ) 343 344 func (ra *roaringArray) frozenView(buf []byte) error { 345 if len(buf) < 4 { 346 return FrozenBitmapIncomplete 347 } 348 349 headerBE := binary.BigEndian.Uint32(buf[len(buf)-4:]) 350 if headerBE&0x7fff == FROZEN_COOKIE { 351 return FrozenBitmapBigEndian 352 } 353 354 header := binary.LittleEndian.Uint32(buf[len(buf)-4:]) 355 buf = buf[:len(buf)-4] 356 357 if header&0x7fff != FROZEN_COOKIE { 358 return FrozenBitmapInvalidCookie 359 } 360 361 nCont := int(header >> 15) 362 if nCont > (1 << 16) { 363 return FrozenBitmapOverpopulated 364 } 365 366 // 1 byte per type, 2 bytes per key, 2 bytes per count. 367 if len(buf) < 5*nCont { 368 return FrozenBitmapIncomplete 369 } 370 371 types := buf[len(buf)-nCont:] 372 buf = buf[:len(buf)-nCont] 373 374 counts := byteSliceAsUint16Slice(buf[len(buf)-2*nCont:]) 375 buf = buf[:len(buf)-2*nCont] 376 377 keys := byteSliceAsUint16Slice(buf[len(buf)-2*nCont:]) 378 buf = buf[:len(buf)-2*nCont] 379 380 nBitmap, nArray, nRun := 0, 0, 0 381 nArrayEl, nRunEl := 0, 0 382 for i, t := range types { 383 switch t { 384 case 1: 385 nBitmap++ 386 case 2: 387 nArray++ 388 nArrayEl += int(counts[i]) + 1 389 case 3: 390 nRun++ 391 nRunEl += int(counts[i]) 392 default: 393 return FrozenBitmapInvalidTypecode 394 } 395 } 396 397 if len(buf) < (1<<13)*nBitmap+4*nRunEl+2*nArrayEl { 398 return FrozenBitmapIncomplete 399 } 400 401 bitsetsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBitmap]) 402 buf = buf[(1<<13)*nBitmap:] 403 404 runsArena := byteSliceAsInterval16Slice(buf[:4*nRunEl]) 405 buf = buf[4*nRunEl:] 406 407 arraysArena := byteSliceAsUint16Slice(buf[:2*nArrayEl]) 408 buf = buf[2*nArrayEl:] 409 410 if len(buf) != 0 { 411 return FrozenBitmapUnexpectedData 412 } 413 414 var c container 415 containersSz := int(unsafe.Sizeof(c))*nCont 416 bitsetsSz := int(unsafe.Sizeof(bitmapContainer{}))*nBitmap 417 arraysSz := int(unsafe.Sizeof(arrayContainer{}))*nArray 418 runsSz := int(unsafe.Sizeof(runContainer16{}))*nRun 419 needCOWSz := int(unsafe.Sizeof(true))*nCont 420 421 bitmapArenaSz := containersSz + bitsetsSz + arraysSz + runsSz + needCOWSz 422 bitmapArena := make([]byte, bitmapArenaSz) 423 424 containers := byteSliceAsContainerSlice(bitmapArena[:containersSz]) 425 bitmapArena = bitmapArena[containersSz:] 426 427 bitsets := byteSliceAsBitsetSlice(bitmapArena[:bitsetsSz]) 428 bitmapArena = bitmapArena[bitsetsSz:] 429 430 arrays := byteSliceAsArraySlice(bitmapArena[:arraysSz]) 431 bitmapArena = bitmapArena[arraysSz:] 432 433 runs := byteSliceAsRun16Slice(bitmapArena[:runsSz]) 434 bitmapArena = bitmapArena[runsSz:] 435 436 needCOW := byteSliceAsBoolSlice(bitmapArena) 437 438 iBitset, iArray, iRun := 0, 0, 0 439 for i, t := range types { 440 needCOW[i] = true 441 442 switch t { 443 case 1: 444 containers[i] = &bitsets[iBitset] 445 bitsets[iBitset].cardinality = int(counts[i]) + 1 446 bitsets[iBitset].bitmap = bitsetsArena[:1024] 447 bitsetsArena = bitsetsArena[1024:] 448 iBitset++ 449 case 2: 450 containers[i] = &arrays[iArray] 451 sz := int(counts[i]) + 1 452 arrays[iArray].content = arraysArena[:sz] 453 arraysArena = arraysArena[sz:] 454 iArray++ 455 case 3: 456 containers[i] = &runs[iRun] 457 runs[iRun].iv = runsArena[:counts[i]] 458 runsArena = runsArena[counts[i]:] 459 iRun++ 460 } 461 } 462 463 // Not consuming the full input is a bug. 464 if iBitset != nBitmap || len(bitsetsArena) != 0 || 465 iArray != nArray || len(arraysArena) != 0 || 466 iRun != nRun || len(runsArena) != 0 { 467 panic("we missed something") 468 } 469 470 ra.keys = keys 471 ra.containers = containers 472 ra.needCopyOnWrite = needCOW 473 ra.copyOnWrite = true 474 475 return nil 476 } 477 478 func (bm *Bitmap) GetFrozenSizeInBytes() uint64 { 479 nBits, nArrayEl, nRunEl := uint64(0), uint64(0), uint64(0) 480 for _, c := range bm.highlowcontainer.containers { 481 switch v := c.(type) { 482 case *bitmapContainer: 483 nBits++ 484 case *arrayContainer: 485 nArrayEl += uint64(len(v.content)) 486 case *runContainer16: 487 nRunEl += uint64(len(v.iv)) 488 } 489 } 490 return 4 + 5*uint64(len(bm.highlowcontainer.containers)) + 491 (nBits << 13) + 2*nArrayEl + 4*nRunEl 492 } 493 494 func (bm *Bitmap) Freeze() ([]byte, error) { 495 sz := bm.GetFrozenSizeInBytes() 496 buf := make([]byte, sz) 497 _, err := bm.FreezeTo(buf) 498 return buf, err 499 } 500 501 func (bm *Bitmap) FreezeTo(buf []byte) (int, error) { 502 containers := bm.highlowcontainer.containers 503 nCont := len(containers) 504 505 nBits, nArrayEl, nRunEl := 0, 0, 0 506 for _, c := range containers { 507 switch v := c.(type) { 508 case *bitmapContainer: 509 nBits++ 510 case *arrayContainer: 511 nArrayEl += len(v.content) 512 case *runContainer16: 513 nRunEl += len(v.iv) 514 } 515 } 516 517 serialSize := 4 + 5*nCont + (1<<13)*nBits + 4*nRunEl + 2*nArrayEl 518 if len(buf) < serialSize { 519 return 0, FrozenBitmapBufferTooSmall 520 } 521 522 bitsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBits]) 523 buf = buf[(1<<13)*nBits:] 524 525 runsArena := byteSliceAsInterval16Slice(buf[:4*nRunEl]) 526 buf = buf[4*nRunEl:] 527 528 arraysArena := byteSliceAsUint16Slice(buf[:2*nArrayEl]) 529 buf = buf[2*nArrayEl:] 530 531 keys := byteSliceAsUint16Slice(buf[:2*nCont]) 532 buf = buf[2*nCont:] 533 534 counts := byteSliceAsUint16Slice(buf[:2*nCont]) 535 buf = buf[2*nCont:] 536 537 types := buf[:nCont] 538 buf = buf[nCont:] 539 540 header := uint32(FROZEN_COOKIE | (nCont << 15)) 541 binary.LittleEndian.PutUint32(buf[:4], header) 542 543 copy(keys, bm.highlowcontainer.keys[:]) 544 545 for i, c := range containers { 546 switch v := c.(type) { 547 case *bitmapContainer: 548 copy(bitsArena, v.bitmap) 549 bitsArena = bitsArena[1024:] 550 counts[i] = uint16(v.cardinality - 1) 551 types[i] = 1 552 case *arrayContainer: 553 copy(arraysArena, v.content) 554 arraysArena = arraysArena[len(v.content):] 555 elems := len(v.content) 556 counts[i] = uint16(elems - 1) 557 types[i] = 2 558 case *runContainer16: 559 copy(runsArena, v.iv) 560 runs := len(v.iv) 561 runsArena = runsArena[runs:] 562 counts[i] = uint16(runs) 563 types[i] = 3 564 } 565 } 566 567 return serialSize, nil 568 } 569 570 func (bm *Bitmap) WriteFrozenTo(wr io.Writer) (int, error) { 571 // FIXME: this is a naive version that iterates 4 times through the 572 // containers and allocates 3*len(containers) bytes; it's quite likely 573 // it can be done more efficiently. 574 containers := bm.highlowcontainer.containers 575 written := 0 576 577 for _, c := range containers { 578 c, ok := c.(*bitmapContainer) 579 if !ok { 580 continue 581 } 582 n, err := wr.Write(uint64SliceAsByteSlice(c.bitmap)) 583 written += n 584 if err != nil { 585 return written, err 586 } 587 } 588 589 for _, c := range containers { 590 c, ok := c.(*runContainer16) 591 if !ok { 592 continue 593 } 594 n, err := wr.Write(interval16SliceAsByteSlice(c.iv)) 595 written += n 596 if err != nil { 597 return written, err 598 } 599 } 600 601 for _, c := range containers { 602 c, ok := c.(*arrayContainer) 603 if !ok { 604 continue 605 } 606 n, err := wr.Write(uint16SliceAsByteSlice(c.content)) 607 written += n 608 if err != nil { 609 return written, err 610 } 611 } 612 613 n, err := wr.Write(uint16SliceAsByteSlice(bm.highlowcontainer.keys)) 614 written += n 615 if err != nil { 616 return written, err 617 } 618 619 countTypeBuf := make([]byte, 3*len(containers)) 620 counts := byteSliceAsUint16Slice(countTypeBuf[:2*len(containers)]) 621 types := countTypeBuf[2*len(containers):] 622 623 for i, c := range containers { 624 switch c := c.(type) { 625 case *bitmapContainer: 626 counts[i] = uint16(c.cardinality - 1) 627 types[i] = 1 628 case *arrayContainer: 629 elems := len(c.content) 630 counts[i] = uint16(elems - 1) 631 types[i] = 2 632 case *runContainer16: 633 runs := len(c.iv) 634 counts[i] = uint16(runs) 635 types[i] = 3 636 } 637 } 638 639 n, err = wr.Write(countTypeBuf) 640 written += n 641 if err != nil { 642 return written, err 643 } 644 645 header := uint32(FROZEN_COOKIE | (len(containers) << 15)) 646 if err := binary.Write(wr, binary.LittleEndian, header); err != nil { 647 return written, err 648 } 649 written += 4 650 651 return written, nil 652 }