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.
399 lines
9.7 KiB
Go
399 lines
9.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"github.com/tehmaze/go-dmr/bit"
|
|
"github.com/tehmaze/go-dmr/dmr"
|
|
"github.com/tehmaze/go-dmr/dmr/repeater"
|
|
"github.com/tehmaze/go-dmr/homebrew"
|
|
"github.com/tehmaze/go-dmr/ipsc"
|
|
)
|
|
|
|
var (
|
|
bursttypes = map[uint32]string{
|
|
0: "pi header",
|
|
1: "voice lc header",
|
|
2: "terminator with lc",
|
|
3: "csbk",
|
|
4: "csbk",
|
|
5: "appended mbc",
|
|
6: "data header",
|
|
7: "rate ½ data continuation",
|
|
8: "rate ¾ data continuation",
|
|
9: "idle",
|
|
}
|
|
headertypes = []string{
|
|
"UDT ", "Resp", "UDat", "CDat", "Hdr4", "Hdr5", "Hdr6", "Hdr7",
|
|
"Hdr8", "Hdr9", "Hdr10", "Hdr11", "Hdr12", "DSDa", "RSDa", "Prop",
|
|
}
|
|
saptypes = []string{
|
|
"UDT", "1", "TCP HC", "UDP HC", "IP Pkt", "ARP Pkt", "6", "7",
|
|
"8", "Prop Pkt", "Short Data", "11", "12", "13", "14", "15",
|
|
}
|
|
fidMap = [256]byte{
|
|
0x01, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x05,
|
|
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
fidName = map[uint8]string{
|
|
0: "Unknown",
|
|
1: "Standard",
|
|
2: "Flyde Micro",
|
|
3: "PROD-EL SPA",
|
|
4: "Motorola Connect+",
|
|
5: "RADIODATA GmbH",
|
|
6: "Hyteria (8)",
|
|
7: "Motorola Capacity+",
|
|
8: "EMC S.p.A (19)",
|
|
9: "EMC S.p.A (28)",
|
|
10: "Radio Activity Srl (51)",
|
|
11: "Radio Activity Srl (60)",
|
|
12: "Tait Electronics",
|
|
13: "Hyteria (104)",
|
|
14: "Vertex Standard",
|
|
}
|
|
)
|
|
|
|
func dumpFCLO(payload []byte, fid uint8) {
|
|
fmt.Printf(" fid: %s (%d)\n", fidName[fid], fid)
|
|
if fid == 0 || fid == 16 { // MotoTRBO Capacity+
|
|
fmt.Printf(" fclo: -\n")
|
|
}
|
|
}
|
|
|
|
func dumpDataHeader(header []byte) {
|
|
dh, err := dmr.ParseDataHeader(header, false)
|
|
if err != nil {
|
|
fmt.Printf(" data header error: %v\n", err)
|
|
return
|
|
}
|
|
dc := dh.CommonHeader()
|
|
fmt.Printf(" service accesspt: %d\n", dc.ServiceAccessPoint)
|
|
fmt.Printf(" src id -> dst id: %d -> %d\n", dc.SrcID, dc.DstID)
|
|
switch d := dh.(type) {
|
|
case dmr.UDTDataHeader:
|
|
fmt.Printf(" format..........: %s (%#02x)\n", dmr.UDTFormatName[d.Format], d.Format)
|
|
fmt.Printf(" pad nibble......: %d\n", d.PadNibble)
|
|
fmt.Printf(" appended blocks.: %d\n", d.AppendedBlocks)
|
|
fmt.Printf(" supplementary f.: %b\n", d.SupplementaryFlag)
|
|
fmt.Printf(" opcode..........: %d\n", d.OPCode)
|
|
case dmr.UnconfirmedDataHeader:
|
|
fmt.Printf(" pad octet count.: %d\n", d.PadOctetCount)
|
|
fmt.Printf(" full message....: %b\n", d.FullMessage)
|
|
fmt.Printf(" blocks to follow: %d\n", d.BlocksToFollow)
|
|
fmt.Printf(" fragment seq.no.: %d\n", d.FragmentSequenceNumber)
|
|
}
|
|
}
|
|
|
|
func dumpData(data bit.Bits) {
|
|
var out = make([][]byte, 7)
|
|
for y := 0; y < 7; y++ {
|
|
out[y] = make([]byte, 80)
|
|
for x := 0; x < 80; x++ {
|
|
if x == 0 || x == 18 || x == 21 || x == 30 || x == 33 || x == 51 {
|
|
out[y][x] = '|'
|
|
} else {
|
|
out[y][x] = ' '
|
|
}
|
|
}
|
|
}
|
|
// Info first half
|
|
for i := 0; i < 98; i++ {
|
|
var x = 1 + (i % 16)
|
|
var y = (i / 16)
|
|
if x > 8 {
|
|
x++
|
|
}
|
|
if data[i] > 0 {
|
|
out[y][x] = '1'
|
|
} else {
|
|
out[y][x] = '0'
|
|
}
|
|
}
|
|
// Slot type first half
|
|
for i := 98; i < 108; i++ {
|
|
var x = 19 + ((i - 98) % 2)
|
|
var y = (i - 98) / 2
|
|
if data[i] > 0 {
|
|
out[y][x] = '1'
|
|
} else {
|
|
out[y][x] = '0'
|
|
}
|
|
}
|
|
// SYNC
|
|
for i := 108; i < 156; i++ {
|
|
var x = 22 + ((i - 108) % 8)
|
|
var y = (i - 108) / 8
|
|
if data[i] > 0 {
|
|
out[y][x] = '1'
|
|
} else {
|
|
out[y][x] = '0'
|
|
}
|
|
}
|
|
// Slot type second half
|
|
for i := 156; i < 166; i++ {
|
|
var x = 31 + ((i - 156) % 2)
|
|
var y = (i - 156) / 2
|
|
if data[i] > 0 {
|
|
out[y][x] = '1'
|
|
} else {
|
|
out[y][x] = '0'
|
|
}
|
|
}
|
|
// Info second half
|
|
for i := 166; i < 264; i++ {
|
|
var x = 34 + ((i - 166) % 16)
|
|
var y = ((i - 166) / 16)
|
|
if x > 41 {
|
|
x++
|
|
}
|
|
if data[i] > 0 {
|
|
out[y][x] = '1'
|
|
} else {
|
|
out[y][x] = '0'
|
|
}
|
|
}
|
|
fmt.Println("|------INFO1------|ST|--SYNC--|ST|------INFO2------|")
|
|
for _, row := range out {
|
|
fmt.Println(string(row))
|
|
}
|
|
}
|
|
|
|
func dumpSync(p *ipsc.Packet, desc string) {
|
|
sync := dmr.ExtractSyncBits(p.PayloadBits)
|
|
patt := dmr.SyncPattern(sync)
|
|
fmt.Printf("dmr[%d->%d]: ts%d got %s:\n", p.SrcID, p.DstID, p.Timeslot+1, desc)
|
|
fmt.Printf(" sync pattern: %s\n", dmr.SyncPatternName[patt])
|
|
if patt == dmr.SyncPatternUnknown {
|
|
fmt.Print(hex.Dump(sync.Bytes()))
|
|
for i, b := range sync {
|
|
sync[i] = b ^ 1
|
|
}
|
|
fmt.Print(hex.Dump(sync.Bytes()))
|
|
}
|
|
|
|
/*
|
|
slot := dmr.ExtractSlotType(p.PayloadBits)
|
|
var (
|
|
codeword uint32
|
|
bursttype uint32
|
|
payload = make([]byte, 12)
|
|
)
|
|
|
|
codeword = (uint32(slot[0]) << 11) | (uint32(slot[1]) << 3) | uint32(slot[2])>>5
|
|
bursttype = uint32(slot[0]) & 7
|
|
fmt.Printf("codeword: %#08x, bursttype: %#04x\n", codeword, bursttype)
|
|
fec.Golay_23_12_Correct(&codeword)
|
|
fmt.Printf("codeword: %#08x (after correcting)\n", codeword)
|
|
codeword &= 0x0f
|
|
fmt.Printf("codeword: %#08x (after masking)\n", codeword)
|
|
bursttype ^= codeword
|
|
var errors int
|
|
if bursttype&1 > 0 {
|
|
errors++
|
|
}
|
|
if bursttype&2 > 0 {
|
|
errors++
|
|
}
|
|
if bursttype&4 > 0 {
|
|
errors++
|
|
}
|
|
if bursttype&8 > 0 {
|
|
errors++
|
|
}
|
|
bursttype = codeword
|
|
//fmt.Printf("%d errors detected, burstype: %08x (%d)\n", errors, bursttype, bursttype)
|
|
fmt.Printf(" burst type: %s (%d), %d errors\n", bursttypes[bursttype], bursttype, errors)
|
|
|
|
if bursttype < 7 {
|
|
if err := bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
|
|
fmt.Printf(" payload error: %v\n", err)
|
|
return
|
|
}
|
|
fmt.Printf(" payload: (%d bytes)\n", len(payload))
|
|
fmt.Print(" " + hex.Dump(payload))
|
|
}
|
|
|
|
fid := payload[1]
|
|
fidm := fidMap[fid]
|
|
|
|
switch bursttype {
|
|
case 1, 2:
|
|
dumpFCLO(payload, fidm)
|
|
case 6:
|
|
dumpDataHeader(payload)
|
|
}
|
|
|
|
/*
|
|
if err := fec.Golay_20_8_Check(slot); err != nil {
|
|
fmt.Printf("%v\n", err)
|
|
fmt.Print(hex.Dump(slot.Bytes()))
|
|
return
|
|
}
|
|
|
|
cc := (slot[0] << 3) | (slot[1] << 2) | (slot[2] << 1) | slot[3]
|
|
dt := (slot[4] << 3) | (slot[5] << 2) | (slot[6] << 1) | slot[7]
|
|
fmt.Printf("cc: %d, data type: %d\n", cc, dt)
|
|
*/
|
|
}
|
|
|
|
func rc() *homebrew.RepeaterConfiguration {
|
|
return &homebrew.RepeaterConfiguration{
|
|
Callsign: "PI1BOL",
|
|
RepeaterID: 2043044,
|
|
RXFreq: 0,
|
|
TXFreq: 0,
|
|
TXPower: 0,
|
|
ColorCode: 1,
|
|
Latitude: 52.296786,
|
|
Longitude: 4.595454,
|
|
Height: 12,
|
|
Location: "Hillegom, ZH, NL",
|
|
Description: fmt.Sprintf("%s go-dmr", homebrew.Version),
|
|
URL: "https://github.com/tehmaze",
|
|
}
|
|
}
|
|
|
|
func dumpRaw(raw []byte) {
|
|
fmt.Printf("dump raw frame of %d bytes\n", len(raw))
|
|
p, err := homebrew.ParseData(raw)
|
|
if err != nil {
|
|
fmt.Printf(" parse error: %v\n", err)
|
|
return
|
|
}
|
|
dumpPacket(p)
|
|
}
|
|
|
|
func dumpPacket(p *ipsc.Packet) {
|
|
fmt.Print(p.Dump())
|
|
dumpData(p.PayloadBits)
|
|
|
|
switch p.SlotType {
|
|
case ipsc.VoiceLCHeader:
|
|
dumpSync(p, "voice LC header")
|
|
case ipsc.TerminatorWithLC:
|
|
dumpSync(p, "terminator with LC")
|
|
case ipsc.CSBK:
|
|
dumpSync(p, "CSBK")
|
|
case ipsc.VoiceDataA:
|
|
dumpSync(p, "voice A")
|
|
case ipsc.VoiceDataB:
|
|
dumpSync(p, "voice B")
|
|
case ipsc.VoiceDataC:
|
|
dumpSync(p, "voice C")
|
|
case ipsc.VoiceDataD:
|
|
dumpSync(p, "voice D")
|
|
case ipsc.VoiceDataE:
|
|
dumpSync(p, "voice E")
|
|
case ipsc.VoiceDataF:
|
|
dumpSync(p, "voice F")
|
|
}
|
|
|
|
fmt.Print("\n---\n\n")
|
|
}
|
|
|
|
func main() {
|
|
dumpFile := flag.String("dump", "", "dump file")
|
|
liveFile := flag.String("live", "", "live configuration file")
|
|
showRaw := flag.Bool("raw", false, "show raw frames")
|
|
flag.Parse()
|
|
|
|
r := repeater.New()
|
|
if *liveFile != "" {
|
|
log.Printf("going in live mode using %q\n", *liveFile)
|
|
f, err := os.Open(*liveFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
d, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
network := &homebrew.Network{}
|
|
if err := yaml.Unmarshal(d, network); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
protocol, err := homebrew.New(network, rc, r.Stream)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
protocol.Dump = true
|
|
panic(protocol.Run())
|
|
|
|
} else {
|
|
|
|
i, err := os.Open(*dumpFile)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer i.Close()
|
|
|
|
var raw = make([]byte, 53)
|
|
for {
|
|
if _, err := i.Read(raw); err != nil {
|
|
panic(err)
|
|
}
|
|
if *showRaw {
|
|
fmt.Println("raw packet:")
|
|
fmt.Print(hex.Dump(raw))
|
|
}
|
|
|
|
//dumpRaw(raw)
|
|
|
|
p, err := homebrew.ParseData(raw)
|
|
if err != nil {
|
|
fmt.Printf(" parse error: %v\n", err)
|
|
continue
|
|
}
|
|
r.Stream(p)
|
|
|
|
// Skip newline in recording
|
|
if _, err := i.Seek(1, 1); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
}
|