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