@@ -3,29 +3,29 @@ package org.ton.sdk.cell.boc
33import kotlinx.atomicfu.locks.reentrantLock
44import kotlinx.atomicfu.locks.withLock
55import kotlinx.io.*
6- import kotlinx.io.bytestring.ByteString
7- import org.ton.sdk.bitstring.BitString
8- import org.ton.sdk.bitstring.unsafe.UnsafeBitStringOperations
6+ import kotlinx.io.unsafe.UnsafeBufferOperations
7+ import kotlinx.io.unsafe.withData
98import org.ton.sdk.cell.*
10- import org.ton.sdk.cell.boc.internal.ByteArrayRandomAccessStorage
11- import org.ton.sdk.cell.boc.internal.ByteStringRandomAccessSource
12- import org.ton.sdk.cell.boc.internal.RandomAccessSource
13- import org.ton.sdk.cell.boc.internal.readLong
9+ import org.ton.sdk.cell.boc.internal.*
1410import org.ton.sdk.cell.internal.DataCell
15- import org.ton.sdk.crypto.HashBytes
11+ import org.ton.sdk.cell.internal.ExtCell
12+ import org.ton.sdk.crypto.CRC32C
13+ import kotlin.math.min
1614
1715private const val HASH_BYTES = 32
1816private const val DEPTH_BYTES = 2
1917
2018public class StaticBagOfCells internal constructor(
21- private val source : RandomAccessSource ,
22- private val checkHashes : Boolean = true
19+ private val source : SeekableRawSource ,
20+ private val options : DecodeOptions
2321) : BagOfCells(), CellContext {
24- public constructor (byteArray: ByteArray ) : this (ByteArrayRandomAccessStorage (byteArray))
25- public constructor (byteString: ByteString ) : this (ByteStringRandomAccessSource (byteString))
22+ public constructor (byteArray: ByteArray , decodeOptions: DecodeOptions ) : this (
23+ ByteArraySeekableRawSource (byteArray),
24+ decodeOptions
25+ )
2626
2727 public val header: BagOfCellsHeader by lazy {
28- readHeader ()
28+ loadHeader ()
2929 }
3030 private val cachedCells = CellCache ()
3131 private val cachedLocations = ArrayList <CellLocation >()
@@ -39,42 +39,14 @@ public class StaticBagOfCells internal constructor(
3939 return RootCell (this , index, dataCell)
4040 }
4141
42- private fun loadSerializedCell (index : Int ): Cell {
43- val cellLocation = cachedLocations.getOrElse(index) {
44- getCellLocation(index)
45- }
46- val buffer = Buffer ()
47-
48- var descriptor = cellLocation.descriptor
49- if (descriptor == null ) {
50- source.position = cellLocation.start
51- source.readAtMostTo(buffer, 2 )
52- val d1 = buffer.readByte()
53- val d2 = buffer.readByte()
54- descriptor = CellDescriptor (d1, d2)
55- }
56- source.position = cellLocation.start + 2
42+ private fun deserializeCell (index : Int , source : Source , descriptor : CellDescriptor , shouldCache : Boolean ): Cell {
5743 require(descriptor.referenceCount in 0 .. 4 ) {
5844 " Invalid BOC cell #$index has invalid reference count: ${descriptor.referenceCount} "
5945 }
6046
61- fun loadBits (): BitString {
62- val dataLength = descriptor.byteLength
63- source.readAtMostTo(buffer, dataLength.toLong())
64- val data = buffer.readByteArray(dataLength)
65- val bitLength = if (descriptor.isAligned) {
66- data.size * 8
67- } else {
68- data.size * 8 - data.last().countTrailingZeroBits() - 1
69- }
70- @Suppress(" OPT_IN_USAGE" )
71- return UnsafeBitStringOperations .wrapUnsafe(data, bitLength)
72- }
73-
7447 fun loadRefs (): List <Cell > {
7548 return List (descriptor.referenceCount) {
76- source.readAtMostTo(buffer, header.refByteSize.toLong())
77- val refIndex = buffer.readLong(header.refByteSize).toInt()
49+ val refIndex = source.readLong(header.refByteSize).toInt()
7850 check(refIndex < header.cellCount) {
7951 " Invalid BOC cell #$index refers ($it ) to cell #$refIndex which too big, cellCount=${header.cellCount} "
8052 }
@@ -86,34 +58,22 @@ public class StaticBagOfCells internal constructor(
8658 }
8759
8860 val hashHashes = descriptor.hasHashes
89- val hashes: Array <HashBytes >
90- val depths: IntArray
9161 val cell = if (hashHashes) {
92- val hashCount = descriptor.levelMask.hashCount
93- source.readAtMostTo(buffer, (HASH_BYTES + 2L ) * hashCount)
94- hashes = Array (hashCount) {
95- HashBytes (buffer.readByteString(HASH_BYTES ))
96- }
97- depths = IntArray (hashCount) {
98- buffer.readUShort().toInt()
99- }
100-
101- val bits = loadBits()
62+ val hashes = source.readHashes(descriptor.levelMask)
63+ val depths = source.readDepths(descriptor.levelMask)
64+ val bits = source.readBits(descriptor)
10265 val refs = loadRefs()
103-
10466 val cell = DataCell (descriptor, bits, refs, hashes, depths)
105- if (checkHashes) {
67+ if (options. checkHashes) {
10668 val expectedCell = with (CellBuilder ()) {
10769 store(bits)
10870 refs.forEach {
10971 storeReference(it)
11072 }
11173 build(descriptor.isExotic)
11274 }
113- check(expectedCell.descriptor == cell.descriptor) {
114- " Invalid BOC cell #$index descriptor: expected=${expectedCell.descriptor} , found=${cell.descriptor} "
115- }
11675 for (level in 0 until descriptor.levelMask.level) {
76+ if (level !in descriptor.levelMask) continue
11777 val expectedDepth = expectedCell.depth(level)
11878 val actualDepth = cell.depth(level)
11979 check(expectedDepth == actualDepth) {
@@ -126,12 +86,10 @@ public class StaticBagOfCells internal constructor(
12686 " Invalid BOC cell #$index hash at level $level : expected=$expectedHash , found=$actualHash "
12787 }
12888 }
129- expectedCell
130- } else {
131- cell
13289 }
90+ cell
13391 } else {
134- val bits = loadBits( )
92+ val bits = source.readBits(descriptor )
13593 val refs = loadRefs()
13694 with (CellBuilder ()) {
13795 store(bits)
@@ -142,20 +100,57 @@ public class StaticBagOfCells internal constructor(
142100 }
143101 }
144102
145- if (cellLocation. shouldCache) {
103+ if (shouldCache) {
146104 cachedCells[index] = cell
147105 }
148106 return cell
149107 }
150108
109+ private fun deserializeAnyCell (index : Int , source : Source , descriptor : CellDescriptor , shouldCache : Boolean ): Cell {
110+ if (options.lazyLoad && descriptor.hasHashes) {
111+ return ExtCell (descriptor, loader = { loadSerializedCell(index) })
112+ }
113+ return deserializeCell(index, source, descriptor, shouldCache)
114+ }
115+
116+ private fun loadSerializedCell (index : Int ): Cell {
117+ cachedCells[index]?.let { return it }
118+ val cellLocation = getCellLocation(index)
119+ source.position = cellLocation.start
120+ val buffer = source.buffer(cellLocation.end - cellLocation.start)
121+
122+ var descriptor = cellLocation.descriptor
123+ if (descriptor == null ) {
124+ val d1 = buffer.readByte()
125+ val d2 = buffer.readByte()
126+ descriptor = CellDescriptor (d1, d2)
127+ } else {
128+ buffer.skip(2 )
129+ }
130+ return deserializeCell(index, buffer, descriptor, cellLocation.shouldCache)
131+ }
132+
151133 private fun loadAnyCell (index : Int ): Cell {
152- return loadSerializedCell(index)
134+ cachedCells[index]?.let { return it }
135+ val cellLocation = getCellLocation(index)
136+ source.position = cellLocation.start
137+ val buffer = source.buffer(cellLocation.end - cellLocation.start)
138+ var descriptor = cellLocation.descriptor
139+ if (descriptor == null ) {
140+ val d1 = buffer.readByte()
141+ val d2 = buffer.readByte()
142+ descriptor = CellDescriptor (d1, d2)
143+ } else {
144+ buffer.skip(2 )
145+ }
146+ return deserializeAnyCell(index, buffer, descriptor, cellLocation.shouldCache)
153147 }
154148
155149 private fun getCellLocation (index : Int ): CellLocation {
156150 require(index in 0 until header.cellCount) {
157151 " Invalid cell index: $index , cellCount=${header.cellCount} "
158152 }
153+ cachedLocations.getOrNull(index)?.let { return it }
159154 if (! header.hasIndex) {
160155 println (" Warning: BOC does not have index, using linear search for cell #$index " )
161156 if (index < cachedLocations.size) {
@@ -178,6 +173,7 @@ public class StaticBagOfCells internal constructor(
178173 }
179174
180175 fun Source.loadIndexOffset (index : Int ): Long {
176+ if (index < 0 ) return 0
181177 source.position = header.indexOffset + (index.toLong() * header.offsetByteSize)
182178 return readLong(header.offsetByteSize)
183179 }
@@ -210,10 +206,37 @@ public class StaticBagOfCells internal constructor(
210206 return buffer.readLong(header.refByteSize).toInt()
211207 }
212208
213- @OptIn(ExperimentalStdlibApi ::class )
214- private fun readHeader (): BagOfCellsHeader {
209+ @OptIn(InternalIoApi :: class , UnsafeIoApi ::class )
210+ private fun loadHeader (): BagOfCellsHeader {
215211 source.position = 0
216- return BagOfCellsHeader .parse(source.buffered())
212+ val buffer = source.buffered()
213+ if (! options.checkCrc32c) {
214+ return BagOfCellsHeader .parse(buffer)
215+ }
216+ val header = BagOfCellsHeader .parse(buffer.peek())
217+ if (header.hasCrc32c) {
218+ val crc32c = CRC32C ()
219+ val dataSize = header.totalSize - 4
220+ var remaining = dataSize
221+ UnsafeBufferOperations .forEachSegment(buffer.buffer) { ctx, segment ->
222+ ctx.withData(segment) { data, startIndex, endIndex ->
223+ val byteCount = endIndex - startIndex
224+ if (remaining <= 0 ) return @withData
225+ val toRead = min(remaining, byteCount.toLong()).toInt()
226+ crc32c.update(data, startIndex, startIndex + toRead)
227+ remaining - = toRead
228+ }
229+ }
230+ buffer.skip(dataSize)
231+ val expectedCrc32c = buffer.readIntLe()
232+ val actualCrc32c = crc32c.intDigest()
233+ check(expectedCrc32c == actualCrc32c) {
234+ " Invalid BOC CRC32C: expected=${expectedCrc32c.toUInt().toString(16 )} , found=${
235+ actualCrc32c.toUInt().toString(16 )
236+ } "
237+ }
238+ }
239+ return header
217240 }
218241
219242 override fun toString (): String = " StaticBagOfCells(header=$header )"
@@ -225,12 +248,13 @@ public class StaticBagOfCells internal constructor(
225248 is LoadedCell -> cell
226249 is RootCell -> toLoadedCell(cell.cell)
227250 is BocCell -> toLoadedCell(cell.cell)
251+ is ExtCell -> toLoadedCell(cell.cell)
228252 else -> throw IllegalArgumentException (" Can't load ${cell::class } $cell " )
229253 }
230254 }
231255
232256 override fun finalizeCell (builder : CellBuilder ): Cell {
233- TODO ( " Not yet implemented " )
257+ return CellContext . EMPTY .finalizeCell(builder )
234258 }
235259
236260 public class RootCell (
0 commit comments