1/**
2 * A thread-safe tree which caches inverted matrices.
3 *
4 * Copyright 2016, Peter Collins
5 */
6
7package reedsolomon
8
9import (
10	"errors"
11	"sync"
12)
13
14// The tree uses a Reader-Writer mutex to make it thread-safe
15// when accessing cached matrices and inserting new ones.
16type inversionTree struct {
17	mutex *sync.RWMutex
18	root  inversionNode
19}
20
21type inversionNode struct {
22	matrix   matrix
23	children []*inversionNode
24}
25
26// newInversionTree initializes a tree for storing inverted matrices.
27// Note that the root node is the identity matrix as it implies
28// there were no errors with the original data.
29func newInversionTree(dataShards, parityShards int) inversionTree {
30	identity, _ := identityMatrix(dataShards)
31	root := inversionNode{
32		matrix:   identity,
33		children: make([]*inversionNode, dataShards+parityShards),
34	}
35	return inversionTree{
36		mutex: &sync.RWMutex{},
37		root:  root,
38	}
39}
40
41// GetInvertedMatrix returns the cached inverted matrix or nil if it
42// is not found in the tree keyed on the indices of invalid rows.
43func (t inversionTree) GetInvertedMatrix(invalidIndices []int) matrix {
44	// Lock the tree for reading before accessing the tree.
45	t.mutex.RLock()
46	defer t.mutex.RUnlock()
47
48	// If no invalid indices were give we should return the root
49	// identity matrix.
50	if len(invalidIndices) == 0 {
51		return t.root.matrix
52	}
53
54	// Recursively search for the inverted matrix in the tree, passing in
55	// 0 as the parent index as we start at the root of the tree.
56	return t.root.getInvertedMatrix(invalidIndices, 0)
57}
58
59// errAlreadySet is returned if the root node matrix is overwritten
60var errAlreadySet = errors.New("the root node identity matrix is already set")
61
62// InsertInvertedMatrix inserts a new inverted matrix into the tree
63// keyed by the indices of invalid rows.  The total number of shards
64// is required for creating the proper length lists of child nodes for
65// each node.
66func (t inversionTree) InsertInvertedMatrix(invalidIndices []int, matrix matrix, shards int) error {
67	// If no invalid indices were given then we are done because the
68	// root node is already set with the identity matrix.
69	if len(invalidIndices) == 0 {
70		return errAlreadySet
71	}
72
73	if !matrix.IsSquare() {
74		return errNotSquare
75	}
76
77	// Lock the tree for writing and reading before accessing the tree.
78	t.mutex.Lock()
79	defer t.mutex.Unlock()
80
81	// Recursively create nodes for the inverted matrix in the tree until
82	// we reach the node to insert the matrix to.  We start by passing in
83	// 0 as the parent index as we start at the root of the tree.
84	t.root.insertInvertedMatrix(invalidIndices, matrix, shards, 0)
85
86	return nil
87}
88
89func (n inversionNode) getInvertedMatrix(invalidIndices []int, parent int) matrix {
90	// Get the child node to search next from the list of children.  The
91	// list of children starts relative to the parent index passed in
92	// because the indices of invalid rows is sorted (by default).  As we
93	// search recursively, the first invalid index gets popped off the list,
94	// so when searching through the list of children, use that first invalid
95	// index to find the child node.
96	firstIndex := invalidIndices[0]
97	node := n.children[firstIndex-parent]
98
99	// If the child node doesn't exist in the list yet, fail fast by
100	// returning, so we can construct and insert the proper inverted matrix.
101	if node == nil {
102		return nil
103	}
104
105	// If there's more than one invalid index left in the list we should
106	// keep searching recursively.
107	if len(invalidIndices) > 1 {
108		// Search recursively on the child node by passing in the invalid indices
109		// with the first index popped off the front.  Also the parent index to
110		// pass down is the first index plus one.
111		return node.getInvertedMatrix(invalidIndices[1:], firstIndex+1)
112	}
113	// If there aren't any more invalid indices to search, we've found our
114	// node.  Return it, however keep in mind that the matrix could still be
115	// nil because intermediary nodes in the tree are created sometimes with
116	// their inversion matrices uninitialized.
117	return node.matrix
118}
119
120func (n inversionNode) insertInvertedMatrix(invalidIndices []int, matrix matrix, shards, parent int) {
121	// As above, get the child node to search next from the list of children.
122	// The list of children starts relative to the parent index passed in
123	// because the indices of invalid rows is sorted (by default).  As we
124	// search recursively, the first invalid index gets popped off the list,
125	// so when searching through the list of children, use that first invalid
126	// index to find the child node.
127	firstIndex := invalidIndices[0]
128	node := n.children[firstIndex-parent]
129
130	// If the child node doesn't exist in the list yet, create a new
131	// node because we have the writer lock and add it to the list
132	// of children.
133	if node == nil {
134		// Make the length of the list of children equal to the number
135		// of shards minus the first invalid index because the list of
136		// invalid indices is sorted, so only this length of errors
137		// are possible in the tree.
138		node = &inversionNode{
139			children: make([]*inversionNode, shards-firstIndex),
140		}
141		// Insert the new node into the tree at the first index relative
142		// to the parent index that was given in this recursive call.
143		n.children[firstIndex-parent] = node
144	}
145
146	// If there's more than one invalid index left in the list we should
147	// keep searching recursively in order to find the node to add our
148	// matrix.
149	if len(invalidIndices) > 1 {
150		// As above, search recursively on the child node by passing in
151		// the invalid indices with the first index popped off the front.
152		// Also the total number of shards and parent index are passed down
153		// which is equal to the first index plus one.
154		node.insertInvertedMatrix(invalidIndices[1:], matrix, shards, firstIndex+1)
155	} else {
156		// If there aren't any more invalid indices to search, we've found our
157		// node.  Cache the inverted matrix in this node.
158		node.matrix = matrix
159	}
160}
161