//+build ignore //go:generate go run gen.go // This program generates STUN parameters: methods, attributes and error codes by reading IANA registry. package main import ( "bytes" "encoding/xml" "fmt" "go/format" "io/ioutil" "net/http" "os" "regexp" "strings" "unicode" "unicode/utf8" ) func main() { err := generate("http://www.iana.org/assignments/stun-parameters/stun-parameters.xml", "registry.go") if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func loadRegistry(url string) (*Registry, error) { resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } r := &Registry{} err = xml.Unmarshal(b, r) if err != nil { return nil, err } name := regexp.MustCompile("^([\\w-]+)(.*was\\s+([\\w-]+))?") for _, reg := range r.Registry { for _, r := range reg.Records { m := name.FindStringSubmatch(r.Description) if m != nil { r.Value = strings.ToLower(r.Value) r.Name = m[1] if r.Name == "Reserved" && m[3] != "" { r.Name = m[3] r.Deprecated = true } r.Ref.Data = strings.TrimPrefix(r.Ref.Data, "rfc") r.Ref.Data = strings.TrimPrefix(r.Ref.Data, "RFC-") } } } return r, nil } type Record struct { Value string `xml:"value"` Description string `xml:"description"` Name string Deprecated bool Ref struct { Type string `xml:"type,attr"` Data string `xml:"data,attr"` } `xml:"xref"` } func (r *Record) IsValid() bool { return r.Name != "Reserved" && r.Name != "Unassigned" && (r.Ref.Type == "rfc" || r.Ref.Type == "draft") } type Registry struct { Title string `xml:"title"` Updated string `xml:"updated"` Registry []struct { Id string `xml:"id,attr"` Records []*Record `xml:"record"` } `xml:"registry"` } func (reg *Registry) GetRecords(id string) []*Record { for _, it := range reg.Registry { if it.Id == id { return it.Records } } return nil } func generate(url, file string) error { reg, err := loadRegistry(url) if err != nil { return err } b := &bytes.Buffer{} fmt.Fprintf(b, "package stun\n\n") fmt.Fprintf(b, "// Do not edit. This file is generated by 'go generate gen.go'\n") fmt.Fprintf(b, "// This file provides STUN parameters managed by the Internet Assigned Numbers Authority (IANA).\n") fmt.Fprintf(b, "// %s, Updated: %s.\n\n", reg.Title, reg.Updated) genMethods(reg.GetRecords("stun-parameters-2"), b) genAttributes(reg.GetRecords("stun-parameters-4"), b) genErrors(reg.GetRecords("stun-parameters-6"), b) src, err := format.Source(b.Bytes()) if err != nil { return err } if err = ioutil.WriteFile(file, src, 0644); err != nil { return err } return nil } func genMethods(records []*Record, b *bytes.Buffer) error { c, m := &bytes.Buffer{}, &bytes.Buffer{} ref := "" for _, it := range records { if it.IsValid() { if ref == it.Ref.Data { fmt.Fprintf(c, "Method%v uint16 = %s\n", it.Name, it.Value) } else { ref = it.Ref.Data fmt.Fprintf(c, "Method%v uint16 = %s // RFC %s\n", it.Name, it.Value, ref) } fmt.Fprintf(m, "Method%v: \"%s\",\n", it.Name, it.Name) } } fmt.Fprintf(b, "// STUN methods.\n") fmt.Fprintf(b, "const (\n%s)\n", c.Bytes()) fmt.Fprintf(b, "// STUN method names.\n") fmt.Fprintf(b, "var methodNames = map[uint16]string{\n%s}\n", m.Bytes()) return nil } func genAttributes(records []*Record, b *bytes.Buffer) error { c, d, m := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{} ref := "" for _, it := range records { if it.IsValid() { a := c if it.Deprecated { a = d } v := strings.Replace(it.Name, "_", "-", -1) n := strings.Replace(v, "-", " ", -1) parts := strings.Fields(n) for i, s := range parts { switch s { case "", "ID": default: r, n := utf8.DecodeRuneInString(s) s = string(unicode.ToUpper(r)) + strings.ToLower(s[n:]) } parts[i] = s } n = strings.Join(parts, "") if ref == it.Ref.Data || it.Deprecated { fmt.Fprintf(a, "Attr%v uint16 = %s\n", n, it.Value) } else { ref = it.Ref.Data fmt.Fprintf(a, "Attr%v uint16 = %s // RFC %s\n", n, it.Value, ref) } fmt.Fprintf(m, "Attr%v: \"%s\",\n", n, v) } } fmt.Fprintf(b, "// STUN attributes.\n") fmt.Fprintf(b, "const (\n%s)\n", c.Bytes()) fmt.Fprintf(b, "// Deprecated: For backwards compatibility only.\n") fmt.Fprintf(b, "const (\n%s)\n", d.Bytes()) fmt.Fprintf(b, "// STUN attribute names.\n") fmt.Fprintf(b, "var attrNames = map[uint16]string{\n%s}\n", m.Bytes()) return nil } func genErrors(records []*Record, b *bytes.Buffer) error { c, m := &bytes.Buffer{}, &bytes.Buffer{} ref := "" for _, it := range records { if it.IsValid() { n := strings.Replace(strings.Title(it.Description), " ", "", -1) if ref == it.Ref.Data { fmt.Fprintf(c, "Code%v int = %s\n", n, it.Value) } else { ref = it.Ref.Data fmt.Fprintf(c, "Code%v int = %s // RFC %s\n", n, it.Value, ref) } fmt.Fprintf(m, "Code%v: \"%s\",\n", n, it.Description) } } fmt.Fprintf(b, "// STUN error codes.\n") fmt.Fprintf(b, "const (\n%s)\n", c.Bytes()) fmt.Fprintf(b, "// STUN error texts.\n") fmt.Fprintf(b, "var errorText = map[int]string{\n%s}\n", m.Bytes()) return nil }