Improve binapi generator
[govpp.git] / cmd / govpp / main.go
diff --git a/cmd/govpp/main.go b/cmd/govpp/main.go
new file mode 100644 (file)
index 0000000..f1ad5d8
--- /dev/null
@@ -0,0 +1,265 @@
+//  Copyright (c) 2020 Cisco and/or its affiliates.
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at:
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+package main
+
+import (
+       "bytes"
+       "context"
+       "encoding/json"
+       "flag"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "log"
+       "net/http"
+       "os"
+       "strings"
+       "text/tabwriter"
+
+       "git.fd.io/govpp.git"
+       "git.fd.io/govpp.git/adapter/socketclient"
+       "git.fd.io/govpp.git/binapi/vpe"
+       "git.fd.io/govpp.git/binapigen"
+       "git.fd.io/govpp.git/binapigen/vppapi"
+)
+
+func main() {
+       flag.Parse()
+
+       apifiles, err := vppapi.Parse()
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       switch cmd := flag.Arg(0); cmd {
+       case "server":
+               runServer(apifiles, ":7777")
+       case "vppapi":
+               showVPPAPI(os.Stdout, apifiles)
+       case "vppapijson":
+               if flag.NArg() == 1 {
+                       writeAsJSON(os.Stdout, apifiles)
+               } else {
+                       f := flag.Arg(1)
+                       var found bool
+                       for _, apifile := range apifiles {
+                               if apifile.Name == f {
+                                       writeAsJSON(os.Stdout, apifile)
+                                       found = true
+                                       break
+                               }
+                       }
+                       if !found {
+                               log.Fatalf("VPP API file %q not found", f)
+                       }
+               }
+       case "rpc":
+               showRPC(apifiles)
+       case "cli":
+               args := flag.Args()
+               if len(args) == 0 {
+                       args = []string{"?"}
+               }
+               sendCLI(args[1:])
+       default:
+               log.Fatalf("invalid command: %q", cmd)
+       }
+
+}
+
+func writeAsJSON(w io.Writer, data interface{}) {
+       b, err := json.MarshalIndent(data, "", "  ")
+       if err != nil {
+               log.Fatal(err)
+               return
+       }
+       if _, err := w.Write(b); err != nil {
+               panic(err)
+       }
+}
+
+func showRPC(apifiles []*vppapi.File) {
+       for _, apifile := range apifiles {
+               fmt.Printf("%s.api\n", apifile.Name)
+               if apifile.Service == nil {
+                       continue
+               }
+               for _, rpc := range apifile.Service.RPCs {
+                       req := rpc.Request
+                       reply := rpc.Reply
+                       if rpc.Stream {
+                               reply = "stream " + reply
+                       }
+                       fmt.Printf(" rpc (%s) --> (%s)\n", req, reply)
+               }
+       }
+}
+
+func showVPPAPI(out io.Writer, apifiles []*vppapi.File) {
+       binapigen.SortFilesByImports(apifiles)
+
+       var buf bytes.Buffer
+       w := tabwriter.NewWriter(&buf, 0, 0, 3, ' ', 0)
+       fmt.Fprintf(w, "API\tOPTIONS\tCRC\tPATH\tIMPORTED\tTYPES\t\n")
+
+       for _, apifile := range apifiles {
+               importedTypes := binapigen.ListImportedTypes(apifiles, apifile)
+               var options []string
+               for k, v := range apifile.Options {
+                       options = append(options, fmt.Sprintf("%s=%v", k, v))
+               }
+               imports := fmt.Sprintf("%d apis, %2d types", len(apifile.Imports), len(importedTypes))
+               path := strings.TrimPrefix(apifile.Path, vppapi.DefaultDir+"/")
+               types := fmt.Sprintf("%2d enum, %2d alias, %2d struct, %2d union, %2d msg",
+                       len(apifile.EnumTypes), len(apifile.AliasTypes), len(apifile.StructTypes), len(apifile.UnionTypes), len(apifile.Messages))
+               fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%v\t%s\t\n",
+                       apifile.Name, strings.Join(options, " "), apifile.CRC, path, imports, types)
+       }
+
+       if err := w.Flush(); err != nil {
+               log.Fatal(err)
+       }
+       fmt.Fprint(out, buf.String())
+}
+
+func sendCLI(args []string) {
+       cmd := strings.Join(args, " ")
+       fmt.Printf("# %s\n", cmd)
+
+       conn, err := govpp.Connect("/run/vpp/api.sock")
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer conn.Disconnect()
+
+       ch, err := conn.NewAPIChannel()
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer ch.Close()
+
+       if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
+               log.Fatal(err)
+       }
+
+       client := vpe.NewServiceClient(conn)
+       reply, err := client.CliInband(context.Background(), &vpe.CliInband{
+               Cmd: cmd,
+       })
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       fmt.Print(reply.Reply)
+}
+
+func runServer(apifiles []*vppapi.File, addr string) {
+       apiRoutes(apifiles, http.DefaultServeMux)
+
+       conn, err := govpp.Connect(socketclient.DefaultSocketName)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       vpeRPC := vpe.NewServiceClient(conn)
+       c := vpe.RESTHandler(vpeRPC)
+
+       http.Handle("/", c)
+
+       log.Printf("listening on %v", addr)
+
+       if err := http.ListenAndServe(addr, nil); err != nil {
+               log.Fatal(err)
+       }
+}
+
+func apiRoutes(apifiles []*vppapi.File, mux *http.ServeMux) {
+       for _, apifile := range apifiles {
+               name := apifile.Name
+               mux.HandleFunc("/vppapi/"+name, apiFileHandler(apifile))
+               mux.HandleFunc("/raw/"+name, rawHandler(apifile))
+               mux.HandleFunc("/rpc/"+name, rpcHandler(apifile))
+       }
+       mux.HandleFunc("/vppapi", apiHandler(apifiles))
+}
+
+func rpcHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
+       return func(w http.ResponseWriter, req *http.Request) {
+               msgName := strings.TrimPrefix(req.URL.Path, "/rpc/"+apifile.Name+"/")
+               if msgName == "" {
+                       http.Error(w, "no message name", 500)
+                       return
+               }
+
+               input, err := ioutil.ReadAll(req.Body)
+               if err != nil {
+                       http.Error(w, err.Error(), 500)
+                       return
+               }
+
+               msgReq := make(map[string]interface{})
+               err = json.Unmarshal(input, &msgReq)
+               if err != nil {
+                       http.Error(w, err.Error(), 500)
+                       return
+               }
+
+               var msg *vppapi.Message
+               for _, m := range apifile.Messages {
+                       if m.Name == msgName {
+                               msg = &m
+                               break
+                       }
+               }
+               if msg == nil {
+                       http.Error(w, "unknown message name: "+msgName, 500)
+                       return
+               }
+
+       }
+}
+
+func apiHandler(apifiles []*vppapi.File) func(http.ResponseWriter, *http.Request) {
+       return func(w http.ResponseWriter, req *http.Request) {
+               b, err := json.MarshalIndent(apifiles, "", "  ")
+               if err != nil {
+                       http.Error(w, err.Error(), 500)
+                       return
+               }
+               w.Write(b)
+       }
+}
+
+func apiFileHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
+       return func(w http.ResponseWriter, req *http.Request) {
+               b, err := json.MarshalIndent(apifile, "", "  ")
+               if err != nil {
+                       http.Error(w, err.Error(), 500)
+                       return
+               }
+               w.Write(b)
+       }
+}
+
+func rawHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
+       return func(w http.ResponseWriter, req *http.Request) {
+               b, err := ioutil.ReadFile(apifile.Path)
+               if err != nil {
+                       http.Error(w, err.Error(), 500)
+                       return
+               }
+               w.Write(b)
+       }
+}