-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
179 lines (167 loc) · 4.82 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package session
import (
"bufio"
"bytes"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/kr/pty"
"github.com/pion/webrtc/v2"
"golang.org/x/term"
)
type ClientSession struct {
Session
OfferURL string
}
func (cs *ClientSession) Run() error {
err := cs.init()
if err != nil {
return fmt.Errorf("could not init client session: %w", err)
}
maxPacketLifeTime := uint16(1000) // arbitrary
ordered := true
cs.Debug.Printf("creating data channel")
if cs.DataChannel, err = cs.PeerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{
Ordered: &ordered,
MaxPacketLifeTime: &maxPacketLifeTime,
}); err != nil {
return fmt.Errorf("could not create client data channel: %w", err)
}
cs.DataChannel.OnOpen(cs.dataChannelOnOpen())
cs.DataChannel.OnMessage(cs.dataChannelOnMessage())
cs.DataChannel.OnError(cs.dataChannelOnError())
cs.DataChannel.OnClose(cs.dataChannelOnClose())
cs.Debug.Printf("data channel setup")
body, err := cs.getSDP(cs.OfferURL)
if err != nil {
return fmt.Errorf("could not get sdp from server: %w", err)
}
cs.Debug.Printf("recieved offer")
cs.Debug.Printf("got body: %s", body)
var offerSD SessionDescription
err = offerSD.Decode(string(body))
if err != nil {
return fmt.Errorf("could not decode sdp answer: %w", err)
}
cs.Debug.Printf("decoded offer: %+v", offerSD)
cs.OfferSD = offerSD
offer := webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer,
SDP: cs.OfferSD.SDP,
}
if err := cs.PeerConnection.SetRemoteDescription(offer); err != nil {
return fmt.Errorf("could not set remote description: %w", err)
}
cs.Debug.Printf("remote connection set")
answer, err := cs.PeerConnection.CreateAnswer(nil)
if err != nil {
return fmt.Errorf("could not create answer: %w", err)
}
cs.Debug.Printf("answer created")
err = cs.PeerConnection.SetLocalDescription(answer)
if err != nil {
return fmt.Errorf("could not set local description: %w", err)
}
cs.Debug.Printf("local description set")
answerSD := SessionDescription{
SDP: answer.SDP,
}
encodedAnswer, err := answerSD.Encode()
if err != nil {
return fmt.Errorf("could not encode answer: %w", err)
}
cs.Debug.Printf("answer encoded")
if cs.OfferSD.SDPAnswerURI == "" {
return fmt.Errorf("no uri provided to upload answer")
}
if err := cs.putSDP(cs.OfferSD.SDPAnswerURI, bytes.NewBuffer([]byte(encodedAnswer))); err != nil {
return fmt.Errorf("could not upload SDP answer: %w", err)
}
cs.Debug.Printf("answer uploaded, waiting for connection")
// wait here to quit
err = <-cs.ErrorChan
if err != nil {
return fmt.Errorf("recieved error from error channel: %w", err)
}
err = cs.cleanup()
if err != nil {
return fmt.Errorf("could not clean up: %w", err)
}
return nil
}
func sendTermSize(term *os.File, dcSend func(s string) error) error {
winSize, err := pty.GetsizeFull(term)
if err != nil {
return fmt.Errorf("could not get terminal size: %w", err)
}
size := fmt.Sprintf(`["set_size",%d,%d,%d,%d]`, winSize.Rows, winSize.Cols, winSize.X, winSize.Y)
err = dcSend(size)
if err != nil {
return fmt.Errorf("could not send terminal size: %w", err)
}
return nil
}
func (cs *ClientSession) dataChannelOnOpen() func() {
return func() {
cs.Debug.Printf("Data channel '%s'-'%d'='%d' open.\n", cs.DataChannel.Label(), cs.DataChannel.ID(), cs.DataChannel.MaxPacketLifeTime())
cs.Debug.Println("Terminal session started")
if err := cs.makeRawTerminal(); err != nil {
cs.ErrorChan <- fmt.Errorf("could not make raw terminal: %w", err)
return
}
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
err := sendTermSize(cs.Stdin, cs.DataChannel.SendText)
if err != nil {
cs.ErrorChan <- fmt.Errorf("could not send terminal size: %w", err)
return
}
}
}()
ch <- syscall.SIGWINCH // initial resize
buf := make([]byte, 1024)
for {
nr, err := cs.Stdin.Read(buf)
if err != nil {
cs.ErrorChan <- fmt.Errorf("could not read stdin: %w", err)
return
}
err = cs.DataChannel.Send(buf[0:nr])
if err != nil {
cs.ErrorChan <- fmt.Errorf("could not send buffer over data channel: %w", err)
return
}
}
}
}
func (cs *ClientSession) dataChannelOnMessage() func(msg webrtc.DataChannelMessage) {
return func(p webrtc.DataChannelMessage) {
if p.IsString {
if string(p.Data) == "quit" {
if cs.IsTerminal {
term.Restore(int(cs.Stdin.Fd()), cs.OldTerminalState)
}
cs.ErrorChan <- nil
return
}
cs.ErrorChan <- fmt.Errorf("unexpected string message: %s", string(p.Data))
} else {
f := bufio.NewWriter(cs.Stdout)
f.Write(p.Data)
f.Flush()
}
}
}
func (cs *ClientSession) dataChannelOnClose() func() {
return func() {
cs.Debug.Printf("data channel closed")
}
}
func (cs *ClientSession) dataChannelOnError() func(err error) {
return func(err error) {
cs.Debug.Printf("error from datachannel: %s", err)
}
}