diff --git a/bptc/bptc.go b/bptc/bptc.go new file mode 100644 index 0000000..f6eecc9 --- /dev/null +++ b/bptc/bptc.go @@ -0,0 +1,287 @@ +// Package bptc implements the BPTC(196, 96) Block Product Turbo Code +package bptc + +import ( + "errors" + "fmt" +) + +type Bit byte + +type vector [4]Bit + +// hamming(15, 11, 3) checking of a matrix row (15 total bits, 11 data bits, +// min. distance: 3) See page 135 of the DMR Air Interface protocol +// specification for the generator matrix. A generator matrix looks like this: +// G = [Ik | P]. The parity check matrix is: H = [-P^T|In-k] In binary codes, +// then -P = P, so the negation is unnecessary. We can get the parity check +// matrix only by transposing the generator matrix. We then take a data row, +// and multiply it with each row of the parity check matrix, then xor each +// resulting row bits together with the corresponding parity check bit. The xor +// result (error vector) should be 0, if it's not, it can be used to determine +// the location of the erroneous bit using the generator matrix (P). +func hamming_15_11_3_parity(data []Bit, errorVector *vector) { + if data == nil || len(data) < 11 || errorVector == nil { + return + } + + var e = *errorVector + e[0] = (data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[5] ^ data[7] ^ data[8]) + e[1] = (data[1] ^ data[2] ^ data[3] ^ data[4] ^ data[6] ^ data[8] ^ data[9]) + e[2] = (data[2] ^ data[3] ^ data[4] ^ data[5] ^ data[7] ^ data[9] ^ data[10]) + e[3] = (data[0] ^ data[1] ^ data[2] ^ data[4] ^ data[6] ^ data[7] ^ data[10]) +} + +func hamming_15_11_3_check(data []Bit, errorVector *vector) bool { + if data == nil || len(data) < 15 || errorVector == nil { + return false + } + + hamming_15_11_3_parity(data, errorVector) + + var e = *errorVector + e[0] ^= data[11] + e[1] ^= data[12] + e[2] ^= data[13] + e[3] ^= data[14] + + return e[0] == 0 && e[1] == 0 && e[2] == 0 && e[3] == 0 +} + +func hamming_13_9_3_parity(data []Bit, errorVector *vector) { + if data == nil || len(data) < 9 || errorVector == nil { + return + } + + var e = *errorVector + e[0] = (data[0] ^ data[1] ^ data[3] ^ data[5] ^ data[6]) + e[1] = (data[0] ^ data[1] ^ data[2] ^ data[4] ^ data[6] ^ data[7]) + e[2] = (data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[5] ^ data[7] ^ data[8]) + e[3] = (data[0] ^ data[2] ^ data[4] ^ data[5] ^ data[8]) +} + +// hamming(13, 9, 3) checking of a matrix column (13 total bits, 9 data bits, +// min. distance: 3) +func hamming_13_9_3_check(data []Bit, errorVector *vector) bool { + if data == nil || len(data) < 13 || errorVector == nil { + return false + } + + hamming_13_9_3_parity(data, errorVector) + + var e = *errorVector + e[0] ^= data[9] + e[1] ^= data[10] + e[2] ^= data[11] + e[3] ^= data[12] + + return e[0] == 0 && e[1] == 0 && e[2] == 0 && e[3] == 0 +} + +var hamming_15_11_generator_matrix = []Bit{ + 1, 0, 0, 1, + 1, 1, 0, 1, + 1, 1, 1, 1, + 1, 1, 1, 0, + 0, 1, 1, 1, + 1, 0, 1, 0, + 0, 1, 0, 1, + 1, 0, 1, 1, + 1, 1, 0, 0, + 0, 1, 1, 0, + 0, 0, 1, 1, + + 1, 0, 0, 0, // These are used to determine errors in the hamming checksum bits. + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, +} + +func hamming_15_11_3_error_position(errorVector *vector) int { + if errorVector == nil { + return -1 + } + var e = *errorVector + for row := 0; row < 15; row++ { + if hamming_15_11_generator_matrix[row*4] == e[0] && + hamming_15_11_generator_matrix[row*4+1] == e[1] && + hamming_15_11_generator_matrix[row*4+2] == e[2] && + hamming_15_11_generator_matrix[row*4+3] == e[3] { + return row + } + } + return -1 +} + +var hamming_13_9_generator_matrix = []Bit{ + 1, 1, 1, 1, + 1, 1, 1, 0, + 0, 1, 1, 1, + 0, 1, 1, 1, + 0, 1, 0, 1, + 1, 0, 1, 1, + 1, 1, 0, 0, + 0, 1, 1, 0, + 0, 0, 1, 1, + + 1, 0, 0, 0, // These are used to determine errors in the hamming checksum bits. + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, +} + +func hamming_13_9_3_error_position(errorVector *vector) int { + if errorVector == nil { + return -1 + } + var e = *errorVector + for row := 0; row < 13; row++ { + if hamming_13_9_generator_matrix[row*4] == e[0] && + hamming_13_9_generator_matrix[row*4+1] == e[1] && + hamming_13_9_generator_matrix[row*4+2] == e[2] && + hamming_13_9_generator_matrix[row*4+3] == e[3] { + return row + } + } + return -1 +} + +func Dump(bits []Bit) { + if len(bits) != 196 { + return + } + + var row, col int + + fmt.Println(" BPTC(196, 96) matrix:") + for row = 0; row < 13; row++ { + for col = 0; col < 11; col++ { + // +1 because the first bit is R(3) and it's not used + // so we can ignore that. + fmt.Printf(" #%.2u ", bits[col+row*15+1]) + } + fmt.Print(" ") + for ; col < 15; col++ { + // +1 because the first bit is R(3) and it's not used + // so we can ignore that. + fmt.Printf("%u", bits[col+row*15+1]) + } + fmt.Println("") + if row == 8 { + fmt.Println("") + } + } +} + +func CheckAndRepair(bits []Bit) (bool, error) { + if bits == nil || len(bits) != 196 { + return false, errors.New("expected 196 input bits") + } + + var ( + cb = make([]Bit, 13) + errorVector = vector{} + ) + for col := 0; col < 15; col++ { + for row := 0; row < 13; row++ { + // +1 because the first bit is R(3) and it's not used so we can ignore that. + cb[row] = bits[col+row*15+1] + } + + if !hamming_13_9_3_check(cb, &errorVector) { + wrong := hamming_13_9_3_error_position(&errorVector) + if wrong < 0 { + return false, fmt.Errorf("dmr/bptc(196, 96): hamming(13, 9) check error in column #%u, can't repair", col) + } + + // Fix bit error + bits[col+wrong*15+1] ^= 1 + for row := 0; row < 13; row++ { + // +1 because the first bit is R(3) and it's not used so we can ignore that. + cb[row] = bits[col+row*15+1] + } + + if !hamming_13_9_3_check(cb, &errorVector) { + return false, fmt.Errorf("dmr/bptc(196, 96): hamming(13, 9) check error in column #%u, couldn't repair", col) + } + } + } + + for row := 0; row < 9; row++ { + // +1 because the first bit is R(3) and it's not used so we can ignore that. + if !hamming_15_11_3_check(bits[row*15+1:], &errorVector) { + wrong := hamming_15_11_3_error_position(&errorVector) + if wrong < 0 { + return false, fmt.Errorf("dmr/bptc(196, 96): hamming(15, 11) check error in row #%u, can't repair", row) + } + + // Fix bit error + bits[row*15+wrong+1] ^= 1 + if !hamming_15_11_3_check(bits[row*15+1:], &errorVector) { + return false, fmt.Errorf("dmr/bptc (196,96): hamming(15,11) check error, couldn't repair row #%u", row) + } + } + } + + return true, nil +} + +// Extract the data bits from the given deinterleaved info bits array (discards BPTC bits). +func Extract(bits []Bit) []Bit { + var e = make([]Bit, 96) + copy(e[0:8], bits[4:12]) + copy(e[8:19], bits[16:27]) + copy(e[19:30], bits[31:42]) + copy(e[30:41], bits[46:57]) + copy(e[41:52], bits[61:72]) + copy(e[52:63], bits[76:87]) + copy(e[63:74], bits[91:102]) + copy(e[74:85], bits[106:117]) + copy(e[85:96], bits[121:132]) + return e +} + +// New BPTC(196, 96) payload from 96 data bits. +func New(bits []Bit) []Bit { + var ( + dbp int + errorVector = vector{} + p = make([]Bit, 196) + ) + + for row := 0; row < 9; row++ { + if row == 0 { + for col := 3; col < 11; col++ { + // +1 because the first bit is R(3) and it's not used so we can ignore that. + p[col+1] = bits[dbp] + dbp++ + } + } else { + for col := 0; col < 11; col++ { + // +1 because the first bit is R(3) and it's not used so we can ignore that. + p[col+row*15+1] = bits[dbp] + dbp++ + } + } + // +1 because the first bit is R(3) and it's not used so we can ignore that. + hamming_15_11_3_parity(bits[row*15+1:], &errorVector) + bits[row*15+11+1] = errorVector[0] + bits[row*15+12+1] = errorVector[1] + bits[row*15+13+1] = errorVector[2] + bits[row*15+14+1] = errorVector[3] + } + + for col := 0; col < 15; col++ { + var cb = make([]Bit, 9) + for row := 0; row < 9; row++ { + cb[row] = bits[col+row*15+1] + } + hamming_13_9_3_parity(cb, &errorVector) + bits[col+135+1] = errorVector[0] + bits[col+135+15+1] = errorVector[1] + bits[col+135+30+1] = errorVector[2] + bits[col+135+45+1] = errorVector[3] + } + + return p +}