diff --git a/README.md b/README.md index 40411f6..ccde867 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/go.mod b/go.mod index 1b99b36..b166e98 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index c9d19b5..21c7255 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/module/sip-call-server.go b/internal/module/sip-call-server.go deleted file mode 100644 index c79b5ea..0000000 --- a/internal/module/sip-call-server.go +++ /dev/null @@ -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() - } -} diff --git a/internal/module/sip-dtmf-server.go b/internal/module/sip-dtmf-server.go deleted file mode 100644 index c3557f3..0000000 --- a/internal/module/sip-dtmf-server.go +++ /dev/null @@ -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() - } -} diff --git a/internal/module/test/sip-call-server_test.go b/internal/module/test/sip-call-server_test.go deleted file mode 100644 index 4c94c47..0000000 --- a/internal/module/test/sip-call-server_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/internal/module/test/sip-dtmf-server_test.go b/internal/module/test/sip-dtmf-server_test.go deleted file mode 100644 index fcbdcb9..0000000 --- a/internal/module/test/sip-dtmf-server_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/internal/processor/sip-response-audio-create.go b/internal/processor/sip-response-audio-create.go deleted file mode 100644 index 390a567..0000000 --- a/internal/processor/sip-response-audio-create.go +++ /dev/null @@ -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 -} diff --git a/internal/processor/sip-response-dtmf-create.go b/internal/processor/sip-response-dtmf-create.go deleted file mode 100644 index af0dda9..0000000 --- a/internal/processor/sip-response-dtmf-create.go +++ /dev/null @@ -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 -} diff --git a/internal/processor/test/sip-response-audio-create_test.go b/internal/processor/test/sip-response-audio-create_test.go deleted file mode 100644 index adafb82..0000000 --- a/internal/processor/test/sip-response-audio-create_test.go +++ /dev/null @@ -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++ - } -} diff --git a/internal/processor/test/sip-response-dtmf-create_test.go b/internal/processor/test/sip-response-dtmf-create_test.go deleted file mode 100644 index 971fade..0000000 --- a/internal/processor/test/sip-response-dtmf-create_test.go +++ /dev/null @@ -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++ - } -}