74 lines
2.6 KiB
Go
74 lines
2.6 KiB
Go
package asterisk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.ipao.vip/rogee/go-sip/internal/configread"
|
|
"git.ipao.vip/rogee/go-sip/internal/contract"
|
|
)
|
|
|
|
var trunkName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$`)
|
|
|
|
type trunk struct {
|
|
ID string `json:"trunk_id"`
|
|
Host string `json:"server_host"`
|
|
Port int `json:"server_port"`
|
|
Transport *string `json:"transport"`
|
|
AuthMode *string `json:"auth_mode"`
|
|
RegistrationRequired *bool `json:"registration_required"`
|
|
Codec string `json:"codec"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
// Render produces only PJSIP endpoint/AOR objects. Transport is owned by the
|
|
// static, reviewed Asterisk base configuration and is never hot-reloaded.
|
|
func Render(sip configread.SIP) (string, error) {
|
|
raw, err := json.Marshal(sip)
|
|
if err != nil {
|
|
return "", fmt.Errorf("encode approved SIP snapshot: %w", err)
|
|
}
|
|
if err := contract.ValidateCurrent("config-read", raw); err != nil {
|
|
return "", fmt.Errorf("validate approved SIP snapshot: %w", err)
|
|
}
|
|
var trunks []trunk
|
|
if err := json.Unmarshal(sip.Trunks, &trunks); err != nil {
|
|
return "", fmt.Errorf("decode approved SIP trunks: %w", err)
|
|
}
|
|
seen := make(map[string]bool, len(trunks))
|
|
for _, t := range trunks {
|
|
if !trunkName.MatchString(t.ID) || seen[t.ID] {
|
|
return "", fmt.Errorf("invalid or duplicated SIP trunk ID %q", t.ID)
|
|
}
|
|
seen[t.ID] = true
|
|
if !t.Enabled {
|
|
continue
|
|
}
|
|
if t.Transport == nil || *t.Transport != "udp" || t.AuthMode == nil || (*t.AuthMode != "none" && *t.AuthMode != "ip") || t.RegistrationRequired == nil || *t.RegistrationRequired {
|
|
return "", fmt.Errorf("trunk %q uses unsupported transport, authentication or registration", t.ID)
|
|
}
|
|
addr, err := netip.ParseAddr(t.Host)
|
|
if err != nil || !addr.Is4() || t.Port < 1 || t.Port > 65535 || t.Codec != "PCMA" {
|
|
return "", fmt.Errorf("trunk %q has unsupported endpoint address or codec", t.ID)
|
|
}
|
|
}
|
|
sort.Slice(trunks, func(i, j int) bool { return trunks[i].ID < trunks[j].ID })
|
|
var text strings.Builder
|
|
text.WriteString("; Generated from the approved SIP snapshot. Do not edit manually.\n")
|
|
for _, t := range trunks {
|
|
if !t.Enabled {
|
|
continue
|
|
}
|
|
_, _ = fmt.Fprintf(&text, "\n[%s]\ntype=endpoint\ntransport=go-sip-udp\ncontext=go-sip-no-inbound\ndisallow=all\nallow=alaw\naors=%s-aor\ndirect_media=no\n\n[%s-aor]\ntype=aor\ncontact=sip:%s:%d\n", t.ID, t.ID, t.ID, t.Host, t.Port)
|
|
}
|
|
if text.Len() == 0 {
|
|
return "", errors.New("empty generated SIP configuration")
|
|
}
|
|
return text.String(), nil
|
|
}
|