remove SIP things

This commit is contained in:
Joel Wetzell
2026-08-30 19:01:27 -05:00
parent 10ca10db0d
commit 36dfda68ce
11 changed files with 4 additions and 1584 deletions
-1
View File
@@ -25,7 +25,6 @@ Simple protocol router _/s_
- Serial (not included in pre-built binaries yet)
- [OSC](https://opensoundcontrol.stanford.edu/spec-1_0.html)
- [FreeD](https://ptzoptics.com/freed/)
- [SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol)
- [Redis](https://redis.io/)
### CLI Usage
+1 -16
View File
@@ -4,8 +4,6 @@ go 1.27.0
require (
github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/emiago/diago v0.32.0
github.com/emiago/sipgo v1.6.0
github.com/expr-lang/expr v1.17.8
github.com/extism/go-sdk v1.7.1
github.com/google/jsonschema-go v0.4.3
@@ -31,16 +29,10 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240828172851-9145d8ad07e1 // indirect
github.com/emiago/dtls/v3 v3.0.0-20260122183559-8b8d23e359c0 // indirect
github.com/go-audio/riff v1.0.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/ianlancetaylor/demangle v0.0.0-20260505044615-1ff4bf46051f // indirect
github.com/icholy/digest v1.1.0 // indirect
github.com/klauspost/compress v1.19.2 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/minio/highwayhash v1.0.4 // indirect
@@ -48,17 +40,10 @@ require (
github.com/nats-io/nkeys v0.4.16 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.16 // indirect
github.com/pion/rtp v1.10.2 // indirect
github.com/pion/srtp/v3 v3.0.10 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/zaf/g711 v1.4.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
@@ -68,7 +53,7 @@ require (
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
modernc.org/libc v1.75.4 // indirect
modernc.org/libquickjs v0.13.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
+3 -35
View File
@@ -14,26 +14,12 @@ github.com/dylibso/observe-sdk/go v0.0.0-20240828172851-9145d8ad07e1 h1:idfl8M8r
github.com/dylibso/observe-sdk/go v0.0.0-20240828172851-9145d8ad07e1/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/emiago/diago v0.32.0 h1:U6urbJc/LD2HwrHVe1d/yT0lMgyDMRUFle7gMqJe0Jg=
github.com/emiago/diago v0.32.0/go.mod h1:CP7p9/aBZG/fQEYdtGdQkCblr1jLr1zbxODcu8MryVU=
github.com/emiago/dtls/v3 v3.0.0-20260122183559-8b8d23e359c0 h1:o4LxpUnZ1zxiQ+Qjc9kLwXcjz31NGAHmnZ7xoJto3VM=
github.com/emiago/dtls/v3 v3.0.0-20260122183559-8b8d23e359c0/go.mod h1:ydcZ977eS1I6uOWodzMuw30BwvNAzT9su/xcNYSJqjA=
github.com/emiago/sipgo v1.6.0 h1:6EuOP7c6f0VRatKYTPEYNezt4hslBEsaCzZZOhT2n3s=
github.com/emiago/sipgo v1.6.0/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY=
github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM=
github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw=
github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
@@ -50,8 +36,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20260505044615-1ff4bf46051f h1:NW3E2QSchEk63/fjeEvWOa2cE02FSv9ox//VE/N4c8g=
github.com/ianlancetaylor/demangle v0.0.0-20260505044615-1ff4bf46051f/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
github.com/jwetzell/artnet-go v0.3.0 h1:MDHElOSbpgmIbYGHO9SoxsT7ynrYtV5/UHGT82geHk0=
github.com/jwetzell/artnet-go v0.3.0/go.mod h1:zqRsSsrd6rPwJacwscPteDqGIqevjZ3Z6dpp092VJ1w=
github.com/jwetzell/free-d-go v0.2.0 h1:8WQW4du8Sf3d18aUzk9n5jZtHE6WdDQHDFjkFffvycc=
@@ -64,8 +48,11 @@ github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
@@ -84,18 +71,6 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.10.2 h1:l+f6tTDcAH6xwepaAoW791ddhuYsJlqRATOzirO04Mo=
github.com/pion/rtp v1.10.2/go.mod h1:Au8fc6cEByy8RLTwKTQTEeQqDB/SJDxwL4mZuxYA5Pk=
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
@@ -112,8 +87,6 @@ github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbw
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/urfave/cli/v3 v3.11.0 h1:P/euJp99kb9p0tlVY+iYTLYYTAQlfl0hR2gUO1Img1Q=
github.com/urfave/cli/v3 v3.11.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c=
github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
gitlab.com/gomidi/midi/v2 v2.3.24 h1:afkq5nhlzKvZaj9QK80YbK8tH3lIlKLnPPP9HxxD7Do=
@@ -136,7 +109,6 @@ golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
@@ -149,12 +121,8 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 h1:xeVptzkP8BuJhoIjNizd2bRHfq9KB9HfOLZu90T04XM=
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
-269
View File
@@ -1,269 +0,0 @@
package module
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"os"
"sync"
"time"
"github.com/emiago/diago"
"github.com/emiago/diago/media"
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func init() {
RegisterModule(ModuleRegistration{
Type: "sip.call.server",
Title: "SIP Call Server",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"ip": {
Title: "IP",
Description: "the IP address to bind the SIP server to",
Type: "string",
Default: json.RawMessage(`"0.0.0.0"`),
},
"port": {
Title: "Port",
Description: "the port for the SIP server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
Default: json.RawMessage(`5060`),
},
"transport": {
Title: "Transport",
Description: "the transport protocol to use for the SIP server",
Type: "string",
Enum: []any{"udp", "tcp", "ws", "udp4", "tcp4"},
Default: json.RawMessage(`"udp"`),
},
"userAgent": {
Title: "User Agent",
Description: "the user agent string to use",
Type: "string",
Default: json.RawMessage(`"showbridge"`),
},
},
Required: []string{},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
portNum, err := params.GetInt("port")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
portNum = 5060
} else {
return nil, fmt.Errorf("sip.call.server port error: %w", err)
}
}
ipString, err := params.GetString("ip")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
ipString = "0.0.0.0"
} else {
return nil, fmt.Errorf("sip.call.server ip error: %w", err)
}
}
transportString, err := params.GetString("transport")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
transportString = "udp"
} else {
return nil, fmt.Errorf("sip.call.server transport error: %w", err)
}
}
userAgentString, err := params.GetString("userAgent")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
userAgentString = "showbridge"
} else {
return nil, fmt.Errorf("sip.call.server userAgent error: %w", err)
}
}
return &SIPCallServer{config: moduleConfig, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, logger: CreateLogger(moduleConfig)}, nil
},
})
}
type SIPCallServer struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
IP string
Port int
Transport string
UserAgent string
logger *slog.Logger
cancel context.CancelFunc
ua *sipgo.UserAgent
uaMu sync.Mutex
}
type SIPCallMessage struct {
To string
}
type SIPCall struct {
inDialog *diago.DialogServerSession
lock sync.Mutex
}
type sipCallContextKey string
func (scs *SIPCallServer) Id() string {
return scs.config.Id
}
func (scs *SIPCallServer) Type() string {
return scs.config.Type
}
func (scs *SIPCallServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
scs.logger.Debug("running")
scs.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
scs.ctx = moduleContext
scs.cancel = cancel
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
ua, _ := sipgo.NewUA(
sipgo.WithUserAgent(scs.UserAgent),
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
)
scs.uaMu.Lock()
scs.ua = ua
scs.uaMu.Unlock()
sip.SetDefaultLogger(diagoLogger)
media.SetDefaultLogger(diagoLogger)
dg := diago.NewDiago(ua, diago.WithLogger(diagoLogger), diago.WithTransport(
diago.Transport{
Transport: scs.Transport,
BindHost: scs.IP,
BindPort: scs.Port,
},
))
err := dg.Serve(scs.ctx, func(inDialog *diago.DialogServerSession) {
scs.HandleCall(inDialog)
})
if err != nil {
scs.logger.Error("diago serve error", "error", err)
}
<-scs.ctx.Done()
scs.logger.Debug("done")
return nil
}
func (scs *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) {
inDialog.Trying()
inDialog.Ringing()
inDialog.Answer()
dialogContext := context.WithValue(scs.ctx, sipCallContextKey("call"), &SIPCall{
inDialog: inDialog,
})
if scs.inputHandler != nil {
scs.inputHandler(dialogContext, scs.Id(), SIPCallMessage{
To: inDialog.ToUser(),
})
}
}
func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
call, ok := ctx.Value(sipCallContextKey("call")).(*SIPCall)
if !ok {
return errors.New("sip.call.server output must originate from sip.call.server input")
}
gotLock := call.lock.TryLock()
if !gotLock {
return errors.New("sip.call.server call is already locked")
}
if call.inDialog.LoadState() == sip.DialogStateEnded {
return errors.New("sip.call.server inDialog already ended")
}
payloadDTMFResponse, ok := common.GetAnyAs[processor.SipDTMFResponse](payload)
if ok {
dtmfWriter, err := call.inDialog.AudioWriterDTMF()
if err != nil {
return err
}
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait))
for i, dtmfRune := range payloadDTMFResponse.Digits {
err := dtmfWriter.WriteDTMF(dtmfRune)
if err != nil {
return fmt.Errorf("sip.dtmf.server error output dtmf digit at index %d", i)
}
}
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PostWait))
return nil
}
payloadAudioFileResponse, ok := common.GetAnyAs[processor.SipAudioFileResponse](payload)
if ok {
audioFile, err := os.Open(payloadAudioFileResponse.AudioFile)
if err != nil {
return err
}
defer audioFile.Close()
playback, err := call.inDialog.PlaybackCreate()
if err != nil {
return err
}
time.Sleep(time.Millisecond * time.Duration(payloadAudioFileResponse.PreWait))
_, err = playback.Play(audioFile, "audio/wav")
time.Sleep(time.Millisecond * time.Duration(payloadAudioFileResponse.PostWait))
if err != nil {
return err
}
return nil
}
return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse")
}
func (scs *SIPCallServer) Stop() {
if scs.cancel != nil {
defer scs.cancel()
}
scs.uaMu.Lock()
defer scs.uaMu.Unlock()
if scs.ua != nil {
scs.ua.Close()
}
}
-308
View File
@@ -1,308 +0,0 @@
package module
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"os"
"strings"
"sync"
"time"
"github.com/emiago/diago"
"github.com/emiago/diago/media"
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func init() {
RegisterModule(ModuleRegistration{
Type: "sip.dtmf.server",
Title: "SIP DTMF Server",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"ip": {
Title: "IP",
Description: "the IP address to bind the SIP server to",
Type: "string",
Default: json.RawMessage(`"0.0.0.0"`),
},
"port": {
Title: "Port",
Description: "the port for the SIP server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
Default: json.RawMessage(`5060`),
},
"transport": {
Title: "Transport",
Description: "the transport protocol to use for the SIP server",
Type: "string",
Enum: []any{"udp", "tcp", "ws", "udp4", "tcp4"},
Default: json.RawMessage(`"udp"`),
},
"userAgent": {
Title: "User Agent",
Description: "the user agent string to use",
Type: "string",
Default: json.RawMessage(`"showbridge"`),
},
"separator": {
Title: "DTMF Separator",
Description: "the DTMF character to use as a separator between DTMF digit groups",
Type: "string",
MinLength: new(1),
MaxLength: new(1),
},
},
Required: []string{"separator"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
portNum, err := params.GetInt("port")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
portNum = 5060
} else {
return nil, fmt.Errorf("sip.dtmf.server port error: %w", err)
}
}
ipString, err := params.GetString("ip")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
ipString = "0.0.0.0"
} else {
return nil, fmt.Errorf("sip.dtmf.server ip error: %w", err)
}
}
transportString, err := params.GetString("transport")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
transportString = "udp"
} else {
return nil, fmt.Errorf("sip.dtmf.server transport error: %w", err)
}
}
userAgentString, err := params.GetString("userAgent")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
userAgentString = "showbridge"
} else {
return nil, fmt.Errorf("sip.dtmf.server userAgent error: %w", err)
}
}
separatorString, err := params.GetString("separator")
if err != nil {
return nil, fmt.Errorf("sip.dtmf.server separator error: %w", err)
}
if len(separatorString) != 1 {
return nil, errors.New("sip.dtmf.server separator must be a single character")
}
if !strings.ContainsRune("0123456789*#ABCD", rune(separatorString[0])) {
return nil, errors.New("sip.dtmf.server separator must be a valid DTMF character")
}
return &SIPDTMFServer{config: moduleConfig, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, Separator: separatorString, logger: CreateLogger(moduleConfig)}, nil
},
})
}
type SIPDTMFServer struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
IP string
Port int
Transport string
UserAgent string
Separator string
logger *slog.Logger
cancel context.CancelFunc
ua *sipgo.UserAgent
uaMu sync.Mutex
}
type SIPDTMFMessage struct {
To string
Digits string
}
type SIPDTMFCall struct {
inDialog *diago.DialogServerSession
lock sync.Mutex
}
func (sds *SIPDTMFServer) Id() string {
return sds.config.Id
}
func (sds *SIPDTMFServer) Type() string {
return sds.config.Type
}
func (sds *SIPDTMFServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
sds.logger.Debug("running")
sds.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
sds.ctx = moduleContext
sds.cancel = cancel
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
ua, _ := sipgo.NewUA(
sipgo.WithUserAgent(sds.UserAgent),
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
)
sds.uaMu.Lock()
sds.ua = ua
sds.uaMu.Unlock()
sip.SetDefaultLogger(diagoLogger)
media.SetDefaultLogger(diagoLogger)
dg := diago.NewDiago(ua, diago.WithLogger(diagoLogger), diago.WithTransport(
diago.Transport{
Transport: sds.Transport,
BindHost: sds.IP,
BindPort: sds.Port,
},
))
err := dg.Serve(sds.ctx, func(inDialog *diago.DialogServerSession) {
sds.HandleCall(inDialog)
})
if err != nil {
return err
}
<-sds.ctx.Done()
sds.logger.Debug("done")
return nil
}
func (sds *SIPDTMFServer) HandleCall(inDialog *diago.DialogServerSession) error {
inDialog.Trying()
inDialog.Ringing()
inDialog.Answer()
reader, err := inDialog.AudioReaderDTMF()
if err != nil {
return err
}
userString := ""
return reader.Listen(func(dtmf rune) error {
if dtmf == rune(sds.Separator[0]) {
if sds.inputHandler != nil {
dialogContext := context.WithValue(sds.ctx, sipCallContextKey("call"), &SIPDTMFCall{
inDialog: inDialog,
})
sds.inputHandler(dialogContext, sds.Id(), SIPDTMFMessage{
To: inDialog.ToUser(),
Digits: userString,
})
}
userString = ""
} else {
userString += string(dtmf)
}
return nil
}, 5*time.Second)
}
func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
call, ok := ctx.Value(sipCallContextKey("call")).(*SIPDTMFCall)
if !ok {
return errors.New("sip.dtmf.server output must originate from sip.dtmf.server input")
}
gotLock := call.lock.TryLock()
if !gotLock {
return errors.New("sip.dtmf.server call is already locked")
}
if call.inDialog.LoadState() == sip.DialogStateEnded {
return errors.New("sip.dtmf.server inDialog already ended")
}
payloadDTMFResponse, ok := common.GetAnyAs[processor.SipDTMFResponse](payload)
if ok {
dtmfWriter, err := call.inDialog.AudioWriterDTMF()
if err != nil {
return err
}
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait))
for i, dtmfRune := range payloadDTMFResponse.Digits {
err := dtmfWriter.WriteDTMF(dtmfRune)
if err != nil {
return fmt.Errorf("sip.dtmf.server error output dtmf digit at index %d", i)
}
}
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PostWait))
return nil
}
payloadAudioFileResponse, ok := common.GetAnyAs[processor.SipAudioFileResponse](payload)
if ok {
audioFile, err := os.Open(payloadAudioFileResponse.AudioFile)
if err != nil {
return err
}
defer audioFile.Close()
playback, err := call.inDialog.PlaybackCreate()
if err != nil {
return err
}
time.Sleep(time.Millisecond * time.Duration(payloadAudioFileResponse.PreWait))
_, err = playback.Play(audioFile, "audio/wav")
time.Sleep(time.Millisecond * time.Duration(payloadAudioFileResponse.PostWait))
if err != nil {
return err
}
return nil
}
return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse")
}
func (sds *SIPDTMFServer) Stop() {
if sds.cancel != nil {
defer sds.cancel()
}
sds.uaMu.Lock()
defer sds.uaMu.Unlock()
if sds.ua != nil {
sds.ua.Close()
}
}
@@ -1,102 +0,0 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestSIPCallServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.call.server")
if !ok {
t.Fatalf("sip.call.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.call.server",
})
if err != nil {
t.Fatalf("failed to create sip.call.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("sip.call.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "sip.call.server" {
t.Fatalf("sip.call.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadSIPCallServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "non-number port param",
params: map[string]any{
"port": "8000",
},
errorString: "sip.call.server port error: not a number",
},
{
name: "non-string ip param",
params: map[string]any{
"ip": 123,
},
errorString: "sip.call.server ip error: not a string",
},
{
name: "non-string transport param",
params: map[string]any{
"transport": 123,
},
errorString: "sip.call.server transport error: not a string",
},
{
name: "non-string userAgent param",
params: map[string]any{
"userAgent": 123,
},
errorString: "sip.call.server userAgent error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.call.server")
if !ok {
t.Fatalf("sip.call.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.call.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("sip.call.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("sip.call.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("sip.call.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
@@ -1,121 +0,0 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestSIPDTMFServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.dtmf.server")
if !ok {
t.Fatalf("sip.dtmf.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.dtmf.server",
Params: map[string]any{
"separator": "#",
},
})
if err != nil {
t.Fatalf("failed to create sip.dtmf.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("sip.dtmf.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "sip.dtmf.server" {
t.Fatalf("sip.dtmf.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadSIPDTMFServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no separator param",
params: map[string]any{},
errorString: "sip.dtmf.server separator error: not found",
},
{
name: "non-string separator param",
params: map[string]any{
"separator": 123,
},
errorString: "sip.dtmf.server separator error: not a string",
},
{
name: "non-number port param",
params: map[string]any{
"separator": "#",
"port": "8000",
},
errorString: "sip.dtmf.server port error: not a number",
},
{
name: "non-string ip param",
params: map[string]any{
"separator": "#",
"ip": 123,
},
errorString: "sip.dtmf.server ip error: not a string",
},
{
name: "non-string transport param",
params: map[string]any{
"separator": "#",
"transport": 123,
},
errorString: "sip.dtmf.server transport error: not a string",
},
{
name: "non-string userAgent param",
params: map[string]any{
"separator": "#",
"userAgent": 123,
},
errorString: "sip.dtmf.server userAgent error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.dtmf.server")
if !ok {
t.Fatalf("sip.dtmf.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.dtmf.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("sip.dtmf.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("sip.dtmf.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("sip.dtmf.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
@@ -1,109 +0,0 @@
package processor
import (
"bytes"
"context"
"fmt"
"text/template"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "sip.response.audio.create",
Title: "Create SIP Audio Response",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"preWait": {
Title: "Pre Wait (ms)",
Description: "number of milliseconds to wait before playing the audio",
Type: "integer",
},
"audioFile": {
Title: "Audio File",
Description: "path to the audio file to play",
Type: "string",
},
"postWait": {
Title: "Post Wait (ms)",
Description: "number of milliseconds to wait after playing the audio",
Type: "integer",
},
},
Required: []string{"preWait", "postWait", "audioFile"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
preWaitNum, err := params.GetInt("preWait")
if err != nil {
return nil, fmt.Errorf("sip.response.audio.create preWait error: %w", err)
}
postWaitNum, err := params.GetInt("postWait")
if err != nil {
return nil, fmt.Errorf("sip.response.audio.create postWait error: %w", err)
}
audioFileString, err := params.GetString("audioFile")
if err != nil {
return nil, fmt.Errorf("sip.response.audio.create audioFile error: %w", err)
}
audioFileTemplate, err := template.New("audioFile").Parse(audioFileString)
if err != nil {
return nil, err
}
return &SipResponseAudioCreate{config: config, AudioFile: audioFileTemplate, PreWait: int(preWaitNum), PostWait: int(postWaitNum)}, nil
},
})
}
type SipResponseAudioCreate struct {
config config.ProcessorConfig
PreWait int
PostWait int
AudioFile *template.Template
}
type SipAudioFileResponse struct {
PreWait int
PostWait int
AudioFile string
}
func (srac *SipResponseAudioCreate) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
templateData := wrappedPayload
var audioFileBuffer bytes.Buffer
err := srac.AudioFile.Execute(&audioFileBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
audioFileString := audioFileBuffer.String()
wrappedPayload.Payload = SipAudioFileResponse{
PreWait: srac.PreWait,
PostWait: srac.PostWait,
AudioFile: audioFileString,
}
return wrappedPayload, nil
}
func (srac *SipResponseAudioCreate) Id() string {
return srac.config.Id
}
func (srac *SipResponseAudioCreate) Type() string {
return srac.config.Type
}
@@ -1,119 +0,0 @@
package processor
import (
"bytes"
"context"
"errors"
"fmt"
"regexp"
"text/template"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "sip.response.dtmf.create",
Title: "Create SIP DTMF Response",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"preWait": {
Title: "Pre Wait (ms)",
Description: "number of milliseconds to wait before sending the DTMF tones",
Type: "integer",
},
"digits": {
Title: "Digits",
Description: "DTMF digits to send",
Type: "string",
},
"postWait": {
Title: "Post Wait (ms)",
Description: "number of milliseconds to wait after sending the DTMF tones",
Type: "integer",
},
},
Required: []string{"preWait", "postWait", "digits"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
preWaitNum, err := params.GetInt("preWait")
if err != nil {
return nil, fmt.Errorf("sip.response.dtmf.create preWait error: %w", err)
}
postWaitNum, err := params.GetInt("postWait")
if err != nil {
return nil, fmt.Errorf("sip.response.dtmf.create postWait error: %w", err)
}
digitsString, err := params.GetString("digits")
if err != nil {
return nil, fmt.Errorf("sip.response.dtmf.create digits error: %w", err)
}
digitsTemplate, err := template.New("digits").Parse(digitsString)
if err != nil {
return nil, err
}
return &SipResponseDTMFCreate{config: config, Digits: digitsTemplate, PreWait: int(preWaitNum), PostWait: int(postWaitNum), validDTMF: validDTMFRegex}, nil
},
})
}
type SipResponseDTMFCreate struct {
config config.ProcessorConfig
PreWait int
PostWait int
Digits *template.Template
validDTMF *regexp.Regexp
}
type SipDTMFResponse struct {
PreWait int
PostWait int
Digits string
}
var validDTMFRegex = regexp.MustCompile(`^[0-9*#A-Da-d]+$`)
func (srdc *SipResponseDTMFCreate) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
templateData := wrappedPayload
var digitsBuffer bytes.Buffer
err := srdc.Digits.Execute(&digitsBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
digitsString := digitsBuffer.String()
if !srdc.validDTMF.MatchString(digitsString) {
wrappedPayload.End = true
return wrappedPayload, errors.New("sip.response.dtmf.create result of digits template contains invalid characters")
}
wrappedPayload.Payload = SipDTMFResponse{
PreWait: srdc.PreWait,
PostWait: srdc.PostWait,
Digits: digitsString,
}
return wrappedPayload, nil
}
func (srdc *SipResponseDTMFCreate) Id() string {
return srdc.config.Id
}
func (srdc *SipResponseDTMFCreate) Type() string {
return srdc.config.Type
}
@@ -1,248 +0,0 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestSipResponseAudioCreateFromRegistry(t *testing.T) {
registration, ok := processor.GetProcessorRegistration("sip.response.audio.create")
if !ok {
t.Fatalf("sip.response.audio.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Id: "test-id",
Type: "sip.response.audio.create",
Params: map[string]any{
"preWait": 0,
"audioFile": "good.wav",
"postWait": 0,
},
})
if err != nil {
t.Fatalf("failed to filter sip.response.audio.create processor: %s", err)
}
if processorInstance.Id() != "test-id" {
t.Fatalf("sip.response.audio.create processor has wrong id: %s", processorInstance.Id())
}
if processorInstance.Type() != "sip.response.audio.create" {
t.Fatalf("sip.response.audio.create processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodSipResponseAudioCreate(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
expected any
}{
{
name: "basic",
params: map[string]any{
"preWait": 0,
"audioFile": "good.wav",
"postWait": 0,
},
payload: nil,
expected: processor.SipAudioFileResponse{
PreWait: 0,
PostWait: 0,
AudioFile: "good.wav",
},
},
{
name: "template audio file",
params: map[string]any{
"preWait": 1,
"audioFile": "{{.Payload.SomeField}}.wav",
"postWait": 2,
},
payload: map[string]any{
"SomeField": "templated",
},
expected: processor.SipAudioFileResponse{
PreWait: 1,
PostWait: 2,
AudioFile: "templated.wav",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.GetProcessorRegistration("sip.response.audio.create")
if !ok {
t.Fatalf("sip.response.audio.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "sip.response.audio.create",
Params: test.params,
})
if err != nil {
t.Fatalf("sip.response.audio.create failed to create processor: %s", err)
}
got, err := processorInstance.Process(t.Context(), common.WrappedPayload{Payload: test.payload})
if err != nil {
t.Fatalf("sip.response.audio.create processing failed: %s", err)
}
if !reflect.DeepEqual(got.Payload, test.expected) {
t.Fatalf("sip.response.audio.create got %+v (%T), expected %+v (%T)", got.Payload, got.Payload, test.expected, test.expected)
}
})
}
}
func TestBadSipResponseAudioCreate(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
errorString string
}{
{
name: "missing preWait param",
params: map[string]any{
"audioFile": "good.wav",
"postWait": 0,
},
errorString: "sip.response.audio.create preWait error: not found",
},
{
name: "non-numeric preWait param",
params: map[string]any{
"preWait": "not a number",
"audioFile": "good.wav",
"postWait": 0,
},
errorString: "sip.response.audio.create preWait error: not a number",
},
{
name: "missing audioFile param",
params: map[string]any{
"preWait": 0,
"postWait": 0,
},
errorString: "sip.response.audio.create audioFile error: not found",
},
{
name: "non-string audioFile param",
params: map[string]any{
"preWait": 0,
"audioFile": 123,
"postWait": 0,
},
errorString: "sip.response.audio.create audioFile error: not a string",
},
{
name: "audioFile template syntax error",
params: map[string]any{
"preWait": 0,
"audioFile": "{{.Unclosed",
"postWait": 0,
},
errorString: "template: audioFile:1: unclosed action",
},
{
name: "audioFile template error",
params: map[string]any{
"preWait": 0,
"audioFile": "{{.NonExistentField}} ",
"postWait": 0,
},
errorString: "template: audioFile:1:2: executing \"audioFile\" at <.NonExistentField>: can't evaluate field NonExistentField in type common.WrappedPayload",
},
{
name: "missing postWait param",
params: map[string]any{
"preWait": 0,
"audioFile": "good.wav",
},
errorString: "sip.response.audio.create postWait error: not found",
},
{
name: "non-numeric postWait param",
params: map[string]any{
"preWait": 0,
"audioFile": "good.wav",
"postWait": "not a number",
},
errorString: "sip.response.audio.create postWait error: not a number",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.GetProcessorRegistration("sip.response.audio.create")
if !ok {
t.Fatalf("sip.response.audio.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "sip.response.audio.create",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("sip.response.audio.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), common.WrappedPayload{Payload: test.payload})
if err == nil {
t.Fatalf("sip.response.audio.create expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("sip.response.audio.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
func BenchmarkSipResponseAudioCreate(b *testing.B) {
registration, ok := processor.GetProcessorRegistration("sip.response.audio.create")
if !ok {
b.Fatalf("sip.response.audio.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "sip.response.audio.create",
Params: map[string]any{
"preWait": 0,
"audioFile": "good.wav",
"postWait": 0,
},
})
if err != nil {
b.Fatalf("sip.response.audio.create failed to create processor: %s", err)
}
count := 0
for b.Loop() {
_, err := processorInstance.Process(b.Context(), common.WrappedPayload{Payload: count})
if err != nil {
b.Fatalf("sip.response.audio.create processing failed: %s", err)
}
count++
}
}
@@ -1,256 +0,0 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestSipResponseDTMFCreateFromRegistry(t *testing.T) {
registration, ok := processor.GetProcessorRegistration("sip.response.dtmf.create")
if !ok {
t.Fatalf("sip.response.dtmf.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Id: "test-id",
Type: "sip.response.dtmf.create",
Params: map[string]any{
"preWait": 0,
"digits": "good.wav",
"postWait": 0,
},
})
if err != nil {
t.Fatalf("failed to filter sip.response.dtmf.create processor: %s", err)
}
if processorInstance.Id() != "test-id" {
t.Fatalf("sip.response.dtmf.create processor has wrong id: %s", processorInstance.Id())
}
if processorInstance.Type() != "sip.response.dtmf.create" {
t.Fatalf("sip.response.dtmf.create processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodSipResponseDTMFCreate(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
expected any
}{
{
name: "basic",
params: map[string]any{
"preWait": 0,
"digits": "12345",
"postWait": 0,
},
payload: nil,
expected: processor.SipDTMFResponse{
PreWait: 0,
PostWait: 0,
Digits: "12345",
},
},
{
name: "template digits",
params: map[string]any{
"preWait": 0,
"digits": "{{.Payload}}",
"postWait": 0,
},
payload: "67890",
expected: processor.SipDTMFResponse{
PreWait: 0,
PostWait: 0,
Digits: "67890",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.GetProcessorRegistration("sip.response.dtmf.create")
if !ok {
t.Fatalf("sip.response.dtmf.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "sip.response.dtmf.create",
Params: test.params,
})
if err != nil {
t.Fatalf("sip.response.dtmf.create failed to create processor: %s", err)
}
got, err := processorInstance.Process(t.Context(), common.WrappedPayload{Payload: test.payload})
if err != nil {
t.Fatalf("sip.response.dtmf.create processing failed: %s", err)
}
if !reflect.DeepEqual(got.Payload, test.expected) {
t.Fatalf("sip.response.dtmf.create got %+v (%T), expected %+v (%T)", got.Payload, got.Payload, test.expected, test.expected)
}
})
}
}
func TestBadSipResponseDTMFCreate(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
errorString string
}{
{
name: "missing preWait param",
params: map[string]any{
"digits": "good.wav",
"postWait": 0,
},
errorString: "sip.response.dtmf.create preWait error: not found",
},
{
name: "non-numeric preWait param",
params: map[string]any{
"preWait": "not a number",
"digits": "good.wav",
"postWait": 0,
},
errorString: "sip.response.dtmf.create preWait error: not a number",
},
{
name: "missing digits param",
params: map[string]any{
"preWait": 0,
"postWait": 0,
},
errorString: "sip.response.dtmf.create digits error: not found",
},
{
name: "non-string digits param",
params: map[string]any{
"preWait": 0,
"digits": 12345,
"postWait": 0,
},
errorString: "sip.response.dtmf.create digits error: not a string",
},
{
name: "digits template syntax error",
params: map[string]any{
"preWait": 0,
"digits": "{{.Unclosed",
"postWait": 0,
},
errorString: "template: digits:1: unclosed action",
},
{
name: "digits template error",
params: map[string]any{
"preWait": 0,
"digits": "{{.NonExistentField}} ",
"postWait": 0,
},
errorString: "template: digits:1:2: executing \"digits\" at <.NonExistentField>: can't evaluate field NonExistentField in type common.WrappedPayload",
},
{
name: "invalid digits template result",
payload: "nhf",
params: map[string]any{
"preWait": 0,
"digits": "{{.Payload}}",
"postWait": 0,
},
errorString: "sip.response.dtmf.create result of digits template contains invalid characters",
},
{
name: "missing postWait param",
params: map[string]any{
"preWait": 0,
"digits": "good.wav",
},
errorString: "sip.response.dtmf.create postWait error: not found",
},
{
name: "non-numeric postWait param",
params: map[string]any{
"preWait": 0,
"digits": "good.wav",
"postWait": "not a number",
},
errorString: "sip.response.dtmf.create postWait error: not a number",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.GetProcessorRegistration("sip.response.dtmf.create")
if !ok {
t.Fatalf("sip.response.dtmf.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "sip.response.dtmf.create",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("sip.response.dtmf.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), common.WrappedPayload{Payload: test.payload})
if err == nil {
t.Fatalf("sip.response.dtmf.create expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("sip.response.dtmf.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
func BenchmarkSipResponseDTMFCreate(b *testing.B) {
registration, ok := processor.GetProcessorRegistration("sip.response.dtmf.create")
if !ok {
b.Fatalf("sip.response.dtmf.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "sip.response.dtmf.create",
Params: map[string]any{
"preWait": 0,
"digits": "{{.Payload}}",
"postWait": 0,
},
})
if err != nil {
b.Fatalf("sip.response.dtmf.create failed to create processor: %s", err)
}
count := 0
for b.Loop() {
_, err := processorInstance.Process(b.Context(), common.WrappedPayload{Payload: count})
if err != nil {
b.Fatalf("sip.response.dtmf.create processing failed: %s", err)
}
count++
}
}