1fd4e5da5Sopenharmony_ci// Copyright 2018 The Go Authors. 2fd4e5da5Sopenharmony_ci// 3fd4e5da5Sopenharmony_ci// Licensed under the Apache License, Version 2.0 (the "License"); 4fd4e5da5Sopenharmony_ci// you may not use this file except in compliance with the License. 5fd4e5da5Sopenharmony_ci// You may obtain a copy of the License at 6fd4e5da5Sopenharmony_ci// 7fd4e5da5Sopenharmony_ci// http://www.apache.org/licenses/LICENSE-2.0 8fd4e5da5Sopenharmony_ci// 9fd4e5da5Sopenharmony_ci// Unless required by applicable law or agreed to in writing, software 10fd4e5da5Sopenharmony_ci// distributed under the License is distributed on an "AS IS" BASIS, 11fd4e5da5Sopenharmony_ci// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12fd4e5da5Sopenharmony_ci// See the License for the specific language governing permissions and 13fd4e5da5Sopenharmony_ci// limitations under the License. 14fd4e5da5Sopenharmony_ci 15fd4e5da5Sopenharmony_cipackage protocol 16fd4e5da5Sopenharmony_ci 17fd4e5da5Sopenharmony_ciimport ( 18fd4e5da5Sopenharmony_ci "context" 19fd4e5da5Sopenharmony_ci "encoding/json" 20fd4e5da5Sopenharmony_ci "fmt" 21fd4e5da5Sopenharmony_ci "io" 22fd4e5da5Sopenharmony_ci "strings" 23fd4e5da5Sopenharmony_ci "sync" 24fd4e5da5Sopenharmony_ci "time" 25fd4e5da5Sopenharmony_ci 26fd4e5da5Sopenharmony_ci "github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/jsonrpc2" 27fd4e5da5Sopenharmony_ci) 28fd4e5da5Sopenharmony_ci 29fd4e5da5Sopenharmony_citype loggingStream struct { 30fd4e5da5Sopenharmony_ci stream jsonrpc2.Stream 31fd4e5da5Sopenharmony_ci log io.Writer 32fd4e5da5Sopenharmony_ci} 33fd4e5da5Sopenharmony_ci 34fd4e5da5Sopenharmony_ci// LoggingStream returns a stream that does LSP protocol logging too 35fd4e5da5Sopenharmony_cifunc LoggingStream(str jsonrpc2.Stream, w io.Writer) jsonrpc2.Stream { 36fd4e5da5Sopenharmony_ci return &loggingStream{str, w} 37fd4e5da5Sopenharmony_ci} 38fd4e5da5Sopenharmony_ci 39fd4e5da5Sopenharmony_cifunc (s *loggingStream) Read(ctx context.Context) ([]byte, int64, error) { 40fd4e5da5Sopenharmony_ci data, count, err := s.stream.Read(ctx) 41fd4e5da5Sopenharmony_ci if err == nil { 42fd4e5da5Sopenharmony_ci logIn(s.log, data) 43fd4e5da5Sopenharmony_ci } 44fd4e5da5Sopenharmony_ci return data, count, err 45fd4e5da5Sopenharmony_ci} 46fd4e5da5Sopenharmony_ci 47fd4e5da5Sopenharmony_cifunc (s *loggingStream) Write(ctx context.Context, data []byte) (int64, error) { 48fd4e5da5Sopenharmony_ci logOut(s.log, data) 49fd4e5da5Sopenharmony_ci count, err := s.stream.Write(ctx, data) 50fd4e5da5Sopenharmony_ci return count, err 51fd4e5da5Sopenharmony_ci} 52fd4e5da5Sopenharmony_ci 53fd4e5da5Sopenharmony_ci// Combined has all the fields of both Request and Response. 54fd4e5da5Sopenharmony_ci// We can decode this and then work out which it is. 55fd4e5da5Sopenharmony_citype Combined struct { 56fd4e5da5Sopenharmony_ci VersionTag jsonrpc2.VersionTag `json:"jsonrpc"` 57fd4e5da5Sopenharmony_ci ID *jsonrpc2.ID `json:"id,omitempty"` 58fd4e5da5Sopenharmony_ci Method string `json:"method"` 59fd4e5da5Sopenharmony_ci Params *json.RawMessage `json:"params,omitempty"` 60fd4e5da5Sopenharmony_ci Result *json.RawMessage `json:"result,omitempty"` 61fd4e5da5Sopenharmony_ci Error *jsonrpc2.Error `json:"error,omitempty"` 62fd4e5da5Sopenharmony_ci} 63fd4e5da5Sopenharmony_ci 64fd4e5da5Sopenharmony_citype req struct { 65fd4e5da5Sopenharmony_ci method string 66fd4e5da5Sopenharmony_ci start time.Time 67fd4e5da5Sopenharmony_ci} 68fd4e5da5Sopenharmony_ci 69fd4e5da5Sopenharmony_citype mapped struct { 70fd4e5da5Sopenharmony_ci mu sync.Mutex 71fd4e5da5Sopenharmony_ci clientCalls map[string]req 72fd4e5da5Sopenharmony_ci serverCalls map[string]req 73fd4e5da5Sopenharmony_ci} 74fd4e5da5Sopenharmony_ci 75fd4e5da5Sopenharmony_civar maps = &mapped{ 76fd4e5da5Sopenharmony_ci sync.Mutex{}, 77fd4e5da5Sopenharmony_ci make(map[string]req), 78fd4e5da5Sopenharmony_ci make(map[string]req), 79fd4e5da5Sopenharmony_ci} 80fd4e5da5Sopenharmony_ci 81fd4e5da5Sopenharmony_ci// these 4 methods are each used exactly once, but it seemed 82fd4e5da5Sopenharmony_ci// better to have the encapsulation rather than ad hoc mutex 83fd4e5da5Sopenharmony_ci// code in 4 places 84fd4e5da5Sopenharmony_cifunc (m *mapped) client(id string, del bool) req { 85fd4e5da5Sopenharmony_ci m.mu.Lock() 86fd4e5da5Sopenharmony_ci defer m.mu.Unlock() 87fd4e5da5Sopenharmony_ci v := m.clientCalls[id] 88fd4e5da5Sopenharmony_ci if del { 89fd4e5da5Sopenharmony_ci delete(m.clientCalls, id) 90fd4e5da5Sopenharmony_ci } 91fd4e5da5Sopenharmony_ci return v 92fd4e5da5Sopenharmony_ci} 93fd4e5da5Sopenharmony_ci 94fd4e5da5Sopenharmony_cifunc (m *mapped) server(id string, del bool) req { 95fd4e5da5Sopenharmony_ci m.mu.Lock() 96fd4e5da5Sopenharmony_ci defer m.mu.Unlock() 97fd4e5da5Sopenharmony_ci v := m.serverCalls[id] 98fd4e5da5Sopenharmony_ci if del { 99fd4e5da5Sopenharmony_ci delete(m.serverCalls, id) 100fd4e5da5Sopenharmony_ci } 101fd4e5da5Sopenharmony_ci return v 102fd4e5da5Sopenharmony_ci} 103fd4e5da5Sopenharmony_ci 104fd4e5da5Sopenharmony_cifunc (m *mapped) setClient(id string, r req) { 105fd4e5da5Sopenharmony_ci m.mu.Lock() 106fd4e5da5Sopenharmony_ci defer m.mu.Unlock() 107fd4e5da5Sopenharmony_ci m.clientCalls[id] = r 108fd4e5da5Sopenharmony_ci} 109fd4e5da5Sopenharmony_ci 110fd4e5da5Sopenharmony_cifunc (m *mapped) setServer(id string, r req) { 111fd4e5da5Sopenharmony_ci m.mu.Lock() 112fd4e5da5Sopenharmony_ci defer m.mu.Unlock() 113fd4e5da5Sopenharmony_ci m.serverCalls[id] = r 114fd4e5da5Sopenharmony_ci} 115fd4e5da5Sopenharmony_ci 116fd4e5da5Sopenharmony_ciconst eor = "\r\n\r\n\r\n" 117fd4e5da5Sopenharmony_ci 118fd4e5da5Sopenharmony_cifunc strID(x *jsonrpc2.ID) string { 119fd4e5da5Sopenharmony_ci if x == nil { 120fd4e5da5Sopenharmony_ci // should never happen, but we need a number 121fd4e5da5Sopenharmony_ci return "999999999" 122fd4e5da5Sopenharmony_ci } 123fd4e5da5Sopenharmony_ci if x.Name != "" { 124fd4e5da5Sopenharmony_ci return x.Name 125fd4e5da5Sopenharmony_ci } 126fd4e5da5Sopenharmony_ci return fmt.Sprintf("%d", x.Number) 127fd4e5da5Sopenharmony_ci} 128fd4e5da5Sopenharmony_ci 129fd4e5da5Sopenharmony_cifunc logCommon(outfd io.Writer, data []byte) (*Combined, time.Time, string) { 130fd4e5da5Sopenharmony_ci if outfd == nil { 131fd4e5da5Sopenharmony_ci return nil, time.Time{}, "" 132fd4e5da5Sopenharmony_ci } 133fd4e5da5Sopenharmony_ci var v Combined 134fd4e5da5Sopenharmony_ci err := json.Unmarshal(data, &v) 135fd4e5da5Sopenharmony_ci if err != nil { 136fd4e5da5Sopenharmony_ci fmt.Fprintf(outfd, "Unmarshal %v\n", err) 137fd4e5da5Sopenharmony_ci panic(err) // do better 138fd4e5da5Sopenharmony_ci } 139fd4e5da5Sopenharmony_ci tm := time.Now() 140fd4e5da5Sopenharmony_ci tmfmt := tm.Format("15:04:05.000 PM") 141fd4e5da5Sopenharmony_ci return &v, tm, tmfmt 142fd4e5da5Sopenharmony_ci} 143fd4e5da5Sopenharmony_ci 144fd4e5da5Sopenharmony_ci// logOut and logIn could be combined. "received"<->"Sending", serverCalls<->clientCalls 145fd4e5da5Sopenharmony_ci// but it wouldn't be a lot shorter or clearer and "shutdown" is a special case 146fd4e5da5Sopenharmony_ci 147fd4e5da5Sopenharmony_ci// Writing a message to the client, log it 148fd4e5da5Sopenharmony_cifunc logOut(outfd io.Writer, data []byte) { 149fd4e5da5Sopenharmony_ci v, tm, tmfmt := logCommon(outfd, data) 150fd4e5da5Sopenharmony_ci if v == nil { 151fd4e5da5Sopenharmony_ci return 152fd4e5da5Sopenharmony_ci } 153fd4e5da5Sopenharmony_ci if v.Error != nil { 154fd4e5da5Sopenharmony_ci id := strID(v.ID) 155fd4e5da5Sopenharmony_ci fmt.Fprintf(outfd, "[Error - %s] Received #%s %s%s", tmfmt, id, v.Error, eor) 156fd4e5da5Sopenharmony_ci return 157fd4e5da5Sopenharmony_ci } 158fd4e5da5Sopenharmony_ci buf := strings.Builder{} 159fd4e5da5Sopenharmony_ci id := strID(v.ID) 160fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning 161fd4e5da5Sopenharmony_ci if v.ID != nil && v.Method != "" && v.Params != nil { 162fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Received request '%s - (%s)'.\n", v.Method, id) 163fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Params: %s%s", *v.Params, eor) 164fd4e5da5Sopenharmony_ci maps.setServer(id, req{method: v.Method, start: tm}) 165fd4e5da5Sopenharmony_ci } else if v.ID != nil && v.Method == "" && v.Params == nil { 166fd4e5da5Sopenharmony_ci cc := maps.client(id, true) 167fd4e5da5Sopenharmony_ci elapsed := tm.Sub(cc.start) 168fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Received response '%s - (%s)' in %dms.\n", 169fd4e5da5Sopenharmony_ci cc.method, id, elapsed/time.Millisecond) 170fd4e5da5Sopenharmony_ci if v.Result == nil { 171fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Result: {}%s", eor) 172fd4e5da5Sopenharmony_ci } else { 173fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Result: %s%s", string(*v.Result), eor) 174fd4e5da5Sopenharmony_ci } 175fd4e5da5Sopenharmony_ci } else if v.ID == nil && v.Method != "" && v.Params != nil { 176fd4e5da5Sopenharmony_ci p := "null" 177fd4e5da5Sopenharmony_ci if v.Params != nil { 178fd4e5da5Sopenharmony_ci p = string(*v.Params) 179fd4e5da5Sopenharmony_ci } 180fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Received notification '%s'.\n", v.Method) 181fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Params: %s%s", p, eor) 182fd4e5da5Sopenharmony_ci } else { // for completeness, as it should never happen 183fd4e5da5Sopenharmony_ci buf = strings.Builder{} // undo common Trace 184fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "[Error - %s] on write ID?%v method:%q Params:%v Result:%v Error:%v%s", 185fd4e5da5Sopenharmony_ci tmfmt, v.ID != nil, v.Method, v.Params != nil, 186fd4e5da5Sopenharmony_ci v.Result != nil, v.Error != nil, eor) 187fd4e5da5Sopenharmony_ci p := "null" 188fd4e5da5Sopenharmony_ci if v.Params != nil { 189fd4e5da5Sopenharmony_ci p = string(*v.Params) 190fd4e5da5Sopenharmony_ci } 191fd4e5da5Sopenharmony_ci r := "null" 192fd4e5da5Sopenharmony_ci if v.Result != nil { 193fd4e5da5Sopenharmony_ci r = string(*v.Result) 194fd4e5da5Sopenharmony_ci } 195fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "%s\n%s\n%s%s", p, r, v.Error, eor) 196fd4e5da5Sopenharmony_ci } 197fd4e5da5Sopenharmony_ci outfd.Write([]byte(buf.String())) 198fd4e5da5Sopenharmony_ci} 199fd4e5da5Sopenharmony_ci 200fd4e5da5Sopenharmony_ci// Got a message from the client, log it 201fd4e5da5Sopenharmony_cifunc logIn(outfd io.Writer, data []byte) { 202fd4e5da5Sopenharmony_ci v, tm, tmfmt := logCommon(outfd, data) 203fd4e5da5Sopenharmony_ci if v == nil { 204fd4e5da5Sopenharmony_ci return 205fd4e5da5Sopenharmony_ci } 206fd4e5da5Sopenharmony_ci // ID Method Params => Sending request 207fd4e5da5Sopenharmony_ci // ID !Method Result(might be null, but !Params) => Sending response (could we get an Error?) 208fd4e5da5Sopenharmony_ci // !ID Method Params => Sending notification 209fd4e5da5Sopenharmony_ci if v.Error != nil { // does this ever happen? 210fd4e5da5Sopenharmony_ci id := strID(v.ID) 211fd4e5da5Sopenharmony_ci fmt.Fprintf(outfd, "[Error - %s] Sent #%s %s%s", tmfmt, id, v.Error, eor) 212fd4e5da5Sopenharmony_ci return 213fd4e5da5Sopenharmony_ci } 214fd4e5da5Sopenharmony_ci buf := strings.Builder{} 215fd4e5da5Sopenharmony_ci id := strID(v.ID) 216fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning 217fd4e5da5Sopenharmony_ci if v.ID != nil && v.Method != "" && (v.Params != nil || v.Method == "shutdown") { 218fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Sending request '%s - (%s)'.\n", v.Method, id) 219fd4e5da5Sopenharmony_ci x := "{}" 220fd4e5da5Sopenharmony_ci if v.Params != nil { 221fd4e5da5Sopenharmony_ci x = string(*v.Params) 222fd4e5da5Sopenharmony_ci } 223fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Params: %s%s", x, eor) 224fd4e5da5Sopenharmony_ci maps.setClient(id, req{method: v.Method, start: tm}) 225fd4e5da5Sopenharmony_ci } else if v.ID != nil && v.Method == "" && v.Params == nil { 226fd4e5da5Sopenharmony_ci sc := maps.server(id, true) 227fd4e5da5Sopenharmony_ci elapsed := tm.Sub(sc.start) 228fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Sending response '%s - (%s)' took %dms.\n", 229fd4e5da5Sopenharmony_ci sc.method, id, elapsed/time.Millisecond) 230fd4e5da5Sopenharmony_ci if v.Result == nil { 231fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Result: {}%s", eor) 232fd4e5da5Sopenharmony_ci } else { 233fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Result: %s%s", string(*v.Result), eor) 234fd4e5da5Sopenharmony_ci } 235fd4e5da5Sopenharmony_ci } else if v.ID == nil && v.Method != "" { 236fd4e5da5Sopenharmony_ci p := "null" 237fd4e5da5Sopenharmony_ci if v.Params != nil { 238fd4e5da5Sopenharmony_ci p = string(*v.Params) 239fd4e5da5Sopenharmony_ci } 240fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Sending notification '%s'.\n", v.Method) 241fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "Params: %s%s", p, eor) 242fd4e5da5Sopenharmony_ci } else { // for completeness, as it should never happen 243fd4e5da5Sopenharmony_ci buf = strings.Builder{} // undo common Trace 244fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "[Error - %s] on read ID?%v method:%q Params:%v Result:%v Error:%v%s", 245fd4e5da5Sopenharmony_ci tmfmt, v.ID != nil, v.Method, v.Params != nil, 246fd4e5da5Sopenharmony_ci v.Result != nil, v.Error != nil, eor) 247fd4e5da5Sopenharmony_ci p := "null" 248fd4e5da5Sopenharmony_ci if v.Params != nil { 249fd4e5da5Sopenharmony_ci p = string(*v.Params) 250fd4e5da5Sopenharmony_ci } 251fd4e5da5Sopenharmony_ci r := "null" 252fd4e5da5Sopenharmony_ci if v.Result != nil { 253fd4e5da5Sopenharmony_ci r = string(*v.Result) 254fd4e5da5Sopenharmony_ci } 255fd4e5da5Sopenharmony_ci fmt.Fprintf(&buf, "%s\n%s\n%s%s", p, r, v.Error, eor) 256fd4e5da5Sopenharmony_ci } 257fd4e5da5Sopenharmony_ci outfd.Write([]byte(buf.String())) 258fd4e5da5Sopenharmony_ci} 259