From b07839f7ef4b7881b280b54f4d832522fe9d8549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ho=CC=88gborg?= Date: Thu, 12 Nov 2015 21:42:30 +0100 Subject: [PATCH] Payload correction using parity check bits --- .gitignore | 3 +- internal/pocsag/pocsag.go | 144 ++++++++++++++++++++++++++++----- internal/pocsag/pocsag_test.go | 121 +++++++++++++++++++++++++++ internal/pocsag/stream.go | 12 +-- internal/utils/util.go | 24 ++++++ 5 files changed, 275 insertions(+), 29 deletions(-) create mode 100644 internal/pocsag/pocsag_test.go diff --git a/.gitignore b/.gitignore index 3b7911c..3e6c92a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -gopocsag \ No newline at end of file +gopocsag +samples diff --git a/internal/pocsag/pocsag.go b/internal/pocsag/pocsag.go index f2716a2..e54929f 100644 --- a/internal/pocsag/pocsag.go +++ b/internal/pocsag/pocsag.go @@ -125,14 +125,14 @@ func (p *POCSAG) ParseMessages(batches []*Batch) []*Message { switch codeword.Type { // append current and begin new message case CodewordTypeAddress: - if message != nil { + if message != nil && len(message.Payload) > 0 { messages = append(messages, message) } message = NewMessage(codeword) // append current but dont start new case CodewordTypeIdle: - if message != nil { + if message != nil && len(message.Payload) > 0 { messages = append(messages, message) } message = nil @@ -182,6 +182,10 @@ func (m *Message) Print(messagetype MessageType) { red.Println("This message has parity check errors. Contents might be corrupted") } + if DEBUG && m.biterrors() > 0 { + red.Println(m.biterrors(), "bits corrected by parity check") + } + println("") print(m.PayloadString(messagetype)) println("") @@ -245,6 +249,14 @@ func (m *Message) IsValid() bool { return true } +func (m *Message) biterrors() (errors int) { + errors = m.Reciptient.BitCorrections + for _, c := range m.Payload { + errors += c.BitCorrections + } + return +} + // PayloadString can try to decide to print the message as bitcoded decimal ("bcd") or // as an alphanumeric string. There is not always a clear indication which is correct, // so we can force either type by setting messagetype to something other than Auto. @@ -434,6 +446,8 @@ type Codeword struct { ParityBits []datatypes.Bit EvenParity datatypes.Bit ValidParity bool + + BitCorrections int } // NewCodeword takes 32 bits, creates a new codeword construct, sets the type and checks for parity errors. @@ -442,6 +456,8 @@ func NewCodeword(bits []datatypes.Bit) (*Codeword, error) { return nil, fmt.Errorf("invalid number of bits for codeword: ", len(bits)) } + bits, corrected := BitCorrection(bits) + mtype := CodewordTypeAddress if bits[0] == true { mtype = CodewordTypeMessage @@ -453,16 +469,69 @@ func NewCodeword(bits []datatypes.Bit) (*Codeword, error) { } c := &Codeword{ - Type: mtype, - Payload: bits[1:21], - ParityBits: bits[21:31], - EvenParity: bits[31], - ValidParity: utils.ParityCheck(bits[0:31], bits[31]), + Type: mtype, + Payload: bits[1:21], + ParityBits: bits[21:31], + EvenParity: bits[31], + ValidParity: (syndrome(bits) == 0) && utils.ParityCheck(bits[:31], bits[31]), + BitCorrections: corrected, } return c, nil } +// BitCorrection will attempt to brute-force the codeword to make it validate +// with the parity bits. This can correct up to two errounous bits from transmission. +func BitCorrection(inbits []datatypes.Bit) (bits []datatypes.Bit, corrections int) { + + bits = make([]datatypes.Bit, 32) + corrections = 0 + + copy(bits, inbits) + + if syndrome(bits) == 0 { + // valid message + return + } + + // test for single bit errors + for a := 0; a < 31; a += 1 { + + bits_x := make([]datatypes.Bit, 32) + copy(bits_x, bits) + + bits_x[a] = !bits_x[a] + + if syndrome(bits_x) == 0 { + bits = bits_x + corrections = 1 + return + } + + // test double bit errors + for b := 0; b < 31; b += 1 { + + // dont flip-flip (negate) the previous change + if b != a { + + bits_xx := make([]datatypes.Bit, 32) + copy(bits_xx, bits_x) + + bits_xx[b] = !bits_xx[b] + + if syndrome(bits_xx) == 0 { + bits = bits_xx + corrections = 2 + return + } + } + } + + } + + return +} + // Print the codeword contents and type to terminal. For debugging. func (c *Codeword) Print() { @@ -485,10 +554,16 @@ func (c *Codeword) Print() { } parity := utils.TernaryStr(c.ValidParity, "", "*") + color.Printf("%s %s %s ", c.Type, payload, parity) + corr := c.BitCorrections + if corr > 0 { + color.Printf("%d bits corrected", corr) + } - color.Printf("%s %s %s\n", c.Type, payload, parity) + println("") } +// Print the address for debugging func (c *Codeword) Adress() string { bytes := utils.MSBBitsToBytes(c.Payload[0:17], 8) addr := uint(bytes[1]) @@ -501,27 +576,52 @@ func (c *Codeword) Adress() string { } // Utilities + // isPreamble matches 4 bytes to the POCSAG preamble 0x7CD215D8 func isPreamble(bytes []byte) bool { - - var a uint32 = 0 - a += uint32(bytes[0]) << 24 - a += uint32(bytes[1]) << 16 - a += uint32(bytes[2]) << 8 - a += uint32(bytes[3]) - - return a == POCSAG_PREAMBLE + return utils.Btouint32(bytes) == POCSAG_PREAMBLE } // isIdle matches 4 bytes to the POCSAG idle codeword 0x7A89C197 func isIdle(bytes []byte) bool { + return utils.Btouint32(bytes) == POCSAG_IDLE +} + +const ( + BHC_COEFF = 0xED200000 + BCH_N = 31 + BCH_K = 21 +) + +// syndrome takes a bitstream and uses the parity bits from the BCH polynomial +// generator to calculate if the bits are received correctly. +// A 0 return means the bits are correct. +// Thanks to multimon-ng (https://github.com/EliasOenal/multimon-ng) for +// detailing implmentation of this. +func syndrome(bits []datatypes.Bit) uint32 { + bytes := utils.MSBBitsToBytes(bits, 8) + + // take the parity-bit out from our codeword + codeword := utils.Btouint32(bytes) >> 1 + + // put the mask bit to the far left in the bitstream + mask := uint32(1 << (BCH_N)) + coeff := uint32(BHC_COEFF) - var a uint32 = 0 - a += uint32(bytes[0]) << 24 - a += uint32(bytes[1]) << 16 - a += uint32(bytes[2]) << 8 - a += uint32(bytes[3]) + // step over each data-bit (the first 21) + for a := 0; a < BCH_K; a += 1 { + // step the coefficient and mask right in the bitstream + mask >>= 1 + coeff >>= 1 + + // if the current bit in the codeword is 1 then XOR the codeword with the coefficient + if (codeword & mask) > 0 { + codeword = codeword ^ coeff + } + } - return a == POCSAG_IDLE + // in the end, if the coefficient matches the codeword they + // are canceled out by the XOR, returning 0 + return codeword } diff --git a/internal/pocsag/pocsag_test.go b/internal/pocsag/pocsag_test.go new file mode 100644 index 0000000..e2fe474 --- /dev/null +++ b/internal/pocsag/pocsag_test.go @@ -0,0 +1,121 @@ +package pocsag + +import ( + . "gopkg.in/check.v1" + "testing" + + "github.com/dhogborg/go-pocsag/internal/datatypes" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { TestingT(t) } + +var _ = Suite(&PocsagSuite{}) + +type PocsagSuite struct{} + +func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Address(c *C) { + // valid bitstream + bits := bitstream("01010001111011110011110111000010") + syndr := syndrome(bits) + + c.Assert(syndr > 0, Equals, false) +} + +func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Idle(c *C) { + // valid bitstream + bits := bitstream("01111010100010011100000110010111") + syndr := syndrome(bits) + + c.Assert(syndr > 0, Equals, false) +} + +func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Message(c *C) { + // valid bitstream + bits := bitstream("11001101100000000000011110001100") + syndr := syndrome(bits) + + c.Assert(syndr > 0, Equals, false) +} + +func (f *PocsagSuite) Test_Syndrome_Calculation_Error_1(c *C) { + // v error + bits := bitstream("01010101111011110011110111000010") + syndr := syndrome(bits) + + c.Assert(syndr > 0, Equals, true) +} + +func (f *PocsagSuite) Test_Syndrome_Calculation_Error_2(c *C) { + // v v errors + bits := bitstream("01110101111011110011110111000010") + syndr := syndrome(bits) + + c.Assert(syndr > 0, Equals, true) + +} + +func (f *PocsagSuite) Test_BitCorrection_No_Rrror(c *C) { + + bits := bitstream("01010001111011110011110111000010") + cbits, corr := BitCorrection(bits) + stream := streambits(cbits) + + c.Assert(corr, Equals, 0) + c.Assert(stream, Equals, "01010001111011110011110111000010") +} + +func (f *PocsagSuite) Test_BitCorrection_PayloadError(c *C) { + // v error + bits := bitstream("01010101111011110011110111000010") + cbits, corr := BitCorrection(bits) + stream := streambits(cbits) + + c.Assert(corr, Equals, 1) + c.Assert(stream, Equals, "01010001111011110011110111000010") +} + +func (f *PocsagSuite) Test_BitCorrection_PayloadErrors(c *C) { + + // v errors v + bits := bitstream("01010101111011110011010111000010") + cbits, corr := BitCorrection(bits) + stream := streambits(cbits) + + c.Assert(corr, Equals, 2) + c.Assert(stream, Equals, "01010001111011110011110111000010") +} + +func (f *PocsagSuite) Test_BitCorrection_ParityError(c *C) { + // v + bits := bitstream("01010001111011110011110111010010") + cbits, corr := BitCorrection(bits) + stream := streambits(cbits) + + c.Assert(corr, Equals, 1) + c.Assert(stream, Equals, "01010001111011110011110111000010") +} + +func bitstream(stream string) []datatypes.Bit { + bits := make([]datatypes.Bit, 32) + for i, c := range stream { + if string(c) == "1" { + bits[i] = datatypes.Bit(true) + } else { + bits[i] = datatypes.Bit(false) + } + } + return bits +} + +func streambits(bits []datatypes.Bit) string { + stream := "" + for _, c := range bits { + if c { + stream += "1" + } else { + stream += "0" + } + } + return stream +} diff --git a/internal/pocsag/stream.go b/internal/pocsag/stream.go index 4dc0d3e..100a8cc 100644 --- a/internal/pocsag/stream.go +++ b/internal/pocsag/stream.go @@ -29,14 +29,14 @@ func NewStreamReader(source io.Reader, bauds int) *StreamReader { } // StartScan takes a channel on which bitstreams will be written when found and parsed. -// The scanner will continue indefently and sleep for 3 ms per cycle to go easy on the system load. +// The scanner will continue indefently or to EOF is reached func (s *StreamReader) StartScan(bitstream chan []datatypes.Bit) { fmt.Println("Starting transmission scanner") for { - bytes := make([]byte, 4096) + bytes := make([]byte, 8192) c, err := s.Stream.Read(bytes) if err != nil { @@ -75,7 +75,7 @@ func (s *StreamReader) ReadTransmission(beginning []int16) []int16 { for { - bytes := make([]byte, 4096) + bytes := make([]byte, 8192) c, _ := s.Stream.Read(bytes) if c > 0 { @@ -146,8 +146,8 @@ func (s *StreamReader) ScanTransmissionStart(stream []int16) (int, int) { } if DEBUG { - fmt.Println("Mean bitlength:", mean_bitlength) - fmt.Println("Determined bitlength:", bitlength) + blue.Println("Mean bitlength:", mean_bitlength) + blue.Println("Determined bitlength:", bitlength) } // look at every other sample to see if we have a repeating pattern with matching size @@ -180,7 +180,7 @@ func (s *StreamReader) ScanTransmissionStart(stream []int16) (int, int) { if confidence > 10 { if DEBUG { - fmt.Println("Found bitsync") + blue.Println("Found bitsync") } return switches[a] + int(bitlength/2), int(bitlength) diff --git a/internal/utils/util.go b/internal/utils/util.go index bf6bcb8..0104f1d 100644 --- a/internal/utils/util.go +++ b/internal/utils/util.go @@ -172,6 +172,17 @@ func bcdChar(foo uint8) string { return chars[foo-10] } +func Btouint32(bytes []byte) uint32 { + + var a uint32 = 0 + a += uint32(bytes[0]) << 24 + a += uint32(bytes[1]) << 16 + a += uint32(bytes[2]) << 8 + a += uint32(bytes[3]) + + return a +} + func TernaryStr(condition bool, a, b string) string { if condition { return a @@ -204,3 +215,16 @@ func PrintSample(sample int16) { red.Printf("%d ", sample) } } + +func PrintUint32(i uint32) { + var x uint32 = 1 << 31 + for a := 0; a < 32; a += 1 { + if (i & x) > 0 { + green.Print("1 ") + } else { + red.Print("0 ") + } + x >>= 1 + } + println("") +}