Prepare for public release

pull/1/head
Wijnand Modderman-Lenstra 9 years ago
parent 493e72583d
commit cba4cf198d

1
.gitattributes vendored

@ -0,0 +1 @@
*.pdf filter=lfs diff=lfs merge=lfs -text

@ -1,27 +0,0 @@
all: deps build
build: build-dmrstream
build-dmrstream:
go build ./cmd/dmrstream/
@ls -alh dmrstream
deps: godeps deps-platform oggfwd
deps-platform:
@echo "For OS X: make deps-brew"
@echo "For Debian: make deps-debian"
deps-brew:
brew install --HEAD mbelib
brew install lame
brew install sox
deps-debian:
sudo apt-get install lame sox
godeps:
go get -v $(shell go list -f '{{ join .Deps "\n" }}' ./... | sort -u | egrep '(gopkg|github)' | grep -v '/tehmaze/go-dmr')
oggfwd:
$(CC) -O2 -pipe -Wall -ffast-math -fsigned-char -lshout -pthread -o $@ $@.c

@ -1,2 +1,32 @@
# dmr
Golang Digital Mobile Radio protocols
# go-dmr
Golang Digital Mobile Radio protocols.
## References
The DMR Air Interface protocol is specified in *Electromagnetic compatibility
and Radio spectrum Matters (ERM); Digital Mobile Radio (DMR) Systems; Part 1:
DMR Air Interface (AI) protocol*, [ETSI TS 102 361-1][ETSI TS 102 361-1].
The Brandmeister Homebrew protocol is specified in
[IPSC Protocol Specs for homebrew DMR repeater][homebrew specs]
by [Hans DL5DI](mailto:dl5di@gmx.de),
[Jonathan Naylor (G4KLXG)](https://twitter.com/g4klx) and Torsten Schultze
(DG1HT).
[ETSI TS 102 361-1]: docs/ts_10236101v010405p.pdf
[homebrew specs]: docs/DMRplus%20IPSC%20Protocol%20for%20HB%20repeater%20(20150726).pdf
## Warning
This implementation is not suitable for commercial use and is for educational
purposes only.
## Acknowledgements
The implementation is possible because of the invaluable help from the
following persons. Thanks for your patience and providing me with sample data
and links to test the protocols.
* Rudy Hardeman (PD0ZRY)
* Artem Prilutskiy (R3ABM)

@ -1,97 +0,0 @@
package bit
import "fmt"
type Bit byte
func (b *Bit) Flip() {
(*b) ^= 0x01
}
type Bits []Bit
func toBits(b byte) Bits {
var o = make(Bits, 8)
for bit, mask := 0, byte(128); bit < 8; bit, mask = bit+1, mask>>1 {
if b&mask != 0 {
o[bit] = 1
}
}
return o
}
func NewBits(bytes []byte) Bits {
var l = len(bytes)
var o = make(Bits, 0)
for i := 0; i < l; i++ {
o = append(o, toBits(bytes[i])...)
}
return o
}
func (bits *Bits) Bytes() []byte {
var l = len(*bits)
var o = make([]byte, (l+7)/8)
for i, b := range *bits {
if b == 0x01 {
o[i/8] |= (1 << byte(7-(i%8)))
}
}
return o
}
func (bits Bits) Debits() Debits {
var debits = make(Debits, (len(bits)+1)/2)
for i := 0; i < len(bits); i += 2 {
debits[i/2] = Debit((bits[i] << 1) | (bits[i+1]))
}
return debits
}
func (bits Bits) Dump() string {
var (
s string
bytes = bits.Bytes()
)
for i, b := range bytes {
if i%7 == 0 {
if i != 0 {
s += "\n"
}
s += fmt.Sprintf("%08x ", i)
}
s += fmt.Sprintf("%08b ", b)
}
s += "\n"
return s
}
func (bits Bits) Equal(other Bits) bool {
var l = bits.Len()
if l != other.Len() {
return false
}
for i := 0; i < l; i++ {
if bits[i] != other[i] {
return false
}
}
return true
}
func (bits Bits) Len() int {
return len(bits)
}
func (bits Bits) String() string {
var s = ""
for _, b := range bits {
if b == 0x01 {
s += "1"
} else {
s += "0"
}
}
return s
}

@ -1,22 +0,0 @@
package bit
type Debit uint8
type Debits []Debit
func toDebits(b byte) Debits {
var o = make(Debits, 4)
for bit, mask := 0, byte(128); bit < 8; bit, mask = bit+2, mask>>2 {
o[bit/2] = Debit((b >> mask) & 3)
}
return o
}
func NewDebits(bytes []byte) Debits {
var l = len(bytes)
var o = make(Debits, 0)
for i := 0; i < l; i++ {
o = append(o, toDebits(bytes[i])...)
}
return o
}

@ -1,4 +1,21 @@
package bit
package dmr
const (
PayloadBits = 98 + 10 + 48 + 10 + 98
PayloadSize = 33
InfoHalfBits = 98
InfoBits = 2 * InfoHalfBits
SlotTypeHalfBits = 10
SlotTypeBits = 2 * SlotTypeHalfBits
SignalBits = 48
SyncOffsetBits = InfoHalfBits + SlotTypeHalfBits
SyncBits = SignalBits
VoiceHalfBits = 108
VoiceBits = 2 * VoiceHalfBits
EMBHalfBits = 8
EMBBits = 2 * EMBHalfBits
EMBSignallingLCFragmentBits = 32
)
// Because Go doesn't have binary literals ("We've found hex and octal to be sufficient")
const (
@ -259,3 +276,33 @@ const (
B11111110
B11111111
)
// BytesToBits converts a byte slice to a byte slice representing the individual data bits.
func BytesToBits(data []byte) []byte {
var bits = make([]byte, len(data)*8)
for i := 0; i < len(data); i++ {
copy(bits[i*8:], toBits(data[i]))
}
return bits
}
func toBits(b byte) []byte {
var o = make([]byte, 8)
for bit, mask := 0, byte(128); bit < 8; bit, mask = bit+1, mask>>1 {
if b&mask != 0 {
o[bit] = 1
}
}
return o
}
// BitsToBytes converts a byte slice of bits to a byte slice.
func BitsToBytes(bits []byte) []byte {
var data = make([]byte, (len(bits)+7)/8)
for i, b := range bits {
if b == 0x01 {
data[i/8] |= (1 << byte(7-(i%8)))
}
}
return data
}

@ -1,31 +1,39 @@
package bit
package dmr
import "testing"
import (
"bytes"
"testing"
)
func TestBit(t *testing.T) {
var tests = []struct {
Test []byte
Want Bits
Want []byte
}{
{
[]byte{0x2a},
Bits{0, 0, 1, 0, 1, 0, 1, 0},
[]byte{0, 0, 1, 0, 1, 0, 1, 0},
},
{
[]byte{0xbe, 0xef},
Bits{1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1},
[]byte{1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1},
},
}
for _, test := range tests {
got := NewBits(test.Test)
got := BytesToBits(test.Test)
if len(got) != len(test.Want) {
t.Fatalf("expected length %d, got %d [%s]", len(test.Want), len(got), got.String())
t.Fatalf("expected length %d, got %d [%s]", len(test.Want), len(got), string(got))
}
for i, b := range got {
if b != test.Want[i] {
t.Fatalf("bit %d is off: %v != %v", i, got, test.Want)
}
}
rev := BitsToBytes(got)
if !bytes.Equal(rev, test.Test) {
t.Fatalf("reverse bits to bytes failed, %v != %v", rev, test.Test)
}
}
}

@ -3,11 +3,11 @@ package bptc
import (
"fmt"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr"
"github.com/tehmaze/go-dmr/fec"
)
func Process(info bit.Bits, payload []byte) error {
func Process(info []byte, payload []byte) error {
if len(info) < 196 {
return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info))
}
@ -17,8 +17,8 @@ func Process(info bit.Bits, payload []byte) error {
var (
i, j, k uint32
datafr = make(bit.Bits, 196)
extracted = make(bit.Bits, 96)
datafr = make([]byte, 196)
extracted = make([]byte, 96)
)
// Deinterleave bits
@ -41,7 +41,7 @@ func Process(info bit.Bits, payload []byte) error {
fec.Hamming15_11_3_Correct(&codeword)
codeword &= 0x01ff
for j = 0; j < 9; j++ {
datafr[j*15+i] = bit.Bit((codeword >> (8 - j)) & 1)
datafr[j*15+i] = byte((codeword >> (8 - j)) & 1)
}
}
for j = 0; j < 9; j++ {
@ -52,7 +52,7 @@ func Process(info bit.Bits, payload []byte) error {
}
fec.Hamming15_11_3_Correct(&codeword)
for i = 0; i < 11; i++ {
datafr[j*15+10-i] = bit.Bit((codeword >> i) & 1)
datafr[j*15+10-i] = byte((codeword >> i) & 1)
}
}
@ -66,7 +66,7 @@ func Process(info bit.Bits, payload []byte) error {
}
}
copy(payload, extracted.Bytes())
copy(payload, dmr.BitsToBytes(extracted))
return nil
}

@ -1,133 +0,0 @@
package bptc
import (
"fmt"
"github.com/tehmaze/go-dmr/bit"
)
func Decode(bits bit.Bits, deinterleave bool) (bit.Bits, error) {
var debits bit.Bits
if deinterleave {
debits = Deinterleave(bits)
} else {
debits = bits
}
if err := Check(bits); err != nil {
return nil, err
}
return Extract(debits), nil
}
// Deinterleave raw bits
func Deinterleave(r bit.Bits) bit.Bits {
// The first bit is R(3) which is not used so can be ignored
var d = make(bit.Bits, 196)
var i int
for a := 0; a < 196; a++ {
i = (a * 181) % 196
d[a] = r[i]
}
return d
}
// Hamming(13, 9, 3) check
func Hamming1393(debits bit.Bits) (bool, bit.Bits) {
var err = make(bit.Bits, 4)
err[0] = debits[0] ^ debits[1] ^ debits[3] ^ debits[5] ^ debits[6]
err[1] = debits[0] ^ debits[1] ^ debits[2] ^ debits[4] ^ debits[6] ^ debits[7]
err[2] = debits[0] ^ debits[1] ^ debits[2] ^ debits[3] ^ debits[5] ^ debits[7] ^ debits[8]
err[3] = debits[0] ^ debits[2] ^ debits[4] ^ debits[5] ^ debits[8]
return (err[0] == debits[9]) && (err[1] == debits[10]) && (err[2] == debits[11]) && (err[3] == debits[12]), err
}
// Hamming(15, 11, 3) check
func Hamming15113(debits bit.Bits) (bool, bit.Bits) {
var err = make(bit.Bits, 4)
err[0] = debits[0] ^ debits[1] ^ debits[2] ^ debits[3] ^ debits[5] ^ debits[7] ^ debits[8]
err[1] = debits[1] ^ debits[2] ^ debits[3] ^ debits[4] ^ debits[6] ^ debits[8] ^ debits[9]
err[2] = debits[2] ^ debits[3] ^ debits[4] ^ debits[5] ^ debits[7] ^ debits[9] ^ debits[10]
err[3] = debits[0] ^ debits[1] ^ debits[2] ^ debits[4] ^ debits[6] ^ debits[7] ^ debits[10]
return (err[0] == debits[11]) && (err[1] == debits[12]) && (err[2] == debits[13]) && (err[3] == debits[14]), err
}
// Check each row with a Hamming (15,11,3) code
func Check(debits bit.Bits) error {
var (
row = make(bit.Bits, 15)
col = make(bit.Bits, 13)
)
// Run through each of the 9 rows containing data
for r := 0; r < 9; r++ {
p := (r * 15) + 1
for a := 0; a < 15; a++ {
row[a] = debits[p]
}
if ok, _ := Hamming15113(row); !ok {
return fmt.Errorf("hamming(15, 11, 3) check failed on row #%d", r)
}
}
// Run through each of the 15 columns
for c := 0; c < 15; c++ {
p := c + 1
for a := 0; a < 13; a++ {
col[a] = debits[p]
p += 15
}
if ok, _ := Hamming1393(col); !ok {
return fmt.Errorf("hamming(13, 9, 3) check failed on col #%d", c)
}
}
return nil
}
// Extract the 96 bits of data
func Extract(debits bit.Bits) bit.Bits {
var (
out = make(bit.Bits, 96)
a, pos int
)
for a = 4; a <= 11; a++ {
out[pos] = debits[a]
pos++
}
for a = 16; a <= 26; a++ {
out[pos] = debits[a]
pos++
}
for a = 31; a <= 41; a++ {
out[pos] = debits[a]
pos++
}
for a = 46; a <= 56; a++ {
out[pos] = debits[a]
pos++
}
for a = 61; a <= 71; a++ {
out[pos] = debits[a]
pos++
}
for a = 76; a <= 86; a++ {
out[pos] = debits[a]
pos++
}
for a = 91; a <= 101; a++ {
out[pos] = debits[a]
pos++
}
for a = 106; a <= 116; a++ {
out[pos] = debits[a]
pos++
}
for a = 121; a <= 131; a++ {
out[pos] = debits[a]
pos++
}
return out
}

@ -1,28 +0,0 @@
package main
import (
"crypto/rand"
"fmt"
)
func main() {
var raw = make([]byte, 4)
rand.Read(raw)
fmt.Printf("raw = %02x%02x%02x%02x\n", raw[0], raw[1], raw[2], raw[3])
for i := 0; i < 4*8; i++ {
b := i / 8
o := byte(7 - i%8)
fmt.Printf("%02x b=%d, o=%d, r=%d\n", raw[b], b, o, raw[b]>>o)
}
for i := 0; i < 4*8; i++ {
b := i / 8
o := byte(7 - i%8)
if (raw[b]>>o)&0x01 == 0x01 {
fmt.Print("1")
} else {
fmt.Print("0")
}
}
fmt.Println("")
}

@ -1,188 +0,0 @@
package main
import (
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"gopkg.in/yaml.v2"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"github.com/gordonklaus/portaudio"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/dmr/repeater"
"github.com/tehmaze/go-dmr/homebrew"
"github.com/tehmaze/go-dmr/ipsc"
"github.com/tehmaze/go-dsd"
)
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 main() {
dumpFile := flag.String("dump", "", "dump file")
pcapFile := flag.String("pcap", "", "PCAP file")
liveFile := flag.String("live", "", "live configuration file")
showRaw := flag.Bool("raw", false, "show raw frames")
audioTS := flag.Int("audiots", 0, "play audio from time slot (1 or 2)")
flag.Parse()
if *audioTS > 2 {
log.Fatalf("invalid time slot %d\n", *audioTS)
return
}
r := repeater.New()
if *audioTS > 0 {
ambeframe := make(chan float32)
vs := dsd.NewAMBEVoiceStream(3)
r.VoiceFrameFunc = func(p *ipsc.Packet, bits bit.Bits) {
var in = make([]byte, len(bits))
for i, b := range bits {
in[i] = byte(b)
}
samples, err := vs.Decode(in)
if err != nil {
log.Printf("error decoding AMBE3000 frame: %v\n", err)
return
}
for _, sample := range samples {
ambeframe <- sample
}
}
portaudio.Initialize()
defer portaudio.Terminate()
h, err := portaudio.DefaultHostApi()
if err != nil {
panic(err)
}
p := portaudio.LowLatencyParameters(nil, h.DefaultOutputDevice)
p.SampleRate = 8000
p.Output.Channels = 1
stream, err := portaudio.OpenStream(p, func(out []float32) {
for i := range out {
out[i] = <-ambeframe
}
})
if err != nil {
log.Printf("error streaming: %v\n", err)
return
}
defer stream.Close()
if err := stream.Start(); err != nil {
log.Printf("error streaming: %v\n", err)
return
}
}
switch {
case *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())
case *dumpFile != "":
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)
}
}
case *pcapFile != "":
var (
handle *pcap.Handle
err error
)
if handle, err = pcap.OpenOffline(*pcapFile); err != nil {
panic(err)
}
defer handle.Close()
dec := gopacket.DecodersByLayerName["Ethernet"]
source := gopacket.NewPacketSource(handle, dec)
for packet := range source.Packets() {
raw := packet.ApplicationLayer().Payload()
if *showRaw {
fmt.Println("raw packet:")
fmt.Print(hex.Dump(raw))
}
p, err := homebrew.ParseData(raw)
if err != nil {
fmt.Printf(" parse error: %v\n", err)
continue
}
r.Stream(p)
}
}
}

@ -1,697 +0,0 @@
package main
/*
#cgo LDFLAGS: -lshout
#include "shout/shout.h"
#include <stdlib.h>
*/
import "C"
import (
"bufio"
"encoding/binary"
"encoding/hex"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"os"
"os/exec"
"os/signal"
"strconv"
"strings"
"sync"
"time"
"unsafe"
"gopkg.in/yaml.v2"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/dmr/repeater"
"github.com/tehmaze/go-dmr/homebrew"
"github.com/tehmaze/go-dmr/ipsc"
"github.com/tehmaze/go-dsd"
)
const (
Timeslot1 uint8 = iota
Timeslot2
)
var (
VoiceFrameDuration = time.Millisecond * 60
VoiceSyncDuration = time.Millisecond * 360
UserMap = map[uint32]string{}
)
type Protocol interface {
Close() error
Run() error
}
type Config struct {
Repeater *homebrew.RepeaterConfiguration
Link map[string]*Link
User string
}
type Shout struct {
Host string
Port uint
User string
Password string
Mount string
Format int
Protocol int
Description string
Genre string
// wrap the native C struct
shout *C.struct_shout
metadata *C.struct_shout_metadata_t
stream chan []byte
}
func (s *Shout) getError() error {
errstr := C.GoString(C.shout_get_error(s.shout))
return errors.New("shout: " + errstr)
}
func (s *Shout) init() error {
if s.shout != nil {
return nil
}
s.shout = C.shout_new()
s.stream = make(chan []byte)
return s.update()
}
func (s *Shout) update() error {
// set hostname
p := C.CString(s.Host)
C.shout_set_host(s.shout, p)
C.free(unsafe.Pointer(p))
// set port
C.shout_set_port(s.shout, C.ushort(s.Port))
// set username
p = C.CString(s.User)
C.shout_set_user(s.shout, p)
C.free(unsafe.Pointer(p))
// set password
p = C.CString(s.Password)
C.shout_set_password(s.shout, p)
C.free(unsafe.Pointer(p))
// set mount point
p = C.CString(s.Mount)
C.shout_set_mount(s.shout, p)
C.free(unsafe.Pointer(p))
// set description
p = C.CString(s.Description)
C.shout_set_description(s.shout, p)
C.free(unsafe.Pointer(p))
// set genre
p = C.CString(s.Genre)
C.shout_set_genre(s.shout, p)
C.free(unsafe.Pointer(p))
// set format
C.shout_set_format(s.shout, C.uint(s.Format))
// set protocol
C.shout_set_protocol(s.shout, C.uint(s.Protocol))
return nil
}
func (s *Shout) Close() error {
if s.shout != nil {
C.shout_free(s.shout)
s.shout = nil
}
return nil
}
func (s *Shout) Open() error {
if err := s.init(); err != nil {
return err
}
errno := int(C.shout_open(s.shout))
if errno != 0 {
return s.getError()
}
return nil
}
func (s *Shout) Stream(data []byte) error {
if s.shout == nil {
return errors.New("shout: stream not open")
}
ptr := (*C.uchar)(&data[0])
C.shout_send(s.shout, ptr, C.size_t(len(data)))
errno := int(C.shout_get_errno(s.shout))
if errno != 0 {
return s.getError()
}
C.shout_sync(s.shout)
return nil
}
func (s *Shout) UpdateDescription(desc string) {
ptr := C.CString(desc)
C.shout_set_description(s.shout, ptr)
C.free(unsafe.Pointer(ptr))
}
func (s *Shout) UpdateGenre(genre string) {
ptr := C.CString(genre)
C.shout_set_genre(s.shout, ptr)
C.free(unsafe.Pointer(ptr))
}
func (s *Shout) UpdateMetadata(mname string, mvalue string) {
md := C.shout_metadata_new()
ptr1 := C.CString(mname)
ptr2 := C.CString(mvalue)
C.shout_metadata_add(md, ptr1, ptr2)
C.free(unsafe.Pointer(ptr1))
C.free(unsafe.Pointer(ptr2))
C.shout_set_metadata(s.shout, md)
C.shout_metadata_free(md)
}
type Link struct {
Disable bool
// Supported protocols
Homebrew *homebrew.Network
PCAP *PCAPProtocol
// Shout streams
Transcode string
TS1Stream *Shout
TS2Stream *Shout
}
type Stream struct {
Timeslot uint8
Repeater string
Transcode string // Transcoder binary/script
Shout *Shout // Shout server details
Buffer chan float32
Samples []float32
pipe io.WriteCloser // Our transcoder input
running bool
connected bool
retry int
}
func NewStream(ts uint8, repeater string, sh *Shout, transcode string) *Stream {
if sh.Description == "" {
sh.Description = fmt.Sprintf("DMR Repeater %s (TS%d)", strings.ToUpper(repeater), ts+1)
}
if sh.Genre == "" {
sh.Genre = "ham"
}
return &Stream{
Timeslot: ts,
Repeater: repeater,
Transcode: transcode,
Shout: sh,
Buffer: make(chan float32),
Samples: make([]float32, 8000),
}
}
func (s *Stream) Close() error {
if s.running {
s.running = false
if s.pipe != nil {
return s.pipe.Close()
}
}
return nil
}
func (s *Stream) Run() error {
var err error
log.Printf("dmr/stream: connecting to icecast server %s:%d%s as %s\n",
s.Shout.Host, s.Shout.Port, s.Shout.Mount, s.Shout.User)
if err = s.Shout.Open(); err != nil {
return err
}
s.connected = true
s.Shout.UpdateDescription(fmt.Sprintf("DMR repeater link to %s (TS%d)", s.Repeater, s.Timeslot+1))
log.Println("dmr/stream: setting up transcoder pipe")
cmnd := strings.Split(s.Transcode, " ")
pipe := exec.Command(cmnd[0], cmnd[1:]...)
enc, err := pipe.StdinPipe()
if err != nil {
return err
}
defer enc.Close()
s.pipe = enc
out, err := pipe.StdoutPipe()
if err != nil {
log.Printf("dmr/stream: error connecting to stream output: %v\n", err)
return err
}
defer out.Close()
// Connect stderr
pipe.Stderr = os.Stderr
if err := pipe.Start(); err != nil {
return err
}
// Spawn goroutine that deals with new audio from the transcode process
go func(out io.Reader) {
var buf = make([]byte, 1024)
for {
if _, err := out.Read(buf); err != nil {
log.Printf("dmr/stream: error reading from stream: %v\n", err)
s.Close()
return
}
var err = s.Shout.Stream(buf)
for err != nil {
log.Printf("dmr/stream: error streaming: %v\n", err)
s.Close()
s.retry++
if s.retry > 15 {
log.Printf("dmr/stream: retry limit exceeded\n")
return
}
time.Sleep(time.Second * time.Duration(3*s.retry))
log.Printf("dmr/stream: connecting to icecast server %s:%d%s as %s\n",
s.Shout.Host, s.Shout.Port, s.Shout.Mount, s.Shout.User)
err = s.Shout.Open()
}
s.retry = 0
}
}(out)
var i uint32
s.running = true
for s.running {
// Ensure that we *always* have new data (even be it silence) within the duration of a voice frame
select {
case sample := <-s.Buffer:
s.Samples[i] = sample
i++
case <-time.After(VoiceFrameDuration):
log.Printf("dmr/stream: filling silence, timeout after %s\n", VoiceFrameDuration)
for ; i < 8000; i++ {
s.Samples[i] = 0
}
}
if i >= 8000 {
log.Printf("dmr/stream: writing %d samples to encoder\n", i)
var buffer = make([]byte, 4)
for _, sample := range s.Samples {
binary.BigEndian.PutUint32(buffer, math.Float32bits(sample))
if _, err := enc.Write(buffer); err != nil {
log.Printf("dmr/stream: error writing to encoder: %v\n", err)
return err
}
}
i = 0
}
}
return nil
}
func (s *Stream) UpdateMetadata(repeater string, src, dst uint32) {
if s.connected {
log.Printf("dmr/stream: updating meta data to %s and %d -> %d\n", repeater, src, dst)
s.Shout.UpdateMetadata("description", fmt.Sprintf("Repeater %s", strings.ToUpper(repeater)))
s.Shout.UpdateMetadata("artist", fmt.Sprintf("Repeater %s", strings.ToUpper(repeater)))
var (
dstName = strconv.Itoa(int(dst))
srcName = strconv.Itoa(int(src))
)
if name, ok := UserMap[dst]; ok {
dstName = name
}
if name, ok := UserMap[src]; ok {
srcName = name
}
s.Shout.UpdateMetadata("song", fmt.Sprintf("TS%d [%s -> %s]", s.Timeslot+1, srcName, dstName))
}
}
func (s *Stream) Write(sample float32) {
s.Buffer <- sample
}
type Samples struct {
data []float32
size, rptr, wptr int
}
func NewSamples(size int) *Samples {
return &Samples{
data: make([]float32, size),
size: size,
}
}
func (s *Samples) Read() float32 {
d := s.data[s.rptr]
s.data[s.rptr] = 0
s.rptr++
if s.rptr == s.size {
s.rptr = 0
}
return d
}
func (s *Samples) Write(sample float32) {
s.data[s.wptr] = sample
s.wptr++
if s.wptr == s.size {
s.wptr = 0
}
}
type PCAPProtocol struct {
Filename string
DumpRaw bool
Stream homebrew.StreamFunc
closed bool
}
func (pp *PCAPProtocol) Close() error {
pp.closed = true
return nil
}
func (pp *PCAPProtocol) Run() error {
var (
handle *pcap.Handle
err error
)
if handle, err = pcap.OpenOffline(pp.Filename); err != nil {
return err
}
defer handle.Close()
dec := gopacket.DecodersByLayerName["Ethernet"]
source := gopacket.NewPacketSource(handle, dec)
for packet := range source.Packets() {
raw := packet.ApplicationLayer().Payload()
if pp.DumpRaw {
fmt.Println("raw packet:")
fmt.Print(hex.Dump(raw))
}
p, err := homebrew.ParseData(raw)
if err != nil {
fmt.Printf(" parse error: %v\n", err)
continue
}
if pp.Stream != nil {
pp.Stream(p)
}
if pp.closed {
break
}
}
return nil
}
func init() {
C.shout_init()
}
func main() {
configFile := flag.String("config", "", "configuration file")
amplify := flag.Float64("amplify", 25.0, "audio amplify rate")
verbose := flag.Bool("verbose", false, "be verbose")
enabled := flag.String("enable", "", "comma separated list of enabled links (overrides config)")
disabled := flag.String("disable", "", "comma separated list of disabled links (overrides config)")
flag.Parse()
overrides := map[string]bool{}
for _, call := range strings.Split(*enabled, ",") {
overrides[call] = true
}
for _, call := range strings.Split(*disabled, ",") {
overrides[call] = false
}
log.Printf("using configuration file %q\n", *configFile)
f, err := os.Open(*configFile)
if err != nil {
log.Fatalf("failed to open %q: %v\n", *configFile, err)
panic(err)
}
defer f.Close()
d, err := ioutil.ReadAll(f)
if err != nil {
log.Fatalf("failed to read %q: %v\n", *configFile, err)
return
}
config := &Config{}
if err := yaml.Unmarshal(d, config); err != nil {
log.Fatalf("failed to parse %q: %v\n", *configFile, err)
return
}
if config.User != "" {
uf, err := os.Open(config.User)
if err != nil {
log.Fatalf("failed to open %q: %v\n", config.User, err)
return
}
defer uf.Close()
scanner := bufio.NewScanner(uf)
var lines int
for scanner.Scan() {
part := strings.Split(string(scanner.Text()), ";")
if lines > 1 {
if dmrID, err := strconv.ParseUint(part[2], 10, 32); err == nil {
UserMap[uint32(dmrID)] = fmt.Sprintf("%s (%s)", part[3], part[1])
}
}
lines++
}
if err := scanner.Err(); err != nil {
log.Fatalf("failed to parse %q: %v\n", config.User, err)
return
}
}
if len(config.Link) == 0 {
log.Fatalln("no links configured")
return
}
sm := map[string]map[uint8]*Stream{}
ps := map[string]Protocol{}
for call, link := range config.Link {
status, ok := overrides[call]
switch {
case ok:
if !status {
log.Printf("link/%s: link disabled, skipping (override)\n", call)
continue
}
log.Printf("link/%s: link enabled (override)\n", call)
case link.Disable:
log.Printf("link/%s: link disabled, skipping\n", call)
continue
}
log.Printf("link/%s: configuring link\n", call)
// Repeater
r := repeater.New()
// Protocol
switch {
case link.Homebrew != nil:
log.Printf("link/%s: homebrew protocol, %s\n", call, link.Homebrew.Master)
rc := func() *homebrew.RepeaterConfiguration {
return config.Repeater
}
proto, err := homebrew.New(link.Homebrew, rc, r.Stream)
if err != nil {
log.Fatalf("link/%s: failed to setup protocol: %v\n", call, err)
return
}
ps[call] = proto
case link.PCAP != nil:
log.Printf("link/%s: PCAP file %q\n", call, link.PCAP.Filename)
link.PCAP.Stream = r.Stream
ps[call] = link.PCAP
default:
log.Fatalf("[%s]: unknown or no protocol configured\n", call)
return
}
// Streams
sm[call] = map[uint8]*Stream{}
if link.TS1Stream != nil {
if link.Transcode == "" {
log.Fatalf("link/%s: TS1 stream defined, but no transcoder\n", call)
}
sm[call][Timeslot1] = NewStream(Timeslot1, call, link.TS1Stream, link.Transcode)
}
if link.TS2Stream != nil {
if link.Transcode == "" {
log.Fatalf("link/%s: TS2 stream defined, but no transcoder\n", call)
}
sm[call][Timeslot2] = NewStream(Timeslot1, call, link.TS2Stream, link.Transcode)
}
// Setup AMBE voice stream decoder
var (
lastsrc, lastdst uint32
last = time.Now()
vs = dsd.NewAMBEVoiceStream(3)
)
// Function that receives decoded AMBE frames as float32 PCM (8kHz mono)
r.VoiceFrameFunc = func(p *ipsc.Packet, bits bit.Bits) {
var in = make([]byte, len(bits))
for i, b := range bits {
in[i] = byte(b)
}
samples, err := vs.Decode(in)
if err != nil {
log.Printf("error decode AMBE3000 frames: %v\n", err)
return
}
for _, sample := range samples {
if stream, ok := sm[call][p.Timeslot]; ok {
stream.Write(sample * float32(*amplify))
if lastsrc != p.SrcID || lastdst != p.DstID {
stream.UpdateMetadata(call, p.SrcID, p.DstID)
lastsrc = p.SrcID
lastdst = p.DstID
}
}
}
if false {
diff := time.Now().Sub(last)
if *verbose {
log.Printf("%s since last voice sync\n", diff)
}
if diff < VoiceFrameDuration {
t := VoiceFrameDuration - diff
if *verbose {
log.Printf("delaying %s, last tick was %s ago\n", t, diff)
}
time.Sleep(t)
}
last = time.Now()
}
}
}
// Signal handler
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(signals chan os.Signal) {
for _ = range signals {
// Terminate protocols
for call, p := range ps {
log.Printf("link/%s: closing\n", call)
if err := p.Close(); err != nil {
log.Printf("link/%s: close error: %v\n", call, err)
continue
}
}
// Terminate streams
for call, streams := range sm {
for ts, stream := range streams {
log.Printf("link/%s: closing stream for TS%d\n", call, ts+1)
if err := stream.Close(); err != nil {
log.Printf("link/%s: error closing stream for TS%d: %v\n", call, ts+1, err)
continue
}
}
}
}
}(c)
wg := &sync.WaitGroup{}
// Spawn a goroutine for all the protocol runners
for call, p := range ps {
wg.Add(1)
go func(call string, p Protocol, wg *sync.WaitGroup) {
defer wg.Done()
if err := p.Run(); err != nil {
log.Printf("link/%s: error running: %v\n", call, err)
}
log.Printf("link/%s: done\n", call)
delete(ps, call)
}(call, p, wg)
}
// Spawn a goroutine for all the streamers
for call, streams := range sm {
if len(streams) == 0 {
continue
}
for ts, stream := range streams {
log.Printf("link/%s: starting stream for TS%d\n", call, ts+1)
wg.Add(1)
go func(call string, stream *Stream, wg *sync.WaitGroup) {
defer wg.Done()
if err := stream.Run(); err != nil {
log.Printf("link/%s: error streaming: %v\n", call, err)
}
log.Printf("link/%s: stream done\n", call)
}(call, stream, wg)
}
}
// Wait for protocols to finish
log.Println("all routines started, waiting for protocols to finish")
wg.Wait()
}

@ -1,45 +0,0 @@
---
repeater:
callsign: PD0MZ
id: 2043044
rxfreq: 0
txfreq: 0
txpower: 0
colorcode: 1
latitude: 52.296786
longitude: 4.595454
height: 12
location: Hillegom
description: \o/ go-dmr
url: https://maze.io/
user: user_by_call.csv
link:
pd0zry:
homebrew:
master: brandmeister.pd0zry.ampr.org:62030
authkey: passw0rd
local: 0.0.0.0:62030
localid: 2042214
ts1stream:
host: 127.0.0.1
port: 8000
user: source
password: source
mount: /ts1.mp3
format: 1
transcode: ./transcode-mp3.sh
debug:
disable: true
pcap:
filename: brandmeister.pcap
ts1stream:
host: 127.0.0.1
port: 8000
user: source
password: source
mount: /debug-ts1.mp3
format: 1
transcode: ./transcode-mp3.sh

@ -1,18 +1,18 @@
// Package quadres_16_7 implements the quadratic residue (16, 7, 6) parity check.
package quadres_16_7
import "github.com/tehmaze/go-dmr/bit"
import "bytes"
var (
validDataParities = [128]bit.Bits{}
validDataParities = [128][]byte{}
)
type Codeword struct {
Data bit.Bits
Parity bit.Bits
Data []byte
Parity []byte
}
func NewCodeword(bits bit.Bits) *Codeword {
func NewCodeword(bits []byte) *Codeword {
if len(bits) < 16 {
return nil
}
@ -23,8 +23,8 @@ func NewCodeword(bits bit.Bits) *Codeword {
}
}
func ParityBits(bits bit.Bits) bit.Bits {
parity := make(bit.Bits, 9)
func ParityBits(bits []byte) []byte {
parity := make([]byte, 9)
// Multiplying the generator matrix with the given data bits.
// See DMR AI spec. page 134.
parity[0] = bits[1] ^ bits[2] ^ bits[3] ^ bits[4]
@ -39,7 +39,7 @@ func ParityBits(bits bit.Bits) bit.Bits {
return parity
}
func Check(bits bit.Bits) bool {
func Check(bits []byte) bool {
codeword := NewCodeword(bits)
if codeword == nil {
return false
@ -52,12 +52,22 @@ func Check(bits bit.Bits) bool {
}
}
return codeword.Parity.Equal(validDataParities[dataval])
return bytes.Equal(codeword.Parity, validDataParities[dataval])
}
func toBits(b byte) []byte {
var o = make([]byte, 8)
for bit, mask := 0, byte(128); bit < 8; bit, mask = bit+1, mask>>1 {
if b&mask != 0 {
o[bit] = 1
}
}
return o
}
func init() {
for i := byte(0); i < 128; i++ {
bits := bit.NewBits([]byte{i})
bits := toBits(i)
validDataParities[i] = ParityBits(bits)
}
}

@ -1,10 +1,25 @@
package dmr
import (
"fmt"
"github.com/tehmaze/go-dmr/bit"
)
import "fmt"
var DataTypeName = [16]string{
"PI Header", // 0000
"VOICE Header:", // 0001
"TLC:", // 0010
"CSBK:", // 0011
"MBC Header:", // 0100
"MBC:", // 0101
"DATA Header:", // 0110
"RATE 1/2 DATA:", // 0111
"RATE 3/4 DATA:", // 1000
"Slot idle", // 1001
"Rate 1 DATA", // 1010
"Unknown/Bad (11)", // 1011
"Unknown/Bad (12)", // 1100
"Unknown/Bad (13)", // 1101
"Unknown/Bad (14)", // 1110
"Unknown/Bad (15)", // 1111
}
// Data Header Packet Format
const (
@ -247,8 +262,8 @@ func ParseDataHeader(header []byte, proprietary bool) (DataHeader, error) {
if proprietary {
return ProprietaryDataHeader{
Common: DataHeaderCommon{
ServiceAccessPoint: (header[0] & bit.B11110000) >> 4,
PacketFormat: (header[0] & bit.B00001111),
ServiceAccessPoint: (header[0] & B11110000) >> 4,
PacketFormat: (header[0] & B00001111),
CRC: ccrc,
},
ManufacturerID: header[1],
@ -266,55 +281,55 @@ func ParseDataHeader(header []byte, proprietary bool) (DataHeader, error) {
case PacketFormatUDT:
return UDTDataHeader{
Common: common,
Format: (header[1] & bit.B00001111),
PadNibble: (header[8] & bit.B11111000) >> 3,
AppendedBlocks: (header[8] & bit.B00000011),
SupplementaryFlag: (header[9] & bit.B10000000) > 0,
OPCode: (header[9] & bit.B00111111),
Format: (header[1] & B00001111),
PadNibble: (header[8] & B11111000) >> 3,
AppendedBlocks: (header[8] & B00000011),
SupplementaryFlag: (header[9] & B10000000) > 0,
OPCode: (header[9] & B00111111),
}, nil
case PacketFormatResponse:
return ResponseDataHeader{
Common: common,
BlocksToFollow: (header[8] & bit.B01111111),
Class: (header[9] & bit.B11000000) >> 6,
Type: (header[9] & bit.B00111000) >> 3,
Status: (header[9] & bit.B00000111),
BlocksToFollow: (header[8] & B01111111),
Class: (header[9] & B11000000) >> 6,
Type: (header[9] & B00111000) >> 3,
Status: (header[9] & B00000111),
}, nil
case PacketFormatUnconfirmedData:
return UnconfirmedDataHeader{
Common: common,
PadOctetCount: (header[0] & bit.B00010000) | (header[1] & bit.B00001111),
FullMessage: (header[8] & bit.B10000000) > 0,
BlocksToFollow: (header[8] & bit.B01111111),
FragmentSequenceNumber: (header[9] & bit.B00001111),
PadOctetCount: (header[0] & B00010000) | (header[1] & B00001111),
FullMessage: (header[8] & B10000000) > 0,
BlocksToFollow: (header[8] & B01111111),
FragmentSequenceNumber: (header[9] & B00001111),
}, nil
case PacketFormatConfirmedData:
return ConfirmedDataHeader{
Common: common,
PadOctetCount: (header[0] & bit.B00010000) | (header[1] & bit.B00001111),
FullMessage: (header[8] & bit.B10000000) > 0,
BlocksToFollow: (header[8] & bit.B01111111),
Resync: (header[9] & bit.B10000000) > 0,
SendSequenceNumber: (header[9] & bit.B01110000) >> 4,
FragmentSequenceNumber: (header[9] & bit.B00001111),
PadOctetCount: (header[0] & B00010000) | (header[1] & B00001111),
FullMessage: (header[8] & B10000000) > 0,
BlocksToFollow: (header[8] & B01111111),
Resync: (header[9] & B10000000) > 0,
SendSequenceNumber: (header[9] & B01110000) >> 4,
FragmentSequenceNumber: (header[9] & B00001111),
}, nil
case PacketFormatShortDataRaw:
return ShortDataRawDataHeader{
Common: common,
AppendedBlocks: (header[0] & bit.B00110000) | (header[1] & bit.B00001111),
SrcPort: (header[8] & bit.B11100000) >> 5,
DstPort: (header[8] & bit.B00011100) >> 2,
Resync: (header[8] & bit.B00000010) > 0,
FullMessage: (header[8] & bit.B00000001) > 0,
AppendedBlocks: (header[0] & B00110000) | (header[1] & B00001111),
SrcPort: (header[8] & B11100000) >> 5,
DstPort: (header[8] & B00011100) >> 2,
Resync: (header[8] & B00000010) > 0,
FullMessage: (header[8] & B00000001) > 0,
BitPadding: (header[9]),
}, nil
case PacketFormatShortDataDefined:
return ShortDataDefinedDataHeader{
Common: common,
AppendedBlocks: (header[0] & bit.B00110000) | (header[1] & bit.B00001111),
DDFormat: (header[8] & bit.B11111100) >> 2,
Resync: (header[8] & bit.B00000010) > 0,
FullMessage: (header[8] & bit.B00000001) > 0,
AppendedBlocks: (header[0] & B00110000) | (header[1] & B00001111),
DDFormat: (header[8] & B11111100) >> 2,
Resync: (header[8] & B00000010) > 0,
FullMessage: (header[8] & B00000001) > 0,
BitPadding: (header[9]),
}, nil
default:

@ -1,4 +0,0 @@
master: brandmeister.pd0zry.ampr.org:62030
authkey: secret
local: 0.0.0.0:62030
localid: 123456

@ -0,0 +1,2 @@
// Package dmr implements various protocols for interfacing Digital Mobile Radio repeaters and base stations.
package dmr

@ -1,4 +0,0 @@
radio_id: 2042214
auth_key: 7061737377307264
master: brandmeister.pd0zry.ampr.org:55000
listen: 0.0.0.0:55000

@ -1,22 +0,0 @@
// Package dmr contains generic message structures for the Digital Mobile Radio standard
package dmr
import (
"fmt"
"github.com/tehmaze/go-dmr/bit"
)
// Burst contains data from a single burst, see 4.2.2 Burst and frame structure
type Burst struct {
bits bit.Bits
}
func NewBurst(raw []byte) (*Burst, error) {
if len(raw)*8 != PayloadBits {
return nil, fmt.Errorf("dmr: expected %d bits, got %d", PayloadBits, len(raw)*8)
}
b := &Burst{}
b.bits = bit.NewBits(raw)
return b, nil
}

@ -1,17 +0,0 @@
package dmr
const (
PayloadBits = 98 + 10 + 48 + 10 + 98
PayloadSize = 33
InfoHalfBits = 98
InfoBits = 2 * InfoHalfBits
SlotTypeHalfBits = 10
SlotTypeBits = 2 * SlotTypeHalfBits
SignalBits = 48
SyncBits = SignalBits
VoiceHalfBits = 108
VoiceBits = 2 * VoiceHalfBits
EMBHalfBits = 8
EMBBits = 2 * EMBHalfBits
EMBSignallingLCFragmentBits = 32
)

@ -1,10 +0,0 @@
package dmr
import "github.com/tehmaze/go-dmr/bit"
func ExtractInfoBits(payload bit.Bits) bit.Bits {
var b = make(bit.Bits, InfoBits)
copy(b[:InfoHalfBits], payload[:InfoHalfBits])
copy(b[InfoHalfBits:], payload[InfoHalfBits+SignalBits+SlotTypeBits:])
return b
}

@ -1,8 +0,0 @@
package repeater
// DataFrame is a decoded frame with data
type DataFrame struct {
SrcID, DstID uint32
Timeslot uint8
Data []byte
}

@ -1,28 +0,0 @@
package repeater
import (
"github.com/tehmaze/go-dmr/bptc"
"github.com/tehmaze/go-dmr/dmr"
"github.com/tehmaze/go-dmr/ipsc"
)
func (r *Repeater) HandleDataHeader(p *ipsc.Packet) error {
var (
h dmr.DataHeader
err error
payload = make([]byte, 12)
)
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
return err
}
if h, err = dmr.ParseDataHeader(payload, false); err != nil {
return err
}
// TODO(maze): handle receiving of data blocks
switch h.(type) {
}
return nil
}

@ -1,108 +0,0 @@
package repeater
import (
"errors"
"fmt"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/bptc"
"github.com/tehmaze/go-dmr/dmr"
"github.com/tehmaze/go-dmr/fec"
"github.com/tehmaze/go-dmr/ipsc"
)
type LC struct {
CallType uint8
DstID, SrcID uint32
}
func (r *Repeater) HandleTerminatorWithLC(p *ipsc.Packet) error {
r.DataCallEnd(p)
var (
err error
payload = make([]byte, 12)
)
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
return err
}
// CRC mask to the checksum. See DMR AI. spec. page 143.
payload[9] ^= 0x99
payload[10] ^= 0x99
payload[11] ^= 0x99
var lc *LC
if lc, err = r.lcDecodeFullLC(payload); err != nil {
return err
}
fmt.Printf(" lc: %d -> %d\n", lc.SrcID, lc.DstID)
return nil
}
func (r *Repeater) HandleVoiceLCHeader(p *ipsc.Packet) error {
r.DataCallEnd(p)
var (
err error
payload = make([]byte, 12)
)
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
return err
}
// CRC mask to the checksum. See DMR AI. spec. page 143
for i := 9; i < 12; i++ {
payload[i] ^= 0x99
}
var lc *LC
if lc, err = r.lcDecodeFullLC(payload); err != nil {
return err
}
fmt.Printf(" lc: %d -> %d\n", lc.SrcID, lc.DstID)
return nil
}
func (r *Repeater) lcDecode(payload []byte) (*LC, error) {
if payload[0]&bit.B10000000 > 0 {
return nil, errors.New("dmr/lc: protect flag is not 0")
}
if payload[1] != 0 {
return nil, errors.New("dmr/lc: feature set ID is not 0")
}
lc := &LC{}
switch payload[0] & bit.B00111111 {
case 3:
lc.CallType = ipsc.CallTypePrivate
case 0:
lc.CallType = ipsc.CallTypeGroup
default:
return nil, fmt.Errorf("dmr/lc: invalid FCLO; unknown call type %#02x", payload[0]&bit.B00111111)
}
lc.DstID = uint32(payload[3])<<16 | uint32(payload[4])<<8 | uint32(payload[5])
lc.SrcID = uint32(payload[6])<<16 | uint32(payload[7])<<8 | uint32(payload[8])
return lc, nil
}
func (r *Repeater) lcDecodeFullLC(payload []byte) (*LC, error) {
var (
err error
syndrome = fec.RS_12_9_Poly{}
)
if err = fec.RS_12_9_CalcSyndrome(payload, &syndrome); err != nil {
return nil, err
}
if fec.RS_12_9_CheckSyndrome(&syndrome) {
if _, err = fec.RS_12_9_Correct(payload, &syndrome); err != nil {
return nil, err
}
}
return r.lcDecode(payload)
}

@ -1,144 +0,0 @@
package repeater
import (
"fmt"
"log"
"time"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/ipsc"
)
const (
Idle uint8 = iota
VoiceCallRunning
DataCallRunning
)
type SlotData struct {
BlocksReceived uint8
BlocksExpected uint8
PacketHeaderValid bool
}
type SlotVoice struct {
// Last frame number
Frame uint8
}
type Slot struct {
State uint8
LastCallReceived time.Time
CallStart time.Time
CallEnd time.Time
CallType uint8
SrcID, DstID uint32
Data SlotData
Voice SlotVoice
LastSequence uint8
LastSlotType uint16
}
type Repeater struct {
Slot []*Slot
DataFrameFunc func(*ipsc.Packet, bit.Bits)
VoiceFrameFunc func(*ipsc.Packet, bit.Bits)
}
func New() *Repeater {
r := &Repeater{
Slot: make([]*Slot, 2),
}
r.Slot[0] = &Slot{}
r.Slot[1] = &Slot{}
return r
}
func (r *Repeater) DataCallEnd(p *ipsc.Packet) {
if p.Timeslot > 1 {
return
}
slot := r.Slot[p.Timeslot]
if slot.State != DataCallRunning {
return
}
log.Printf("dmr/repeater: data call ended on TS%d, %d -> %d\n", p.Timeslot+1, slot.SrcID, slot.DstID)
slot.State = Idle
slot.CallEnd = time.Now()
slot.Data.PacketHeaderValid = false
}
func (r *Repeater) VoiceCallStart(p *ipsc.Packet) {
if p.Timeslot > 1 {
return
}
slot := r.Slot[p.Timeslot]
if slot.State == VoiceCallRunning {
r.VoiceCallEnd(p)
}
log.Printf("dmr/repeater: voice call started on TS%d, %d -> %d\n", p.Timeslot+1, slot.SrcID, slot.DstID)
slot.CallStart = time.Now()
slot.CallType = p.CallType
slot.SrcID = p.SrcID
slot.DstID = p.DstID
slot.State = VoiceCallRunning
}
func (r *Repeater) VoiceCallEnd(p *ipsc.Packet) {
if p.Timeslot > 1 {
return
}
slot := r.Slot[p.Timeslot]
if slot.State != VoiceCallRunning {
return
}
log.Printf("dmr/repeater: voice call ended on TS%d, %d -> %d\n", p.Timeslot+1, slot.SrcID, slot.DstID)
slot.State = Idle
slot.CallEnd = time.Now()
}
func (r *Repeater) Stream(p *ipsc.Packet) {
// Kill errneous timeslots here
if p.Timeslot > 1 {
log.Printf("killed packet with timeslot %d\n", p.Timeslot)
return
}
if p.Sequence == r.Slot[p.Timeslot].LastSequence {
return
}
r.Slot[p.Timeslot].LastSequence = p.Sequence
var err error
fmt.Printf("ts%d/dmr[%03d] [%d->%d]: %s: ", p.Timeslot+1, p.Sequence, p.SrcID, p.DstID, ipsc.SlotTypeName[p.SlotType])
switch p.SlotType {
case ipsc.VoiceLCHeader:
err = r.HandleVoiceLCHeader(p)
case ipsc.TerminatorWithLC:
err = r.HandleTerminatorWithLC(p)
case ipsc.DataHeader:
err = r.HandleDataHeader(p)
case ipsc.VoiceDataA, ipsc.VoiceDataB, ipsc.VoiceDataC, ipsc.VoiceDataD, ipsc.VoiceDataE, ipsc.VoiceDataF:
// De-duplicate packets, since we could run in merged TS1/2 mode
if r.Slot[p.Timeslot].LastSlotType == p.SlotType {
fmt.Println("(ignored)")
} else {
err = r.HandleVoiceData(p)
}
r.Slot[p.Timeslot].LastSlotType = p.SlotType
default:
fmt.Println("unhandled")
}
if err != nil {
fmt.Printf("error: %v\n", err)
}
}

@ -1,95 +0,0 @@
package repeater
import (
"errors"
"fmt"
"log"
"github.com/tehmaze/go-dmr/bptc"
"github.com/tehmaze/go-dmr/dmr"
"github.com/tehmaze/go-dmr/ipsc"
)
var voiceFrameMap = map[uint16]uint8{
ipsc.VoiceDataA: 0,
ipsc.VoiceDataB: 1,
ipsc.VoiceDataC: 2,
ipsc.VoiceDataD: 3,
ipsc.VoiceDataE: 4,
ipsc.VoiceDataF: 5,
}
func (r *Repeater) HandleVoiceData(p *ipsc.Packet) error {
r.DataCallEnd(p)
var (
err error
payload = make([]byte, 12)
)
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
return err
}
if r.Slot[p.Timeslot].State != VoiceCallRunning {
r.VoiceCallStart(p)
}
return r.HandleVoiceFrame(p)
}
func (r *Repeater) HandleVoiceFrame(p *ipsc.Packet) error {
// This may contain a sync frame
sync := dmr.ExtractSyncBits(p.PayloadBits)
patt := dmr.SyncPattern(sync)
if patt != dmr.SyncPatternUnknown && r.Slot[p.Timeslot].Voice.Frame != 0 {
fmt.Printf("sync pattern %s\n", dmr.SyncPatternName[patt])
r.Slot[p.Timeslot].Voice.Frame = 0
return nil
} else {
// This may be a duplicate frame
var (
oldFrame = r.Slot[p.Timeslot].Voice.Frame
newFrame = voiceFrameMap[p.SlotType]
)
switch {
case oldFrame > 5:
// Ignore, wait for next sync frame
return nil
case newFrame == oldFrame:
return errors.New("dmr/voice: ignored, duplicate frame")
case newFrame > oldFrame:
if newFrame-oldFrame > 1 {
log.Printf("dmr/voice: framedrop, went from %c -> %c", 'A'+oldFrame, 'A'+newFrame)
}
case newFrame < oldFrame:
if newFrame > 0 {
log.Printf("dmr/voice: framedrop, went from %c -> %c", 'A'+oldFrame, 'A'+newFrame)
}
}
r.Slot[p.Timeslot].Voice.Frame++
}
// If it's not a sync frame, then it should have an EMB inside the sync field.
var (
emb *dmr.EMB
err error
)
if emb, err = dmr.ParseEMB(dmr.ExtractEMBBitsFromSyncBits(sync)); err != nil {
fmt.Println("unknown sync pattern, no EMB")
}
if emb != nil {
fmt.Printf("EMB LCSS %d\n", emb.LCSS)
// TODO(maze): implement VBPTC matrix handling
switch emb.LCSS {
}
}
// Extract the three embedded AMBE frames if we have a callback function to process them.
if r.VoiceFrameFunc != nil {
r.VoiceFrameFunc(p, dmr.ExtractVoiceBits(p.PayloadBits))
}
return nil
}

@ -1,35 +0,0 @@
package dmr
import "github.com/tehmaze/go-dmr/bit"
var SlotTypeName = [16]string{
"PI Header", // 0000
"VOICE Header:", // 0001
"TLC:", // 0010
"CSBK:", // 0011
"MBC Header:", // 0100
"MBC:", // 0101
"DATA Header:", // 0110
"RATE 1/2 DATA:", // 0111
"RATE 3/4 DATA:", // 1000
"Slot idle", // 1001
"Rate 1 DATA", // 1010
"Unknown/Bad (11)", // 1011
"Unknown/Bad (12)", // 1100
"Unknown/Bad (13)", // 1101
"Unknown/Bad (14)", // 1110
"Unknown/Bad (15)", // 1111
}
func ExtractSlotType(payload bit.Bits) []byte {
bits := ExtractSlotTypeBits(payload)
return bits.Bytes()
}
func ExtractSlotTypeBits(payload bit.Bits) bit.Bits {
var b = make(bit.Bits, SlotTypeBits)
copy(b[:SlotTypeHalfBits], payload[InfoHalfBits:InfoHalfBits+SlotTypeHalfBits])
var o = InfoHalfBits + SlotTypeHalfBits + SyncBits
copy(b[SlotTypeHalfBits:], payload[o:o+SlotTypeHalfBits])
return b
}

@ -1,10 +0,0 @@
package dmr
import "github.com/tehmaze/go-dmr/bit"
func ExtractVoiceBits(payload bit.Bits) bit.Bits {
var b = make(bit.Bits, VoiceBits)
copy(b[:VoiceHalfBits], payload[:VoiceHalfBits])
copy(b[VoiceHalfBits:], payload[VoiceHalfBits+SyncBits:])
return b
}

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e5f51c3e75c21287d3038c1dc84d99838509e46ac65fcfda35a8f24ea0dc50fa
size 123620

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:560f886910ee68c9d17bebb17ea4f96b297ef77ff036a0bcd149da0b2316ec8c
size 1259856

@ -1,13 +1,9 @@
package fec
import (
"fmt"
import "fmt"
"github.com/tehmaze/go-dmr/bit"
)
func Golay_20_8_Parity(bits bit.Bits) bit.Bits {
var p = make(bit.Bits, 12)
func Golay_20_8_Parity(bits []byte) []byte {
var p = make([]byte, 12)
p[0] = bits[1] ^ bits[4] ^ bits[5] ^ bits[6] ^ bits[7]
p[1] = bits[1] ^ bits[2] ^ bits[4]
p[2] = bits[0] ^ bits[2] ^ bits[3] ^ bits[5]
@ -23,14 +19,14 @@ func Golay_20_8_Parity(bits bit.Bits) bit.Bits {
return p
}
func Golay_20_8_Check(bits bit.Bits) error {
func Golay_20_8_Check(bits []byte) error {
if len(bits) != 20 {
return fmt.Errorf("fec/golay_20_8: expected 20 bits, got %d", len(bits))
}
parity := Golay_20_8_Parity(bits[:8])
for i := 0; i < 20; i++ {
if parity[i] != bits[8+i] {
return fmt.Errorf("fec/golay_20_8: parity error at bit %d: %q != %q", i, parity.String(), bits[8:].String())
return fmt.Errorf("fec/golay_20_8: parity error at bit %d: %v != %v", i, parity, bits[8:])
}
}
return nil

@ -0,0 +1,12 @@
package homebrew
type Config struct {
// ID is the local DMR ID.
ID uint32
// PeerID is the remote DMR ID.
PeerID uint32
// AuthKey is the shared secret.
AuthKey string
}

@ -1,65 +0,0 @@
package homebrew
import (
"fmt"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/ipsc"
)
// ParseData reads a raw DMR data frame from the homebrew protocol and parses it as it were an IPSC packet.
func ParseData(data []byte) (*ipsc.Packet, error) {
if len(data) != 53 {
return nil, fmt.Errorf("invalid packet length %d, expected 53 bytes", len(data))
}
var (
slotType uint16
dataType = (data[15] & bit.B11110000) >> 4
frameType = (data[15] & bit.B00001100) >> 2
)
switch frameType {
case bit.B00000000, bit.B00000001: // voice/voice sync
switch dataType {
case bit.B00000000:
slotType = ipsc.VoiceDataA
case bit.B00000001:
slotType = ipsc.VoiceDataB
case bit.B00000010:
slotType = ipsc.VoiceDataC
case bit.B00000011:
slotType = ipsc.VoiceDataD
case bit.B00000100:
slotType = ipsc.VoiceDataE
case bit.B00000101:
slotType = ipsc.VoiceDataF
}
case bit.B00000010: // data sync
switch dataType {
case bit.B00000001:
slotType = ipsc.VoiceLCHeader
case bit.B00000010:
slotType = ipsc.TerminatorWithLC
case bit.B00000011:
slotType = ipsc.CSBK
case bit.B00000110:
slotType = ipsc.DataHeader
case bit.B00000111:
slotType = ipsc.Rate12Data
case bit.B00001000:
slotType = ipsc.Rate34Data
}
}
return &ipsc.Packet{
Timeslot: (data[15] & bit.B00000001),
CallType: (data[15] & bit.B00000010) >> 1,
FrameType: (data[15] & bit.B00001100) >> 2,
SlotType: slotType,
SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]),
DstID: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]),
Payload: data[20:],
PayloadBits: bit.NewBits(data[20:]),
Sequence: data[4],
}, nil
}

@ -3,413 +3,581 @@ package homebrew
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"log"
"net"
"runtime"
"strings"
"sync/atomic"
"os"
"strconv"
"sync"
"time"
"github.com/tehmaze/go-dmr/ipsc"
"github.com/tehmaze/go-dmr"
)
var logger *log.Logger
type AuthStatus uint8
const (
AuthNone AuthStatus = iota
AuthBegin
AuthDone
AuthFailed
)
// Messages as documented by DL5DI, G4KLX and DG1HT, see "DMRplus IPSC Protocol for HB repeater (20150726).pdf".
var (
Version = "20151214"
SoftwareID = fmt.Sprintf("%s:go-dmr:%s", runtime.GOOS, Version)
PackageID = fmt.Sprintf("%s:go-dmr:%s-%s", runtime.GOOS, Version, runtime.GOARCH)
DMRData = []byte("DMRD")
MasterNAK = []byte("MSTNAK")
MasterACK = []byte("MSTACK")
RepeaterLogin = []byte("RPTL")
RepeaterKey = []byte("RPTK")
MasterPing = []byte("MSTPING")
RepeaterPong = []byte("RPTPONG")
MasterClosing = []byte("MSTCL")
RepeaterClosing = []byte("RPTCL")
)
// RepeaterConfiguration holds information about the current repeater. It
// should be returned by a callback in the implementation, returning actual
// information about the current repeater status.
type RepeaterConfiguration struct {
Callsign string
ID uint32 // Our RepeaterID
RXFreq uint32
TXFreq uint32
TXPower uint8
ColorCode uint8
Latitude float32
Longitude float32
Height uint16
Location string
Description string
URL string
// We ping the peers every minute
var (
AuthTimeout = time.Second * 90
PingInterval = time.Minute
PingTimeout = time.Second * 150
RepeaterConfigurationInterval = time.Minute * 5
)
// Peer is a remote repeater that also speaks the Homebrew protocol
type Peer struct {
ID uint32
Addr *net.UDPAddr
AuthKey []byte
Status AuthStatus
Nonce []byte
Token []byte
Incoming bool
UnlinkOnAuthFailure bool
PacketReceived dmr.PacketFunc
Last struct {
PacketSent time.Time
PacketReceived time.Time
PingSent time.Time
PongReceived time.Time
RepeaterConfigurationSent time.Time
}
}
// Bytes returns the configuration as bytes.
func (r *RepeaterConfiguration) Bytes() []byte {
return []byte(r.String())
func (p *Peer) UpdateToken(nonce []byte) {
p.Nonce = nonce
hash := sha256.New()
hash.Write(p.Nonce)
hash.Write(p.AuthKey)
p.Token = []byte(hex.EncodeToString(hash.Sum(nil)))
}
// String returns the configuration as string.
func (r *RepeaterConfiguration) String() string {
if r.ColorCode < 1 {
r.ColorCode = 1
}
if r.ColorCode > 15 {
r.ColorCode = 15
}
if r.TXPower > 99 {
r.TXPower = 99
// Homebrew is implements the Homebrew IPSC DMR Air Interface protocol
type Homebrew struct {
Config *RepeaterConfiguration
Peer map[string]*Peer
PeerID map[uint32]*Peer
PacketReceived dmr.PacketFunc
conn *net.UDPConn
closed bool
id []byte
mutex *sync.Mutex
stop chan bool
}
// New creates a new Homebrew repeater
func New(config *RepeaterConfiguration, addr *net.UDPAddr) (*Homebrew, error) {
var err error
if config == nil {
return nil, errors.New("homebrew: RepeaterConfiguration can't be nil")
}
var lat = fmt.Sprintf("%-08f", r.Latitude)
if len(lat) > 8 {
lat = lat[:8]
h := &Homebrew{
Config: config,
Peer: make(map[string]*Peer),
PeerID: make(map[uint32]*Peer),
id: []byte(fmt.Sprintf("%08x", config.ID)),
mutex: &sync.Mutex{},
}
var lon = fmt.Sprintf("%-09f", r.Longitude)
if len(lon) > 9 {
lon = lon[:9]
if h.conn, err = net.ListenUDP("udp", addr); err != nil {
return nil, errors.New("homebrew: " + err.Error())
}
var b = "RPTC"
b += fmt.Sprintf("%-8s", r.Callsign)
b += fmt.Sprintf("%08x", r.ID)
b += fmt.Sprintf("%09d", r.RXFreq)
b += fmt.Sprintf("%09d", r.TXFreq)
b += fmt.Sprintf("%02d", r.TXPower)
b += fmt.Sprintf("%02d", r.ColorCode)
b += lat
b += lon
b += fmt.Sprintf("%03d", r.Height)
b += fmt.Sprintf("%-20s", r.Location)
b += fmt.Sprintf("%-20s", r.Description)
b += fmt.Sprintf("%-124s", r.URL)
b += fmt.Sprintf("%-40s", SoftwareID)
b += fmt.Sprintf("%-40s", PackageID)
return b
return h, nil
}
// ConfigFunc returns an actual RepeaterConfiguration instance when called.
// This is used by the DMR repeater to poll for current configuration,
// statistics and metrics.
type ConfigFunc func() *RepeaterConfiguration
// CallType reflects the DMR data frame call type.
type CallType byte
const (
GroupCall CallType = iota
UnitCall
)
// StreamFunc is called by the DMR repeater when a DMR data frame is received.
type StreamFunc func(*ipsc.Packet)
func (h *Homebrew) Active() bool {
return !h.closed && h.conn != nil
}
type authStatus byte
// Close stops the active listeners
func (h *Homebrew) Close() error {
h.mutex.Lock()
defer h.mutex.Unlock()
const (
authNone authStatus = iota
authBegin
authDone
authFail
)
if !h.Active() {
return nil
}
type Network struct {
AuthKey string
Local string
LocalID uint32
Master string
MasterID uint32
}
// Kill keepalive goroutine
if h.stop != nil {
close(h.stop)
h.stop = nil
}
type packet struct {
addr *net.UDPAddr
data []byte
// Kill listening socket
h.closed = true
return h.conn.Close()
}
type Link struct {
Dump bool
config ConfigFunc
stream StreamFunc
network *Network
conn *net.UDPConn
authKey []byte
local struct {
addr *net.UDPAddr
id []byte
// Link establishes a new link with a peer
func (h *Homebrew) Link(peer *Peer) error {
if peer.Addr == nil {
return errors.New("homebrew: peer Addr can't be nil")
}
master struct {
addr *net.UDPAddr
id []byte
status authStatus
secret []byte
keepalive struct {
outstanding uint32
sent uint64
}
if peer.AuthKey == nil || len(peer.AuthKey) == 0 {
return errors.New("homebrew: peer AuthKey can't be nil")
}
}
// New starts a new DMR repeater using the Home Brew protocol.
func New(network *Network, cf ConfigFunc, sf StreamFunc) (*Link, error) {
if cf == nil {
return nil, errors.New("config func can't be nil")
}
h.mutex.Lock()
defer h.mutex.Unlock()
link := &Link{
network: network,
config: cf,
stream: sf,
}
// Reset state
peer.Last.PacketSent = time.Time{}
peer.Last.PacketReceived = time.Time{}
peer.Last.PingSent = time.Time{}
peer.Last.PongReceived = time.Time{}
var err error
if strings.HasPrefix(network.AuthKey, "0x") {
if link.authKey, err = hex.DecodeString(network.AuthKey[2:]); err != nil {
return nil, err
}
} else {
link.authKey = []byte(network.AuthKey)
}
if network.Local == "" {
network.Local = "0.0.0.0:62030"
}
if network.LocalID == 0 {
return nil, errors.New("missing localid")
}
link.local.id = []byte(fmt.Sprintf("%08x", network.LocalID))
if link.local.addr, err = net.ResolveUDPAddr("udp", network.Local); err != nil {
return nil, err
}
if network.Master == "" {
return nil, errors.New("no master address configured")
}
if link.master.addr, err = net.ResolveUDPAddr("udp", network.Master); err != nil {
return nil, err
}
// Register our peer
h.Peer[peer.Addr.String()] = peer
h.PeerID[peer.ID] = peer
return link, nil
return h.handleAuth(peer)
}
// Close stops the socket and stops the runner
func (l *Link) Close() error {
if l.conn == nil {
return errors.New("dmr/homebrew: link not open")
}
if l.master.addr != nil {
l.Send(l.master.addr, append(RepeaterClosing, l.local.id...))
func (h *Homebrew) Unlink(id uint32) error {
h.mutex.Lock()
defer h.mutex.Unlock()
peer, ok := h.PeerID[id]
if !ok {
return fmt.Errorf("homebrew: peer %d not linked", id)
}
return l.conn.Close()
delete(h.Peer, peer.Addr.String())
delete(h.PeerID, id)
return nil
}
// Run starts the datagram receiver and logs the repeater in with the master.
func (l *Link) Run() error {
var err error
func (h *Homebrew) ListenAndServe() error {
var data = make([]byte, 53)
h.stop = make(chan bool)
go h.keepalive(h.stop)
if l.conn, err = net.ListenUDP("udp", l.local.addr); err != nil {
return err
h.closed = false
for !h.closed {
n, peer, err := h.conn.ReadFromUDP(data)
if err != nil {
return err
}
if err := h.handle(peer, data[:n]); err != nil {
return err
}
}
queue := make(chan packet)
go l.login()
go l.parse(queue)
return nil
}
receiving:
for {
var (
n int
peer *net.UDPAddr
data = make([]byte, 512)
)
if n, peer, err = l.conn.ReadFromUDP(data); err != nil {
if peer == nil {
break receiving
}
log.Printf("dmr/homebrew: error reading from %s: %v\n", peer, err)
continue
}
func (h *Homebrew) WritePacketToPeer(p *dmr.Packet, peer *Peer) error {
return h.WriteToPeer(h.parsePacket(p), peer)
}
queue <- packet{peer, data[:n]}
func (h *Homebrew) WriteToPeer(b []byte, peer *Peer) error {
if peer == nil {
return errors.New("homebrew: can't write to nil peer")
}
// Because we close it in .Close()
if strings.HasSuffix(err.Error(), ": use of closed network connection") {
return nil
}
peer.Last.PacketSent = time.Now()
_, err := h.conn.WriteTo(b, peer.Addr)
return err
}
// Send data to an UDP address using the repeater datagram socket.
func (l *Link) Send(addr *net.UDPAddr, data []byte) error {
for len(data) > 0 {
n, err := l.conn.WriteToUDP(data, addr)
if err != nil {
return err
}
data = data[n:]
func (h *Homebrew) WriteToPeerWithID(b []byte, id uint32) error {
return h.WriteToPeer(b, h.getPeer(id))
}
func (h *Homebrew) getPeer(id uint32) *Peer {
h.mutex.Lock()
defer h.mutex.Unlock()
if peer, ok := h.PeerID[id]; ok {
return peer
}
return nil
}
func (l *Link) login() {
var previous = authDone
for l.master.status != authFail {
var p []byte
func (h *Homebrew) getPeerByAddr(addr *net.UDPAddr) *Peer {
h.mutex.Lock()
defer h.mutex.Unlock()
if l.master.status != previous {
switch l.master.status {
case authNone:
log.Printf("dmr/homebrew: logging in as %d\n", l.network.LocalID)
p = append(RepeaterLogin, l.local.id...)
if peer, ok := h.Peer[addr.String()]; ok {
return peer
}
case authBegin:
log.Printf("dmr/homebrew: authenticating as %d\n", l.network.LocalID)
p = append(RepeaterKey, l.local.id...)
return nil
}
hash := sha256.New()
hash.Write(l.master.secret)
hash.Write(l.authKey)
func (h *Homebrew) getPeers() []*Peer {
h.mutex.Lock()
defer h.mutex.Unlock()
p = append(p, []byte(hex.EncodeToString(hash.Sum(nil)))...)
var peers = make([]*Peer, 0)
for _, peer := range h.Peer {
peers = append(peers, peer)
}
case authDone:
config := l.config().Bytes()
if l.Dump {
fmt.Printf(hex.Dump(config))
}
log.Printf("dmr/homebrew: logged in, sending %d bytes of repeater configuration\n", len(config))
return peers
}
if err := l.Send(l.master.addr, config); err != nil {
log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
return
}
l.keepAlive()
return
func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
peer := h.getPeerByAddr(remote)
if peer == nil {
logger.Printf("ignored packet from unknown peer %s\n", remote)
return nil
}
case authFail:
log.Println("dmr/homebrew: login failed")
return
}
if p != nil {
l.Send(l.master.addr, p)
}
previous = l.master.status
} else {
log.Println("dmr/homebrew: waiting for master to respond in login sequence...")
time.Sleep(time.Second)
}
// Ignore packet that are clearly invalid, this is the minimum packet length for any Homebrew protocol frame
if len(data) < 14 {
return nil
}
}
func (l *Link) keepAlive() {
for {
atomic.AddUint32(&l.master.keepalive.outstanding, 1)
atomic.AddUint64(&l.master.keepalive.sent, 1)
var p = append(MasterPing, l.local.id...)
if err := l.Send(l.master.addr, p); err != nil {
log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
return
if peer.Status != AuthDone {
// Ignore DMR data at this stage
if bytes.Equal(data[:4], DMRData) {
return nil
}
time.Sleep(time.Minute)
}
}
func (l *Link) parse(queue <-chan packet) {
for {
select {
case p := <-queue:
size := len(p.data)
if size < 4 {
continue
if peer.Incoming {
// Verify we have a matching peer ID
id, err := h.parseRepeaterID(data[4:])
if err != nil {
logger.Printf("peer %d@%s sent invalid repeater ID (ignored)\n", peer.ID, remote)
return nil
}
var ok = id == peer.ID
if !ok {
logger.Printf("peer %d@%s sent unexpected repeater ID %d\n", peer.ID, remote, id)
}
switch l.master.status {
case authNone:
if bytes.Equal(p.data[:4], DMRData) {
return
}
if size < 14 {
return
}
packet := p.data[:6]
repeater, err := hex.DecodeString(string(p.data[6:14]))
if err != nil {
log.Println("dmr/homebrew: unexpected login reply from master")
l.master.status = authFail
break
}
switch peer.Status {
case AuthNone:
switch {
case bytes.Equal(packet, MasterNAK):
log.Printf("dmr/homebrew: login refused by master %d\n", repeater)
l.master.status = authFail
break
case bytes.Equal(packet, MasterACK):
log.Printf("dmr/homebrew: login accepted by master %d\n", repeater)
l.master.secret = p.data[14:]
l.master.status = authBegin
break
case bytes.Equal(data[:4], RepeaterLogin):
if !ok {
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
// Peer is verified, generate a nonce
nonce := make([]byte, 4)
if _, err := rand.Read(nonce); err != nil {
logger.Printf("peer %d@%s nonce generation failed: %v\n", peer.ID, remote, err)
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
peer.UpdateToken(nonce)
peer.Status = AuthBegin
return h.WriteToPeer(append(append(MasterACK, h.id...), nonce...), peer)
default:
log.Printf("dmr/homebrew: unexpected login reply from master %d\n", repeater)
l.master.status = authFail
// Ignore unauthenticated repeater, we're not going to reply unless it's
// an actual login request; if it was indeed a valid repeater and we missed
// anything, we rely on the remote end to retry to reconnect if it doesn't
// get an answer in a timely manner.
break
}
break
case authBegin:
if bytes.Equal(p.data[:4], DMRData) {
return
case AuthBegin:
switch {
case bytes.Equal(data[:4], RepeaterKey):
if ok && len(data) != 76 {
logger.Printf("peer %d@%s sent invalid key challenge length of %d\n", peer.ID, remote, len(data))
ok = false
}
if !ok {
peer.Status = AuthNone
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
if !bytes.Equal(data[12:], peer.Token) {
logger.Printf("peer %d@%s sent invalid key challenge token\n", peer.ID, remote)
peer.Status = AuthNone
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
peer.Status = AuthDone
return h.WriteToPeer(append(MasterACK, h.id...), peer)
}
if size < 14 {
log.Println("dmr/homebrew: unexpected login reply from master")
l.master.status = authFail
}
} else {
// Verify we have a matching peer ID
id, err := h.parseRepeaterID(data[6:14])
if err != nil {
logger.Printf("peer %d@%s sent invalid repeater ID (ignored)\n", peer.ID, remote)
return nil
}
if id != peer.ID {
logger.Printf("peer %d@%s sent unexpected repeater ID %d (ignored)\n", peer.ID, remote, id)
return nil
}
switch peer.Status {
case AuthNone:
switch {
case bytes.Equal(data[:6], MasterACK):
logger.Printf("peer %d@%s sent nonce\n", peer.ID, remote)
peer.Status = AuthBegin
peer.UpdateToken(data[14:])
return h.handleAuth(peer)
case bytes.Equal(data[:6], MasterNAK):
logger.Printf("peer %d@%s refused login\n", peer.ID, remote)
peer.Status = AuthFailed
if peer.UnlinkOnAuthFailure {
h.Unlink(peer.ID)
}
break
}
packet := p.data[:6]
repeater, err := hex.DecodeString(string(p.data[6:14]))
if err != nil {
log.Println("dmr/homebrew: unexpected login reply from master")
l.master.status = authFail
default:
logger.Printf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
break
}
case AuthBegin:
switch {
case bytes.Equal(packet, MasterNAK):
log.Printf("dmr/homebrew: authentication refused by master %d\n", repeater)
l.master.status = authFail
break
case bytes.Equal(packet, MasterACK):
log.Printf("dmr/homebrew: authentication accepted by master %d\n", repeater)
l.master.status = authDone
case bytes.Equal(data[:6], MasterACK):
logger.Printf("peer %d@%s accepted login\n", peer.ID, remote)
peer.Status = AuthDone
peer.Last.RepeaterConfigurationSent = time.Now()
return h.WriteToPeer(h.Config.Bytes(), peer)
case bytes.Equal(data[:6], MasterNAK):
logger.Printf("peer %d@%s refused login\n", peer.ID, remote)
peer.Status = AuthFailed
if peer.UnlinkOnAuthFailure {
h.Unlink(peer.ID)
}
break
default:
log.Printf("dmr/homebrew: unexpected authentication reply from master %d\n", repeater)
l.master.status = authFail
logger.Printf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
break
}
}
}
} else {
// Authentication is done
switch {
case bytes.Equal(data[:4], DMRData):
p, err := h.parseData(data[4:])
if err != nil {
return err
}
return h.handlePacket(p, peer)
case peer.Incoming && len(data) == 15 && bytes.Equal(data[:7], MasterPing):
// Verify we have a matching peer ID
id, err := h.parseRepeaterID(data[7:])
if err != nil {
logger.Printf("peer %d@%s sent invalid repeater ID (ignored)\n", peer.ID, remote)
return nil
}
if id != peer.ID {
logger.Printf("peer %d@%s sent unexpected repeater ID %d (ignored)\n", peer.ID, remote, id)
return nil
}
return h.WriteToPeer(append(RepeaterPong, h.id...), peer)
default:
logger.Printf("peer %d@%s sent unexpected packet\n", peer.ID, remote)
break
}
}
return nil
}
func (h *Homebrew) handleAuth(peer *Peer) error {
if !peer.Incoming {
switch peer.Status {
case AuthNone:
// Send login packet
return h.WriteToPeer(append(RepeaterLogin, h.id...), peer)
case authDone:
if l.Dump {
fmt.Printf("received from %s:\n", p.addr)
fmt.Print(hex.Dump(p.data))
case AuthBegin:
// Send repeater key exchange packet
return h.WriteToPeer(append(append(RepeaterKey, h.id...), peer.Token...), peer)
}
}
return nil
}
func (h *Homebrew) handlePacket(p *dmr.Packet, peer *Peer) error {
if peer.PacketReceived != nil {
return peer.PacketReceived(h, p)
}
if h.PacketReceived == nil {
return errors.New("homebrew: no PacketReceived func defined to handle DMR packet")
}
return h.PacketReceived(h, p)
}
func (h *Homebrew) keepalive(stop <-chan bool) {
for {
select {
case <-time.After(time.Second):
now := time.Now()
for _, peer := range h.getPeers() {
// Ping protocol only applies to outgoing links, and also the auth retries
// are entirely up to the peer.
if peer.Incoming {
continue
}
switch {
case bytes.Equal(p.data[:4], DMRData):
l.parseDMR(p.data)
switch peer.Status {
case AuthNone, AuthBegin:
switch {
case now.Sub(peer.Last.PacketReceived) > AuthTimeout:
logger.Printf("peer %d@%s not responding to login; retrying\n", peer.ID, peer.Addr)
if err := h.handleAuth(peer); err != nil {
logger.Printf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
}
break
}
case AuthDone:
switch {
case now.Sub(peer.Last.PongReceived) > PingTimeout:
peer.Status = AuthNone
logger.Printf("peer %d@%s not responding to ping; trying to re-establish connection", peer.ID, peer.Addr)
if err := h.handleAuth(peer); err != nil {
logger.Printf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
}
break
case now.Sub(peer.Last.PingSent) > PingInterval:
peer.Last.PingSent = now
if err := h.WriteToPeer(append(MasterPing, h.id...), peer); err != nil {
logger.Printf("peer %d@%s ping failed: %v\n", peer.ID, peer.Addr, err)
}
break
case now.Sub(peer.Last.RepeaterConfigurationSent) > RepeaterConfigurationInterval:
peer.Last.RepeaterConfigurationSent = time.Now()
if err := h.WriteToPeer(h.Config.Bytes(), peer); err != nil {
logger.Printf("peer %d@%s repeater configuration failed: %v\n", peer.ID, peer.Addr, err)
}
break
}
}
}
case <-stop:
return
}
}
}
func (l *Link) parseDMR(d []byte) {
// If we have no stream callback, don't even bother to decode the DMR data frame.
if l.stream == nil {
return
// parseData converts Homebrew packet format to DMR packet format
func (h *Homebrew) parseData(data []byte) (*dmr.Packet, error) {
if len(data) != 53 {
return nil, fmt.Errorf("homebrew: expected 53 data bytes, got %d", len(data))
}
var p = &dmr.Packet{
Sequence: data[4],
SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]),
DstID: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]),
}
p.SetData(data[20:])
return p, nil
}
// parsePacket converts DMR packet format to Homebrew packet format suitable for sending on the wire
func (h *Homebrew) parsePacket(p *dmr.Packet) []byte {
var d = make([]byte, 53)
// Signature, 4 bytes, "DMRD"
copy(d[0:], DMRData)
// Seq No, 1 byte
d[4] = p.Sequence
// Src ID, 3 bytes
d[5] = uint8((p.SrcID >> 16) & 0xff)
d[6] = uint8((p.SrcID >> 8) & 0xff)
d[7] = uint8((p.SrcID & 0xff))
// Dst ID, 3 bytes
d[8] = uint8((p.DstID >> 16) & 0xff)
d[9] = uint8((p.DstID >> 8) & 0xff)
d[10] = uint8((p.DstID & 0xff))
// RptrID, 4 bytes
binary.LittleEndian.PutUint32(d[11:], p.RepeaterID)
var s byte
s |= (p.Timeslot & 0x01) // Slot no, 1 bit
s |= (p.CallType & 0x01) << 1 // Call Type, 1 bit
switch p.DataType { // Frame Type, 2 bits and Data Type or Voice Seq, 4 bits
case dmr.VoiceBurstA:
s |= 0x01 << 2 // Voice sync
s |= (p.DataType - dmr.VoiceBurstA) << 4
case dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF:
s |= 0x00 << 2 // Voice (no-op)
s |= (p.DataType - dmr.VoiceBurstA) << 4
default:
s |= 0x02 << 2 // Data
s |= p.DataType << 4
}
var (
p *ipsc.Packet
err error
)
if p, err = ParseData(d); err != nil {
log.Printf("dmr/homebrew: error parsing DMRD frame: %v\n", err)
return
// StreamID, 4 bytes
binary.BigEndian.PutUint32(d[16:], p.StreamID)
// DMR Data, 33 bytes
copy(d[20:], p.Data)
return d
}
func (h *Homebrew) parseRepeaterID(data []byte) (uint32, error) {
id, err := strconv.ParseUint(string(data), 10, 32)
if err != nil {
return 0, err
}
l.stream(p)
return uint32(id), nil
}
// Interface compliance check
var _ dmr.Repeater = (*Homebrew)(nil)
// UpdateLogger replaces the package logger.
func UpdateLogger(l *log.Logger) {
logger = l
}
func init() {
UpdateLogger(log.New(os.Stderr, "dmr/homebrew: ", log.LstdFlags))
}

@ -1,15 +0,0 @@
package homebrew
// Messages as documented by DL5DI, G4KLX and DG1HT, see
// http://download.prgm.org/dl5di-soft/dmrplus/documentation/Homebrew-Repeater/DMRplus%20IPSC%20Protocol%20for%20HB%20repeater%20(20150726).pdf
var (
DMRData = []byte("DMRD")
MasterNAK = []byte("MSTNAK")
MasterACK = []byte("MSTACK")
RepeaterLogin = []byte("RPTL")
RepeaterKey = []byte("RPTK")
MasterPing = []byte("MSTPING")
RepeaterPong = []byte("RPTPONG")
MasterClosing = []byte("MSTCL")
RepeaterClosing = []byte("RPTCL")
)

@ -0,0 +1,74 @@
package homebrew
import (
"fmt"
"github.com/tehmaze/go-dmr"
)
// RepeaterConfiguration holds information about the current repeater. It
// should be returned by a callback in the implementation, returning actual
// information about the current repeater status.
type RepeaterConfiguration struct {
Callsign string
ID uint32 // Our RepeaterID
RXFreq uint32
TXFreq uint32
TXPower uint8
ColorCode uint8
Latitude float32
Longitude float32
Height uint16
Location string
Description string
URL string
}
// Bytes returns the configuration as bytes.
func (r *RepeaterConfiguration) Bytes() []byte {
return []byte(r.String())
}
// String returns the configuration as string.
func (r *RepeaterConfiguration) String() string {
if r.ColorCode < 1 {
r.ColorCode = 1
}
if r.ColorCode > 15 {
r.ColorCode = 15
}
if r.TXPower > 99 {
r.TXPower = 99
}
var lat = fmt.Sprintf("%-08f", r.Latitude)
if len(lat) > 8 {
lat = lat[:8]
}
var lon = fmt.Sprintf("%-09f", r.Longitude)
if len(lon) > 9 {
lon = lon[:9]
}
var b = "RPTC"
b += fmt.Sprintf("%-8s", r.Callsign)
b += fmt.Sprintf("%08x", r.ID)
b += fmt.Sprintf("%09d", r.RXFreq)
b += fmt.Sprintf("%09d", r.TXFreq)
b += fmt.Sprintf("%02d", r.TXPower)
b += fmt.Sprintf("%02d", r.ColorCode)
b += lat
b += lon
b += fmt.Sprintf("%03d", r.Height)
b += fmt.Sprintf("%-20s", r.Location)
b += fmt.Sprintf("%-20s", r.Description)
b += fmt.Sprintf("%-124s", r.URL)
b += fmt.Sprintf("%-40s", dmr.SoftwareID)
b += fmt.Sprintf("%-40s", dmr.PackageID)
return b
}
// ConfigFunc returns an actual RepeaterConfiguration instance when called.
// This is used by the DMR repeater to poll for current configuration,
// statistics and metrics.
type ConfigFunc func() *RepeaterConfiguration

@ -4,7 +4,6 @@ import (
"encoding/hex"
"fmt"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/dmr"
)
@ -70,8 +69,8 @@ type Packet struct {
CallType uint8 // 0=private, 1=group
SrcID uint32
DstID uint32
Payload []byte // 34 bytes
PayloadBits bit.Bits // 264 bits
Payload []byte // 34 bytes
PayloadBits []byte // 264 bits
Sequence uint8
}
@ -90,15 +89,15 @@ func (p *Packet) Dump() string {
return s
}
func (p *Packet) InfoBits() bit.Bits {
var b = make(bit.Bits, dmr.InfoBits)
func (p *Packet) InfoBits() []byte {
var b = make([]byte, dmr.InfoBits)
copy(b[0:dmr.InfoHalfBits], p.PayloadBits[0:dmr.InfoHalfBits])
copy(b[dmr.InfoHalfBits:], p.PayloadBits[dmr.InfoHalfBits+dmr.SlotTypeBits+dmr.SignalBits:])
return b
}
func (p *Packet) VoiceBits() bit.Bits {
var b = make(bit.Bits, dmr.VoiceBits)
func (p *Packet) VoiceBits() []byte {
var b = make([]byte, dmr.VoiceBits)
copy(b[:dmr.VoiceHalfBits], p.PayloadBits[:dmr.VoiceHalfBits])
copy(b[dmr.VoiceHalfBits:], p.PayloadBits[dmr.VoiceHalfBits+dmr.SignalBits:])
return b

@ -1,400 +0,0 @@
/* vi:si:noexpandtab:sw=4:sts=4:ts=4
*/
/*
* oggfwd -- Forward an Ogg stream from stdin to an Icecast server
* A useful demonstration of the libshout API
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* This program is provided "as-is", with no explicit or implied warranties of
* any kind.
*
* Copyright (C) 2003-2006, J <j@v2v.cc>,
* rafael2k <rafael(at)riseup(dot)net>,
* Moritz Grimm <gtgbr@gmx.net>
* Copyright (C) 2015, Philipp Schafft <lion@lion.leolix.org>
*/
/* thanx to rhatto <rhatto (AT) riseup (DOT) net> and others at submidialogia :-P */
#include <sys/types.h>
#include <sys/param.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifndef NO_UNISTD_H
# include <unistd.h>
#endif /* no-NO_UNISTD_H */
#include <shout/shout.h>
extern char *__progname;
extern char *optarg;
extern int optind;
extern int errno;
volatile sig_atomic_t print_total = 0;
volatile sig_atomic_t quit = 0;
#define METABUFSIZE 4096
char *metafilename;
shout_t *shout;
#define BUFFERSIZE 4096
#if defined(__dead)
__dead void
#else
void
#endif /* __dead */
usage(void)
{
printf("usage: %s "
"[-hp] "
#ifdef SHOUT_TLS
"[-T {disabled|auto|auto_no_plain|rfc2818|rfc2817}] "
#endif
"[-m metadata file] "
"[-d description] "
"[-g genre] "
"[-n name] "
"[-u URL]\n"
" address port password mountpoint\n",
__progname);
exit(1);
}
void
load_metadata()
{
int i, fh, r;
char buf[METABUFSIZE], *key, *val;
enum {state_comment, state_key, state_value, state_unknown} state;
bzero(buf, METABUFSIZE);
if (!metafilename) {
fprintf(stderr, "Please use the -m argument to set the meta file name!\n");
return;
}
fh = open(metafilename, O_RDONLY);
if (-1==fh) {
fprintf(stderr, "Error while opening meta file \"%s\": %s\n", metafilename, strerror(errno));
return;
}
r = read(fh, &buf, METABUFSIZE);
if (-1==r) {
fprintf(stderr, "Error while reading meta file \"%s\": %s\n", metafilename, strerror(errno));
close(fh);
return;
}
state = state_unknown;
key = val = NULL;
i = 0;
while (i<METABUFSIZE) {
switch (buf[i]) {
case 0:
/* we're done */
i = METABUFSIZE;
break;
case '\r':
case '\n':
if (state_value==state) {
buf[i] = 0;
if (key && val) {
if (0==strcmp("name", key)) {
shout_set_name(shout, val);
} else if (0==strcmp("genre", key)) {
shout_set_genre(shout, val);
} else if (0==strcmp("description", key)) {
shout_set_description(shout, val);
} else if (0==strcmp("url", key)) {
shout_set_url(shout, val);
}
}
}
state = state_unknown;
key = NULL;
val = NULL;
break;
case '=':
if (state_key==state) {
buf[i] = 0;
state = state_value;
val = &buf[i+1];
}
break;
case '#':
if (state_unknown==state) {
state = state_comment;
}
break;
default:
if (state_unknown==state) {
state = state_key;
key = &buf[i];
}
}
i++;
}
close(fh);
}
void
sig_handler(int sig)
{
switch (sig) {
case SIGHUP:
print_total = 1;
break;
case SIGTERM:
case SIGINT:
quit = 1;
break;
case SIGUSR1:
load_metadata();
break;
default:
/* ignore */
break;
}
}
void
set_argument_string(char **param, char *opt, char optname)
{
size_t siz;
if (*param) {
fprintf(stderr, "%s: Parameter -%c given multiple times\n",
__progname, optname);
usage();
}
siz = strlen(opt) + 1;
if (siz >= MAXPATHLEN) {
fprintf(stderr, "%s: Argument for parameter -%c too long\n",
__progname, optname);
exit(1);
}
if ((*param = malloc(siz)) == NULL) {
fprintf(stderr, "%s: %s\n", __progname, strerror(errno));
exit(1);
}
snprintf(*param, siz, "%s", opt);
}
#ifdef SHOUT_TLS
void
set_tls_mode(int *tls_mode, char *opt, char optname)
{
if (0==strcasecmp("DISABLED", opt)) {
*tls_mode = SHOUT_TLS_DISABLED;
} else if (0==strcasecmp("AUTO", opt)) {
*tls_mode = SHOUT_TLS_AUTO;
} else if (0==strcasecmp("AUTO_NO_PLAIN", opt)) {
*tls_mode = SHOUT_TLS_AUTO_NO_PLAIN;
} else if (0==strcasecmp("RFC2818", opt)) {
*tls_mode = SHOUT_TLS_RFC2818;
} else if (0==strcasecmp("RFC2817", opt)) {
*tls_mode = SHOUT_TLS_RFC2817;
} else {
fprintf(stderr, "%s: Invalid value for -%c.\n",
__progname, optname);
usage();
exit(1);
}
}
#endif
int
main(int argc, char **argv)
{
unsigned char buff[BUFFERSIZE];
int ret, ch;
unsigned int pFlag;
char *description, *genre, *name, *url;
size_t bytes_read = 0;
unsigned short port;
unsigned long long total;
#ifdef SHOUT_TLS
int tls_mode = SHOUT_TLS_AUTO;
#endif
pFlag = 0;
description = genre = name = url = metafilename = NULL;
while ((ch = getopt(argc, argv, "d:g:hn:m:pu:T:")) != -1) {
switch (ch) {
case 'd':
set_argument_string(&description, optarg, 'D');
break;
case 'g':
set_argument_string(&genre, optarg, 'g');
break;
case 'n':
set_argument_string(&name, optarg, 'n');
break;
case 'm':
set_argument_string(&metafilename, optarg, 'm');
break;
case 'p':
pFlag = 1;
break;
case 'u':
set_argument_string(&url, optarg, 'u');
break;
case 'T':
#ifdef SHOUT_TLS
set_tls_mode(&tls_mode, optarg, 'T');
break;
#endif
case 'h':
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc != 4) {
fprintf(stderr, "%s: Wrong number of arguments\n", __progname);
usage();
}
if ((shout = shout_new()) == NULL) {
fprintf(stderr, "%s: Could not allocate shout_t\n",
__progname);
return (1);
}
shout_set_format(shout, SHOUT_FORMAT_OGG);
#ifdef SHOUT_TLS
if (shout_set_tls(shout, tls_mode) != SHOUTERR_SUCCESS) {
fprintf(stderr, "%s: Error setting TLS mode: %s\n", __progname,
shout_get_error(shout));
return (1);
}
#endif
if (shout_set_host(shout, argv[0]) != SHOUTERR_SUCCESS) {
fprintf(stderr, "%s: Error setting hostname: %s\n", __progname,
shout_get_error(shout));
return (1);
}
if (sscanf(argv[1], "%hu", &port) != 1) {
fprintf(stderr, "Invalid port `%s'\n", argv[1]);
usage();
}
if (shout_set_port(shout, port) != SHOUTERR_SUCCESS) {
fprintf(stderr, "%s: Error setting port: %s\n", __progname,
shout_get_error(shout));
return (1);
}
if (shout_set_password(shout, argv[2]) != SHOUTERR_SUCCESS) {
fprintf(stderr, "%s: Error setting password: %s\n", __progname,
shout_get_error(shout));
return (1);
}
if (shout_set_mount(shout, argv[3]) != SHOUTERR_SUCCESS) {
fprintf(stderr, "%s: Error setting mount: %s\n", __progname,
shout_get_error(shout));
return (1);
}
shout_set_public(shout, pFlag);
if (metafilename)
load_metadata();
if (description)
shout_set_description(shout, description);
if (genre)
shout_set_genre(shout, genre);
if (name)
shout_set_name(shout, name);
if (url)
shout_set_url(shout, url);
signal(SIGUSR1, sig_handler);
//wait for data before opening connection to server
bytes_read = fread(buff, 1, sizeof(buff), stdin);
if (shout_open(shout) == SHOUTERR_SUCCESS) {
printf("%s: Connected to server\n", __progname);
total = 0;
signal(SIGHUP, sig_handler);
signal(SIGTERM, sig_handler);
signal(SIGINT, sig_handler);
while (quit == 0) {
total += bytes_read;
if (bytes_read > 0) {
ret = shout_send(shout, buff, bytes_read);
if (ret != SHOUTERR_SUCCESS) {
printf("%s: Send error: %s\n",
__progname,
shout_get_error(shout));
quit = 1;
}
} else
quit = 1;
if (quit) {
printf("%s: Quitting ...\n", __progname);
print_total = 1;
}
if (print_total) {
printf("%s: Total bytes read: %llu\n",
__progname, total);
print_total = 0;
}
shout_sync(shout);
bytes_read = fread(buff, 1, sizeof(buff), stdin);
if (bytes_read != sizeof(buff) && feof(stdin)) {
quit = 1;
}
}
} else {
fprintf(stderr, "%s: Error connecting: %s\n", __progname,
shout_get_error(shout));
return (1);
}
shout_close(shout);
return (0);
}

@ -0,0 +1,110 @@
package dmr
// Data Type information element definitions, DMR Air Interface (AI) protocol, Table 6.1
const (
PrivacyIndicator uint8 = iota // Privacy Indicator information in a standalone burst
VoiceLC // Indicates the beginning of voice transmission, carries addressing information
TerminatorWithLC // Indicates the end of transmission, carries LC information
ControlBlock // Carries a control block
MultiBlockControl // Header for multi-block control
MultiBlockControlContinuation // Follow-on blocks for multi-block control
Data // Carries addressing and numbering of packet data blocks
Rate12Data // Payload for rate 1/2 packet data
Rate34Data // Payload for rate 34 packet data
Idle // Fills channel when no info to transmit
VoiceBurstA // Burst A marks the start of a superframe and always contains a voice SYNC pattern
VoiceBurstB // Bursts B to F carry embedded signalling in place of the SYNC pattern
VoiceBurstC // Bursts B to F carry embedded signalling in place of the SYNC pattern
VoiceBurstD // Bursts B to F carry embedded signalling in place of the SYNC pattern
VoiceBurstE // Bursts B to F carry embedded signalling in place of the SYNC pattern
VoiceBurstF // Bursts B to F carry embedded signalling in place of the SYNC pattern
IPSCSync
UnknownSlotType
)
// Call Type
const (
CallTypePrivate uint8 = iota
CallTypeGroup
)
// Packet represents a frame transported by the Air Interface
type Packet struct {
// 0 for slot 1, 1 for slot 2
Timeslot uint8
// Starts at zero for each incoming transmission, wraps back to zero when 256 is reached
Sequence uint8
// Source and destination DMR ID
SrcID uint32
DstID uint32
// 3 bytes registered DMR-ID for public repeaters, 4 bytes for private repeaters
RepeaterID uint32
// Random or incremented number which stays the same from PTT-press to PTT-release which identifies a stream
StreamID uint32
// Data Type or Slot type
DataType uint8
// 0 for group call, 1 for unit to unit
CallType uint8
// The on-air DMR data with possible FEC fixes to the AMBE data and/or Slot Type and/or EMB, etc
Data []byte // 34 bytes
Bits []byte // 264 bits
}
// EMBBits returns the frame EMB bits from the SYNC bits
func (p *Packet) EMBBits() []byte {
var (
b = make([]byte, EMBBits)
o = EMBHalfBits + EMBSignallingLCFragmentBits
sync = p.SyncBits()
)
copy(b[:EMBHalfBits], sync[:EMBHalfBits])
copy(b[EMBHalfBits:], sync[o:o+EMBHalfBits])
return b
}
// InfoBits returns the frame Info bits
func (p *Packet) InfoBits() []byte {
var b = make([]byte, InfoBits)
copy(b[0:InfoHalfBits], p.Bits[0:InfoHalfBits])
copy(b[InfoHalfBits:], p.Bits[InfoHalfBits+SlotTypeBits+SignalBits:])
return b
}
// SyncBits returns the frame SYNC bits
func (p *Packet) SyncBits() []byte {
return p.Bits[SyncOffsetBits : SyncOffsetBits+SyncBits]
}
// SlotType returns the frame Slot Type parsed from the Slot Type bits
func (p *Packet) SlotType() []byte {
return BitsToBytes(p.SlotTypeBits())
}
// SlotTypeBits returns the SloT Type bits
func (p *Packet) SlotTypeBits() []byte {
var o = InfoHalfBits + SlotTypeHalfBits + SyncBits
return append(p.Bits[InfoHalfBits:InfoHalfBits+SlotTypeHalfBits], p.Bits[o:o+SlotTypeHalfBits]...)
}
// VoiceBits returns the bits containing voice data
func (p *Packet) VoiceBits() []byte {
var b = make([]byte, VoiceBits)
copy(b[:VoiceHalfBits], p.Bits[:VoiceHalfBits])
copy(b[VoiceHalfBits:], p.Bits[VoiceHalfBits+SignalBits:])
return b
}
func (p *Packet) SetData(data []byte) {
p.Data = data
p.Bits = BytesToBits(data)
}
// PacketFunc is a callback function that handles DMR packets
type PacketFunc func(Repeater, *Packet) error

@ -0,0 +1,8 @@
package dmr
// Repeater implements a repeater station.
type Repeater interface {
Active() bool
Close() error
ListenAndServe() error
}

@ -1 +0,0 @@
cmd/dmrstream/stream.yaml

@ -2,13 +2,11 @@ package dmr
import (
"bytes"
"github.com/tehmaze/go-dmr/bit"
)
// Table 9.2: SYNC Patterns
const (
SyncPatternBSSourcedVoice uint16 = 1 << iota
SyncPatternBSSourcedVoice uint8 = iota
SyncPatternBSSourcedData
SyncPatternMSSourcedVoice
SyncPatternMSSourcedData
@ -30,7 +28,7 @@ var (
directDataTS1 = []byte{0xf7, 0xfd, 0xd5, 0xdd, 0xfd, 0x55}
directVoiceTS2 = []byte{0x7d, 0xff, 0xd5, 0xf5, 0x5d, 0x5f}
directDataTS2 = []byte{0xd7, 0x55, 0x7f, 0x5f, 0xf7, 0xf5}
SyncPatternName = map[uint16]string{
SyncPatternName = map[uint8]string{
SyncPatternBSSourcedVoice: "bs sourced voice",
SyncPatternBSSourcedData: "bs sourced data",
SyncPatternMSSourcedVoice: "ms sourced voice",
@ -44,12 +42,8 @@ var (
}
)
func ExtractSyncBits(payload bit.Bits) bit.Bits {
return payload[108:156]
}
func SyncPattern(bits bit.Bits) uint16 {
var b = bits.Bytes()
func SyncPattern(bits []byte) uint8 {
var b = BitsToBytes(bits)
switch {
case bytes.Equal(b, bsSourcedVoice):
return SyncPatternBSSourcedVoice

@ -1,5 +0,0 @@
#!/bin/bash
#
# sox converts big endian float32 to signed pcm wave
sox --endian big -t f32 - -t wav - | lame -b 96 - -

@ -1,6 +0,0 @@
#!/bin/bash
#
# sox converts big endian float32 to signed pcm wave
sox --endian big -t f32 - -t wav - \
| oggenc --quiet --skeleton -q 1 -

File diff suppressed because it is too large Load Diff

@ -0,0 +1,12 @@
package dmr
import (
"fmt"
"runtime"
)
var (
Version = "0.2.1" // Version number
SoftwareID = fmt.Sprintf("%s go-dmr %s", Version, runtime.GOOS) // Software identifier
PackageID = fmt.Sprintf("%s/%s", SoftwareID, runtime.GOARCH) // Package identifier
)

@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/crc/quadres_16_7"
)
@ -16,20 +15,12 @@ const (
Continuation
)
func ExtractEMBBitsFromSyncBits(sync bit.Bits) bit.Bits {
var b = make(bit.Bits, EMBBits)
var o = EMBHalfBits + EMBSignallingLCFragmentBits
copy(b[:EMBHalfBits], sync[:EMBHalfBits])
copy(b[EMBHalfBits:], sync[o:o+EMBHalfBits])
return b
}
type EMB struct {
ColorCode uint8
LCSS uint8
}
func ParseEMB(bits bit.Bits) (*EMB, error) {
func ParseEMB(bits []byte) (*EMB, error) {
if len(bits) != EMBBits {
return nil, fmt.Errorf("dmr/emb: expected %d bits, got %d", EMBBits, len(bits))
}
Loading…
Cancel
Save