Change module name to go.fd.io/govpp
[govpp.git] / cmd / govpp / main.go
1 //  Copyright (c) 2020 Cisco and/or its affiliates.
2 //
3 //  Licensed under the Apache License, Version 2.0 (the "License");
4 //  you may not use this file except in compliance with the License.
5 //  You may obtain a copy of the License at:
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 //  Unless required by applicable law or agreed to in writing, software
10 //  distributed under the License is distributed on an "AS IS" BASIS,
11 //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 //  See the License for the specific language governing permissions and
13 //  limitations under the License.
14
15 package main
16
17 import (
18         "bytes"
19         "context"
20         "encoding/json"
21         "flag"
22         "fmt"
23         "io"
24         "io/ioutil"
25         "log"
26         "net/http"
27         "os"
28         "strings"
29         "text/tabwriter"
30
31         "go.fd.io/govpp"
32         "go.fd.io/govpp/adapter/socketclient"
33         "go.fd.io/govpp/binapi/vlib"
34         "go.fd.io/govpp/binapi/vpe"
35         "go.fd.io/govpp/binapigen"
36         "go.fd.io/govpp/binapigen/vppapi"
37 )
38
39 func main() {
40         flag.Parse()
41
42         apifiles, err := vppapi.Parse()
43         if err != nil {
44                 log.Fatal(err)
45         }
46
47         switch cmd := flag.Arg(0); cmd {
48         case "server":
49                 runServer(apifiles, ":7777")
50         case "vppapi":
51                 showVPPAPI(os.Stdout, apifiles)
52         case "vppapijson":
53                 if flag.NArg() == 1 {
54                         writeAsJSON(os.Stdout, apifiles)
55                 } else {
56                         f := flag.Arg(1)
57                         var found bool
58                         for _, apifile := range apifiles {
59                                 if apifile.Name == f {
60                                         writeAsJSON(os.Stdout, apifile)
61                                         found = true
62                                         break
63                                 }
64                         }
65                         if !found {
66                                 log.Fatalf("VPP API file %q not found", f)
67                         }
68                 }
69         case "rpc":
70                 showRPC(apifiles)
71         case "cli":
72                 args := flag.Args()
73                 if len(args) == 0 {
74                         args = []string{"?"}
75                 }
76                 sendCLI(args[1:])
77         default:
78                 log.Fatalf("invalid command: %q", cmd)
79         }
80
81 }
82
83 func writeAsJSON(w io.Writer, data interface{}) {
84         b, err := json.MarshalIndent(data, "", "  ")
85         if err != nil {
86                 log.Fatal(err)
87                 return
88         }
89         if _, err := w.Write(b); err != nil {
90                 panic(err)
91         }
92 }
93
94 func showRPC(apifiles []*vppapi.File) {
95         for _, apifile := range apifiles {
96                 fmt.Printf("%s.api\n", apifile.Name)
97                 if apifile.Service == nil {
98                         continue
99                 }
100                 for _, rpc := range apifile.Service.RPCs {
101                         req := rpc.Request
102                         reply := rpc.Reply
103                         if rpc.Stream {
104                                 reply = "stream " + reply
105                         }
106                         fmt.Printf(" rpc (%s) --> (%s)\n", req, reply)
107                 }
108         }
109 }
110
111 func showVPPAPI(out io.Writer, apifiles []*vppapi.File) {
112         binapigen.SortFilesByImports(apifiles)
113
114         var buf bytes.Buffer
115         w := tabwriter.NewWriter(&buf, 0, 0, 3, ' ', 0)
116         fmt.Fprintf(w, "API\tOPTIONS\tCRC\tPATH\tIMPORTED\tTYPES\t\n")
117
118         for _, apifile := range apifiles {
119                 importedTypes := binapigen.ListImportedTypes(apifiles, apifile)
120                 var options []string
121                 for k, v := range apifile.Options {
122                         options = append(options, fmt.Sprintf("%s=%v", k, v))
123                 }
124                 imports := fmt.Sprintf("%d apis, %2d types", len(apifile.Imports), len(importedTypes))
125                 path := strings.TrimPrefix(apifile.Path, vppapi.DefaultDir+"/")
126                 types := fmt.Sprintf("%2d enum, %2d enumflag, %2d alias, %2d struct, %2d union, %2d msg",
127                         len(apifile.EnumTypes), len(apifile.EnumflagTypes), len(apifile.AliasTypes), len(apifile.StructTypes), len(apifile.UnionTypes), len(apifile.Messages))
128                 fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%v\t%s\t\n",
129                         apifile.Name, strings.Join(options, " "), apifile.CRC, path, imports, types)
130         }
131
132         if err := w.Flush(); err != nil {
133                 log.Fatal(err)
134         }
135         fmt.Fprint(out, buf.String())
136 }
137
138 func sendCLI(args []string) {
139         cmd := strings.Join(args, " ")
140         fmt.Printf("# %s\n", cmd)
141
142         conn, err := govpp.Connect("/run/vpp/api.sock")
143         if err != nil {
144                 log.Fatal(err)
145         }
146         defer conn.Disconnect()
147
148         ch, err := conn.NewAPIChannel()
149         if err != nil {
150                 log.Fatal(err)
151         }
152         defer ch.Close()
153
154         if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
155                 log.Fatal(err)
156         }
157
158         client := vlib.NewServiceClient(conn)
159         reply, err := client.CliInband(context.Background(), &vlib.CliInband{
160                 Cmd: cmd,
161         })
162         if err != nil {
163                 log.Fatal(err)
164         }
165
166         fmt.Print(reply.Reply)
167 }
168
169 func runServer(apifiles []*vppapi.File, addr string) {
170         apiRoutes(apifiles, http.DefaultServeMux)
171
172         conn, err := govpp.Connect(socketclient.DefaultSocketName)
173         if err != nil {
174                 log.Fatal(err)
175         }
176
177         vpeRPC := vpe.NewServiceClient(conn)
178         c := vpe.HTTPHandler(vpeRPC)
179
180         http.Handle("/", c)
181
182         log.Printf("listening on %v", addr)
183
184         if err := http.ListenAndServe(addr, nil); err != nil {
185                 log.Fatal(err)
186         }
187 }
188
189 func apiRoutes(apifiles []*vppapi.File, mux *http.ServeMux) {
190         for _, apifile := range apifiles {
191                 name := apifile.Name
192                 mux.HandleFunc("/vppapi/"+name, apiFileHandler(apifile))
193                 mux.HandleFunc("/raw/"+name, rawHandler(apifile))
194                 mux.HandleFunc("/rpc/"+name, rpcHandler(apifile))
195         }
196         mux.HandleFunc("/vppapi", apiHandler(apifiles))
197 }
198
199 func rpcHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
200         return func(w http.ResponseWriter, req *http.Request) {
201                 msgName := strings.TrimPrefix(req.URL.Path, "/rpc/"+apifile.Name+"/")
202                 if msgName == "" {
203                         http.Error(w, "no message name", 500)
204                         return
205                 }
206
207                 input, err := ioutil.ReadAll(req.Body)
208                 if err != nil {
209                         http.Error(w, err.Error(), 500)
210                         return
211                 }
212
213                 msgReq := make(map[string]interface{})
214                 err = json.Unmarshal(input, &msgReq)
215                 if err != nil {
216                         http.Error(w, err.Error(), 500)
217                         return
218                 }
219
220                 var msg *vppapi.Message
221                 for _, m := range apifile.Messages {
222                         if m.Name == msgName {
223                                 msg = &m
224                                 break
225                         }
226                 }
227                 if msg == nil {
228                         http.Error(w, "unknown message name: "+msgName, 500)
229                         return
230                 }
231
232         }
233 }
234
235 func apiHandler(apifiles []*vppapi.File) func(http.ResponseWriter, *http.Request) {
236         return func(w http.ResponseWriter, req *http.Request) {
237                 b, err := json.MarshalIndent(apifiles, "", "  ")
238                 if err != nil {
239                         http.Error(w, err.Error(), 500)
240                         return
241                 }
242                 w.Write(b)
243         }
244 }
245
246 func apiFileHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
247         return func(w http.ResponseWriter, req *http.Request) {
248                 b, err := json.MarshalIndent(apifile, "", "  ")
249                 if err != nil {
250                         http.Error(w, err.Error(), 500)
251                         return
252                 }
253                 w.Write(b)
254         }
255 }
256
257 func rawHandler(apifile *vppapi.File) func(http.ResponseWriter, *http.Request) {
258         return func(w http.ResponseWriter, req *http.Request) {
259                 b, err := ioutil.ReadFile(apifile.Path)
260                 if err != nil {
261                         http.Error(w, err.Error(), 500)
262                         return
263                 }
264                 w.Write(b)
265         }
266 }