1
+ /* eslint-disable no-cond-assign */
1
2
import type { EncodedBlock } from './shared'
2
3
import { inflate } from 'pako'
3
4
import { getChecksum } from './checksum'
@@ -12,6 +13,10 @@ export class LtDecoder {
12
13
public decodedCount = 0
13
14
public encodedCount = 0
14
15
public encodedBlocks : Set < EncodedBlock > = new Set ( )
16
+ public encodedBlockKeyMap : Map < string , EncodedBlock > = new Map ( )
17
+ public encodedBlockSubkeyMap : Map < string , Set < EncodedBlock > > = new Map ( )
18
+ public encodedBlockIndexMap : Map < number , Set < EncodedBlock > > = new Map ( )
19
+ public disposedEncodedBlocks : Map < number , ( ( ) => void ) [ ] > = new Map ( )
15
20
public meta : EncodedBlock = undefined !
16
21
17
22
constructor ( blocks ?: EncodedBlock [ ] ) {
@@ -32,65 +37,113 @@ export class LtDecoder {
32
37
if ( block . checksum !== this . meta . checksum ) {
33
38
throw new Error ( 'Adding block with different checksum' )
34
39
}
35
- this . encodedBlocks . add ( block )
36
40
this . encodedCount += 1
37
41
38
- this . propagateDecoded ( )
42
+ block . indices = block . indices . sort ( ( a , b ) => a - b )
43
+ this . propagateDecoded ( indicesToKey ( block . indices ) , block )
39
44
40
45
return this . decodedCount === this . meta . k
41
46
}
42
47
43
- propagateDecoded ( ) {
44
- let changed = false
45
- for ( const block of this . encodedBlocks ) {
46
- let { data, indices } = block
48
+ propagateDecoded ( key : string , block : EncodedBlock ) {
49
+ const { decodedData, encodedBlocks, encodedBlockIndexMap, encodedBlockKeyMap, encodedBlockSubkeyMap, disposedEncodedBlocks } = this
47
50
48
- // We already have all the data from this block
49
- if ( indices . every ( index => this . decodedData [ index ] != null ) ) {
50
- this . encodedBlocks . delete ( block )
51
- continue
51
+ let index : number
52
+ let blocks : Set < EncodedBlock > | undefined
53
+
54
+ let { data, indices } = block
55
+ const indicesSet = new Set ( indices )
56
+
57
+ let subblock : EncodedBlock | undefined
58
+ let subIndicesSet : Set < number >
59
+
60
+ if ( encodedBlockKeyMap . has ( key ) || indices . every ( i => decodedData [ i ] != null ) ) {
61
+ return
62
+ }
63
+
64
+ // XOR the data
65
+ // Current block > degree 1, find decoded subset degree blocks to decode
66
+ if ( indices . length > 1 ) {
67
+ for ( const index of indices ) {
68
+ if ( decodedData [ index ] != null ) {
69
+ block . data = data = xorUint8Array ( data , decodedData [ index ] ! )
70
+ indicesSet . delete ( index )
71
+ }
52
72
}
73
+ if ( indicesSet . size !== indices . length ) {
74
+ block . indices = indices = Array . from ( indicesSet )
75
+ }
76
+ }
53
77
54
- // XOR the data
78
+ // Use 1x2x3 XOR 2x3 to get 1
79
+ if ( indices . length > 2 ) {
80
+ const subkeys : [ index : number , subkey : string ] [ ] = [ ]
55
81
for ( const index of indices ) {
56
- if ( this . decodedData [ index ] != null ) {
57
- block . data = data = xorUint8Array ( data , this . decodedData [ index ] ! )
58
- block . indices = indices = indices . filter ( i => i !== index )
59
- changed = true
82
+ const subkey = indicesToKey ( indices . filter ( i => i !== index ) )
83
+ if ( subblock = encodedBlockKeyMap . get ( subkey ) ) {
84
+ block . data = data = xorUint8Array ( data , subblock . data )
85
+ subIndicesSet = new Set ( subblock . indices )
86
+ for ( const i of subIndicesSet ) {
87
+ indicesSet . delete ( i )
88
+ }
89
+ block . indices = indices = Array . from ( indicesSet )
90
+ break
91
+ }
92
+ else {
93
+ subkeys . push ( [ index , subkey ] )
60
94
}
61
95
}
62
96
63
- if ( indices . length === 1 && this . decodedData [ indices [ 0 ] ! ] == null ) {
64
- this . decodedData [ indices [ 0 ] ! ] = block . data
65
- this . decodedCount ++
66
- this . encodedBlocks . delete ( block )
67
- changed = true
97
+ // If we can't find a subblock, store the subkeys for future decoding
98
+ if ( indicesSet . size > 1 ) {
99
+ subkeys . forEach ( ( [ index , subkey ] ) => {
100
+ const dispose = ( ) => encodedBlockSubkeyMap . get ( subkey ) ?. delete ( block )
101
+ encodedBlockSubkeyMap . get ( subkey ) ?. add ( block ) ?? encodedBlockSubkeyMap . set ( subkey , new Set ( [ block ] ) )
102
+ disposedEncodedBlocks . get ( index ) ?. push ( dispose ) ?? disposedEncodedBlocks . set ( index , [ dispose ] )
103
+ } )
68
104
}
69
105
}
70
106
71
- for ( const block of this . encodedBlocks ) {
72
- const { data, indices } = block
73
-
74
- // Use 1x2x3 XOR 2x3 to get 1
75
- if ( indices . length >= 3 ) {
76
- const lowerBlocks = Array . from ( this . encodedBlocks ) . filter ( i => i . indices . length === indices . length - 1 )
77
- for ( const lower of lowerBlocks ) {
78
- const extraIndices = indices . filter ( i => ! lower . indices . includes ( i ) )
79
- if ( extraIndices . length === 1 && this . decodedData [ extraIndices [ 0 ] ! ] == null ) {
80
- const extraData = xorUint8Array ( data , lower . data )
81
- const extraIndex = extraIndices [ 0 ] !
82
- this . decodedData [ extraIndex ] = extraData
83
- this . decodedCount ++
84
- this . encodedBlocks . delete ( lower )
85
- changed = true
107
+ // After decoding, if the block > degree 1, store it as a pending block awaiting decoding
108
+ if ( indices . length > 1 ) {
109
+ block . indices . forEach ( ( i ) => {
110
+ encodedBlocks . add ( block )
111
+ encodedBlockIndexMap . get ( i ) ?. add ( block ) ?? encodedBlockIndexMap . set ( i , new Set ( [ block ] ) )
112
+ } )
113
+
114
+ encodedBlockKeyMap . set ( key = indicesToKey ( indices ) , block )
115
+
116
+ // Use 1x2 XOR 1x2x3 to get 3
117
+ const superset = encodedBlockSubkeyMap . get ( key )
118
+ if ( superset ) {
119
+ encodedBlockSubkeyMap . delete ( key )
120
+ for ( const superblock of superset ) {
121
+ const superIndicesSet = new Set ( superblock . indices )
122
+ superblock . data = xorUint8Array ( superblock . data , data )
123
+ for ( const i of indices ) {
124
+ superIndicesSet . delete ( i )
86
125
}
126
+ superblock . indices = Array . from ( superIndicesSet )
127
+ this . propagateDecoded ( indicesToKey ( superblock . indices ) , superblock )
87
128
}
88
129
}
89
130
}
90
131
91
- // If making some progress, continue
92
- if ( changed ) {
93
- this . propagateDecoded ( )
132
+ // After decoding, if the block is a degree 1 block, store it in decoded data and find blocks that can be decoded
133
+ else if ( decodedData [ index = indices [ 0 ] ! ] == null ) {
134
+ encodedBlocks . delete ( block )
135
+ disposedEncodedBlocks . get ( index ) ?. forEach ( dispose => dispose ( ) )
136
+ decodedData [ index ] = block . data
137
+ this . decodedCount += 1
138
+
139
+ if ( blocks = encodedBlockIndexMap . get ( index ) ) {
140
+ encodedBlockIndexMap . delete ( index )
141
+ for ( const block of blocks ) {
142
+ key = indicesToKey ( block . indices )
143
+ encodedBlockKeyMap . delete ( key )
144
+ this . propagateDecoded ( key , block )
145
+ }
146
+ }
94
147
}
95
148
}
96
149
@@ -135,3 +188,7 @@ export class LtDecoder {
135
188
throw new Error ( 'Checksum mismatch' )
136
189
}
137
190
}
191
+
192
+ function indicesToKey ( indices : number [ ] ) {
193
+ return indices . join ( ',' )
194
+ }
0 commit comments