You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

628 lines
14 KiB
Go

package pocsag
import (
"fmt"
"os"
"strings"
"time"
"github.com/dhogborg/go-pocsag/internal/datatypes"
"github.com/dhogborg/go-pocsag/internal/utils"
"github.com/fatih/color"
)
const (
POCSAG_PREAMBLE uint32 = 0x7CD215D8
POCSAG_IDLE uint32 = 0x7A89C197
POCSAG_BATCH_LEN int = 512
POCSAG_CODEWORD_LEN int = 32
)
type CodewordType string
const (
CodewordTypeAddress CodewordType = "ADDRESS"
CodewordTypeMessage CodewordType = "MESSAGE"
CodewordTypeIdle CodewordType = "IDLE"
)
type MessageType string
const (
MessageTypeAuto MessageType = "auto"
MessageTypeAlphanumeric MessageType = "alpha"
MessageTypeBitcodedDecimal MessageType = "bcd"
)
// ParsePOCSAG takes bits decoded from the stream and parses them for
// batches of codewords then prints them using the specefied message type.
func ParsePOCSAG(bits []datatypes.Bit, messagetype MessageType) []*Message {
pocsag := &POCSAG{}
batches, err := pocsag.ParseBatches(bits)
if err != nil {
println(err.Error())
return []*Message{}
}
if DEBUG && LEVEL > 1 {
for i, batch := range batches {
println("")
println("Batch: ", i)
batch.Print()
}
}
return pocsag.ParseMessages(batches)
}
type POCSAG struct{}
// ParseBatches takes bits decoded from the stream and parses them for
// batches of codewords.
func (p *POCSAG) ParseBatches(bits []datatypes.Bit) ([]*Batch, error) {
batches := []*Batch{}
var start = -1
var batchno = -1
// synchornize with the decoded bits
for a := 0; a < len(bits)-32; a += 1 {
bytes := utils.MSBBitsToBytes(bits[a:a+32], 8)
if isPreamble(bytes) {
batchno += 1
start = a + 32
// for file output as bin data
batchbits := bits[a : a+POCSAG_BATCH_LEN+32]
stream := utils.MSBBitsToBytes(batchbits, 8)
if DEBUG && LEVEL > 2 {
out, err := os.Create(fmt.Sprintf("batches/batch-%d.bin", batchno))
if err != nil {
return nil, err
}
out.Write(stream)
}
batch, err := NewBatch(bits[start : start+POCSAG_BATCH_LEN])
if err != nil {
println(err.Error())
} else {
batches = append(batches, batch)
}
}
}
if start < 0 {
return nil, fmt.Errorf("could not obtain message sync")
}
return batches, nil
}
// ParseMessages takes a bundle of codeword from a series of batches and
// compiles them into messages.
// A message starts with an address codeword and a bunch of message codewords follows
// until either the batch ends or an idle codeword appears.
func (p *POCSAG) ParseMessages(batches []*Batch) []*Message {
messages := []*Message{}
var message *Message
for _, b := range batches {
for _, codeword := range b.Codewords {
switch codeword.Type {
// append current and begin new message
case CodewordTypeAddress:
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 && len(message.Payload) > 0 {
messages = append(messages, message)
}
message = nil
case CodewordTypeMessage:
if message != nil {
message.AddPayload(codeword)
} else {
red.Println("Message codeword without sync!")
}
default:
red.Println("Unknown codeword encounterd")
}
}
}
if message != nil {
messages = append(messages, message)
}
return messages
}
// Message construct holds refernces to codewords.
// The Payload is a seies of codewords of message type.
type Message struct {
Timestamp time.Time
Reciptient *Codeword
Payload []*Codeword
}
// NewMessage creates a new message construct ready to accept payload codewords
func NewMessage(reciptient *Codeword) *Message {
return &Message{
Timestamp: time.Now(),
Reciptient: reciptient,
Payload: []*Codeword{},
}
}
func (m *Message) Print(messagetype MessageType) {
green.Println("-- Message --------------")
green.Println("Reciptient: ", m.ReciptientString())
if !m.IsValid() {
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("")
println("")
}
func (m *Message) Write(path string, messagetype MessageType) {
if !os.IsPathSeparator(path[len(path)-1]) {
path += "/"
}
now := time.Now()
timestr := now.Format("20060102_15.04.05")
file, err := os.Create(path + m.ReciptientString() + "_" + timestr + ".txt")
defer file.Close()
if err != nil {
println("error creating file: " + err.Error())
return
}
file.WriteString("Time: " + now.String() + "\n")
file.WriteString("Reciptient: " + m.ReciptientString() + "\n")
file.WriteString("-------------------\n")
file.WriteString(m.PayloadString(messagetype) + "\n")
}
// AddPayload codeword to a message. Must be codeword of CodewordTypeMessage type
// to make sense.
func (m *Message) AddPayload(codeword *Codeword) {
m.Payload = append(m.Payload, codeword)
}
// ReciptientString returns the reciptient address as a hexadecimal representation,
// with the function bits as 0 or 1.
func (m *Message) ReciptientString() string {
bytes := utils.MSBBitsToBytes(m.Reciptient.Payload[0:17], 8)
addr := uint(bytes[1])
addr += uint(bytes[0]) << 8
return fmt.Sprintf("%X%d%d", addr,
m.Reciptient.Payload[18].Int(),
m.Reciptient.Payload[19].Int())
}
// IsValid returns true if no parity bit check errors occurs in the message payload
// or the reciptient address.
func (m *Message) IsValid() bool {
if !m.Reciptient.ValidParity {
return false
}
for _, c := range m.Payload {
if !c.ValidParity {
return false
}
}
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.
func (m *Message) PayloadString(messagetype MessageType) string {
bits := m.concactenateBits()
alphanum := m.AlphaPayloadString(bits)
bcd := utils.BitcodedDecimals(bits)
var decided = MessageTypeAuto
if messagetype == MessageTypeAuto {
decided = m.estimateMessageType(alphanum, bcd)
} else {
decided = messagetype
}
switch decided {
case MessageTypeAlphanumeric:
return alphanum
case MessageTypeBitcodedDecimal:
return bcd
default:
return alphanum
}
}
// AlphaPayloadString takes bits in LSB to MSB order and decodes them as
// 7 bit bytes that will become ASCII text.
// Characters outside of ASCII can occur, so we substitude the most common.
func (m *Message) AlphaPayloadString(bits []datatypes.Bit) string {
str := string(utils.LSBBitsToBytes(bits, 7))
// translate to utf8
charmap := map[string]string{
"[": "Ä",
"\\": "Ö",
"]": "Å",
"{": "ä",
"|": "ö",
"}": "å",
"~": "ß",
}
for b, s := range charmap {
str = strings.Replace(str, b, s, -1)
}
return str
}
// concactenateBits to a single bitstream
func (m *Message) concactenateBits() []datatypes.Bit {
msgsbits := []datatypes.Bit{}
for _, cw := range m.Payload {
if cw.Type == CodewordTypeMessage {
msgsbits = append(msgsbits, cw.Payload...)
}
}
return msgsbits
}
// estimateMessageType tries to figure out if a message is in alpha-numeric format
// or Bitcoded decimal format. There is not always a clear indication which is correct,
// so we try to guess based on some assumptions:
// 1) Alpha numeric messages contains mostly printable charaters.
// 2) BCD messages are usually shorter.
func (m *Message) estimateMessageType(persumed_alpha, persumed_bcd string) MessageType {
// MessageTypeAuto...
// Start guessing
odds_a := 0
odds_b := 0
specials_a := 0
specials_b := 0
const alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 *.,-()<>\n\r"
const bcdnum = "0123456789"
for _, char := range persumed_alpha {
r := rune(char)
if strings.IndexRune(alphanum, r) < 0 {
specials_a++
}
}
for _, char := range persumed_bcd {
r := rune(char)
if strings.IndexRune(bcdnum, r) < 0 {
specials_b++
}
}
partspecial_a := float32(specials_a) / float32(len(persumed_alpha))
partspecial_b := float32(specials_b) / float32(len(persumed_bcd))
if partspecial_a < 0.2 {
odds_a += 2
}
if partspecial_a >= 0.2 {
odds_a += 1
}
if partspecial_a >= 0.50 {
odds_b += 2
}
// special charaters are uncommon in bcd messages
if partspecial_b == 0 {
odds_b += 2
}
if partspecial_b >= 0.1 {
odds_a += 1
}
if len(persumed_alpha) > 25 {
odds_a += 2
}
if len(persumed_alpha) > 40 {
odds_a += 3
}
if DEBUG {
red.Printf("odds: %d/%d\nspecial: %d/%d (%0.0f%%)\n\n", odds_a, odds_b, specials_a, specials_b, (partspecial_a * 100))
}
if odds_a > odds_b {
return MessageTypeAlphanumeric
} else {
return MessageTypeBitcodedDecimal
}
}
//-----------------------------
// Batch
// Contains codewords. We dont care about frames, we keep the 16 codewords in a single list.
type Batch struct {
Codewords []*Codeword
}
func NewBatch(bits []datatypes.Bit) (*Batch, error) {
if len(bits) != POCSAG_BATCH_LEN {
return nil, fmt.Errorf("invalid number of bits in batch: ", len(bits))
}
words := []*Codeword{}
for a := 0; a < len(bits); a = a + POCSAG_CODEWORD_LEN {
word, err := NewCodeword(bits[a : a+POCSAG_CODEWORD_LEN])
if err != nil {
println(err.Error())
} else {
words = append(words, word)
}
}
b := &Batch{
Codewords: words,
}
return b, nil
}
// Print will print a list with the codewords of this bach. FOr debugging.
func (b *Batch) Print() {
for _, w := range b.Codewords {
w.Print()
}
}
//-----------------------------
// Codeword contains the actual data. There are two codewords per frame,
// and there are 8 frames per batch.
// Type can be either Address or Message, and a special Idle codeword will occur
// from time to time.
// Payload is a stream of bits, and ValidParity bit is set on creation for later
// reference.
type Codeword struct {
Type CodewordType
Payload []datatypes.Bit
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.
func NewCodeword(bits []datatypes.Bit) (*Codeword, error) {
if len(bits) != 32 {
return nil, fmt.Errorf("invalid number of bits for codeword: ", len(bits))
}
bits, corrected := BitCorrection(bits)
mtype := CodewordTypeAddress
if bits[0] == true {
mtype = CodewordTypeMessage
}
bytes := utils.MSBBitsToBytes(bits, 8)
if isIdle(bytes) {
mtype = CodewordTypeIdle
}
c := &Codeword{
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() {
payload := ""
var color *color.Color = blue
switch c.Type {
case CodewordTypeAddress:
payload = c.Adress()
color = red
case CodewordTypeMessage:
payload = ""
color = green
default:
color = blue
}
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)
}
println("")
}
// Print the address for debugging
func (c *Codeword) Adress() string {
bytes := utils.MSBBitsToBytes(c.Payload[0:17], 8)
addr := uint(bytes[1])
addr += uint(bytes[0]) << 8
return fmt.Sprintf("%X:%s%s", addr,
utils.TernaryStr(bool(c.Payload[18]), "1", "0"),
utils.TernaryStr(bool(c.Payload[19]), "1", "0"))
}
// Utilities
// isPreamble matches 4 bytes to the POCSAG preamble 0x7CD215D8
func isPreamble(bytes []byte) bool {
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)
// 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
}
}
// in the end, if the coefficient matches the codeword they
// are canceled out by the XOR, returning 0
return codeword
}