rclone/vendor/github.com/vivint/infectious/gf_alg.go
2020-05-12 15:56:50 +00:00

423 lines
8.1 KiB
Go

// The MIT License (MIT)
//
// Copyright (C) 2016-2017 Vivint, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package infectious
import (
"fmt"
"strings"
"unsafe"
)
//
// basic helpers around gf(2^8) values
//
type gfVal byte
func gfConst(val byte) gfVal {
return gfVal(val)
}
func (b gfVal) pow(val int) gfVal {
out := gfVal(1)
mul_base := gf_mul_table[b][:]
for i := 0; i < val; i++ {
out = gfVal(mul_base[out])
}
return out
}
func (a gfVal) mul(b gfVal) gfVal {
return gfVal(gf_mul_table[a][b])
}
func (a gfVal) div(b gfVal) (gfVal, error) {
if b == 0 {
return 0, Error.New("divide by zero")
}
if a == 0 {
return 0, nil
}
return gfVal(gf_exp[gf_log[a]-gf_log[b]]), nil
}
func (a gfVal) add(b gfVal) gfVal {
return gfVal(a ^ b)
}
func (a gfVal) isZero() bool {
return a == 0
}
func (a gfVal) inv() (gfVal, error) {
if a == 0 {
return 0, Error.New("invert zero")
}
return gfVal(gf_exp[255-gf_log[a]]), nil
}
//
// basic helpers about a slice of gf(2^8) values
//
type gfVals []gfVal
func (a gfVals) unsafeBytes() []byte {
return *(*[]byte)(unsafe.Pointer(&a))
}
func (a gfVals) dot(b gfVals) gfVal {
out := gfConst(0)
for i := range a {
out = out.add(a[i].mul(b[i]))
}
return out
}
func (a gfVals) String() string {
return fmt.Sprintf("%02x", a.unsafeBytes())
}
//
// basic helpers for dealing with polynomials with coefficients in gf(2^8)
//
type gfPoly []gfVal
func polyZero(size int) gfPoly {
out := make(gfPoly, size)
for i := range out {
out[i] = gfConst(0)
}
return out
}
func (p gfPoly) isZero() bool {
for _, coef := range p {
if !coef.isZero() {
return false
}
}
return true
}
func (p gfPoly) deg() int {
return len(p) - 1
}
func (p gfPoly) index(power int) gfVal {
if power < 0 {
return gfConst(0)
}
which := p.deg() - power
if which < 0 {
return gfConst(0)
}
return p[which]
}
func (p gfPoly) scale(factor gfVal) gfPoly {
out := make(gfPoly, len(p))
for i, coef := range p {
out[i] = coef.mul(factor)
}
return out
}
func (p *gfPoly) set(pow int, coef gfVal) {
which := p.deg() - pow
if which < 0 {
*p = append(polyZero(-which), *p...)
which = p.deg() - pow
}
(*p)[which] = coef
}
func (p gfPoly) add(b gfPoly) gfPoly {
size := len(p)
if lb := len(b); lb > size {
size = lb
}
out := make(gfPoly, size)
for i := range out {
pi := p.index(i)
bi := b.index(i)
out.set(i, pi.add(bi))
}
return out
}
func (p gfPoly) div(b gfPoly) (q, r gfPoly, err error) {
// sanitize the divisor by removing leading zeros.
for len(b) > 0 && b[0].isZero() {
b = b[1:]
}
if len(b) == 0 {
return nil, nil, Error.New("divide by zero")
}
// sanitize the base poly as well
for len(p) > 0 && p[0].isZero() {
p = p[1:]
}
if len(p) == 0 {
return polyZero(1), polyZero(1), nil
}
const debug = false
indent := 2*len(b) + 1
if debug {
fmt.Printf("%02x %02x\n", b, p)
}
for b.deg() <= p.deg() {
leading_p := p.index(p.deg())
leading_b := b.index(b.deg())
if debug {
fmt.Printf("leading_p: %02x leading_b: %02x\n",
leading_p, leading_b)
}
coef, err := leading_p.div(leading_b)
if err != nil {
return nil, nil, err
}
if debug {
fmt.Printf("coef: %02x\n", coef)
}
q = append(q, coef)
scaled := b.scale(coef)
padded := append(scaled, polyZero(p.deg()-scaled.deg())...)
if debug {
fmt.Printf("%s%02x\n", strings.Repeat(" ", indent), padded)
indent += 2
}
p = p.add(padded)
if !p[0].isZero() {
return nil, nil, Error.New("alg error: %x", p)
}
p = p[1:]
}
for len(p) > 1 && p[0].isZero() {
p = p[1:]
}
return q, p, nil
}
func (p gfPoly) eval(x gfVal) gfVal {
out := gfConst(0)
for i := 0; i <= p.deg(); i++ {
x_i := x.pow(i)
p_i := p.index(i)
out = out.add(p_i.mul(x_i))
}
return out
}
//
// basic helpers for matrices in gf(2^8)
//
type gfMat struct {
d gfVals
r, c int
}
func matrixNew(i, j int) gfMat {
return gfMat{
d: make(gfVals, i*j),
r: i, c: j,
}
}
func (m gfMat) String() (out string) {
if m.r == 0 {
return ""
}
for i := 0; i < m.r-1; i++ {
out += fmt.Sprintln(m.indexRow(i))
}
out += fmt.Sprint(m.indexRow(m.r - 1))
return out
}
func (m gfMat) index(i, j int) int {
return m.c*i + j
}
func (m gfMat) get(i, j int) gfVal {
return m.d[m.index(i, j)]
}
func (m gfMat) set(i, j int, val gfVal) {
m.d[m.index(i, j)] = val
}
func (m gfMat) indexRow(i int) gfVals {
return m.d[m.index(i, 0):m.index(i+1, 0)]
}
func (m gfMat) swapRow(i, j int) {
tmp := make(gfVals, m.r)
ri := m.indexRow(i)
rj := m.indexRow(j)
copy(tmp, ri)
copy(ri, rj)
copy(rj, tmp)
}
func (m gfMat) scaleRow(i int, val gfVal) {
ri := m.indexRow(i)
for i := range ri {
ri[i] = ri[i].mul(val)
}
}
func (m gfMat) addmulRow(i, j int, val gfVal) {
ri := m.indexRow(i)
rj := m.indexRow(j)
addmul(rj.unsafeBytes(), ri.unsafeBytes(), byte(val))
}
// in place invert. the output is put into a and m is turned into the identity
// matrix. a is expected to be the identity matrix.
func (m gfMat) invertWith(a gfMat) error {
for i := 0; i < m.r; i++ {
p_row, p_val := i, m.get(i, i)
for j := i + 1; j < m.r && p_val.isZero(); j++ {
p_row, p_val = j, m.get(j, i)
}
if p_val.isZero() {
continue
}
if p_row != i {
m.swapRow(i, p_row)
a.swapRow(i, p_row)
}
inv, err := p_val.inv()
if err != nil {
return err
}
m.scaleRow(i, inv)
a.scaleRow(i, inv)
for j := i + 1; j < m.r; j++ {
leading := m.get(j, i)
m.addmulRow(i, j, leading)
a.addmulRow(i, j, leading)
}
}
for i := m.r - 1; i > 0; i-- {
for j := i - 1; j >= 0; j-- {
trailing := m.get(j, i)
m.addmulRow(i, j, trailing)
a.addmulRow(i, j, trailing)
}
}
return nil
}
// in place standardize.
func (m gfMat) standardize() error {
for i := 0; i < m.r; i++ {
p_row, p_val := i, m.get(i, i)
for j := i + 1; j < m.r && p_val.isZero(); j++ {
p_row, p_val = j, m.get(j, i)
}
if p_val.isZero() {
continue
}
if p_row != i {
m.swapRow(i, p_row)
}
inv, err := p_val.inv()
if err != nil {
return err
}
m.scaleRow(i, inv)
for j := i + 1; j < m.r; j++ {
leading := m.get(j, i)
m.addmulRow(i, j, leading)
}
}
for i := m.r - 1; i > 0; i-- {
for j := i - 1; j >= 0; j-- {
trailing := m.get(j, i)
m.addmulRow(i, j, trailing)
}
}
return nil
}
// parity returns the new matrix because it changes dimensions and stuff. it
// can be done in place, but is easier to implement with a copy.
func (m gfMat) parity() gfMat {
// we assume m is in standard form already
// it is of form [I_r | P]
// our output will be [-P_transpose | I_(c - r)]
// but our field is of characteristic 2 so we do not need the negative.
// In terms of m:
// I_r has r rows and r columns.
// P has r rows and c-r columns.
// P_transpose has c-r rows, and r columns.
// I_(c-r) has c-r rows and c-r columns.
// so: out.r == c-r, out.c == r + c - r == c
out := matrixNew(m.c-m.r, m.c)
// step 1. fill in the identity. it starts at column offset r.
for i := 0; i < m.c-m.r; i++ {
out.set(i, i+m.r, gfConst(1))
}
// step 2: fill in the transposed P matrix. i and j are in terms of out.
for i := 0; i < m.c-m.r; i++ {
for j := 0; j < m.r; j++ {
out.set(i, j, m.get(j, i+m.r))
}
}
return out
}