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
open Sexplib.Conv
open Util
type state = Open | Sent_close
[@@deriving sexp]
type channel_end = {
id : int32;
win : int32;
max_pkt : int32;
} [@@deriving sexp]
type channel = {
us : channel_end;
them : channel_end;
state : state;
tosend: Cstruct_sexp.t;
} [@@deriving sexp]
let compare a b =
Int32.compare a.us.id b.us.id
type t = channel
module Ordered = struct
type t = channel
let compare = compare
end
let make_end id win max_pkt = { id; win; max_pkt }
let make ~us ~them = { us; them; state = Open; tosend = Cstruct.create 0 }
let to_string t = Sexplib.Sexp.to_string_hum (sexp_of_channel t)
let input_data t data =
let len = min (Cstruct.length data |> Int32.of_int) t.us.win in
let data, left = Cstruct.split data (Int32.to_int len) in
if Cstruct.length left > 0 then
Printf.printf "channel input_data: discarding %d bytes (window size)\n%!"
(Cstruct.length left);
let new_win = Int32.sub t.us.win len in
let* () = guard Int32.(new_win >= zero) "window underflow" in
let win, adjust =
if new_win < Ssh.channel_win_adj_threshold then
Ssh.channel_win_len, Int32.sub Ssh.channel_win_len new_win
else
new_win, Int32.zero
in
let* () = guard (Int32.(adjust >= zero)) "adjust underflow" in
assert Int32.(adjust >= zero);
let t = { t with us = { t.us with win } } in
let msg = if adjust <> Int32.zero then
Some (Ssh.Msg_channel_window_adjust (t.them.id, adjust))
else
None
in
Ok (t, data, msg)
let output_data t data =
let fragment data =
let max_pkt = Int32.to_int t.them.max_pkt in
let i =
Cstruct.iter
(fun buf ->
if (Cstruct.length buf) = 0 then
None
else
Some (min (Cstruct.length buf) max_pkt))
(fun buf -> buf)
data
in
Cstruct.fold (fun frags frag ->
Ssh.Msg_channel_data (t.them.id, frag) :: frags)
i [] |> List.rev
in
let tosend = cs_join t.tosend data in
let len = min (Cstruct.length tosend) (Int32.to_int t.them.win) in
let data, tosend = Cstruct.split tosend len in
let win = Int32.sub t.them.win (Int32.of_int len) in
let* () = guard Int32.(win >= zero) "window underflow" in
let t = { t with tosend; them = { t.them with win } } in
Ok (t, fragment data)
let adjust_window t len =
let win = Int32.add t.them.win len in
let* () = guard Int32.(win > zero) "window overflow" in
let data = t.tosend in
let t = { t with tosend = Cstruct.create 0; them = { t.them with win } } in
output_data t data
module Channel_map = Map.Make(Int32)
type db = channel Channel_map.t
let empty_db = Channel_map.empty
let is_empty = Channel_map.is_empty
let next_free db =
let rec linear lkey = function
| [] -> None
| hd :: tl ->
let key = fst hd in
if Int32.succ lkey <> key then
Some (Int32.succ lkey)
else
linear key tl
in
match Channel_map.max_binding_opt db with
| None -> Some Int32.zero
| Some (key, _) ->
if key <> (Int32.of_int (Ssh.max_channels - 1)) then
Some (Int32.succ key)
else
linear Int32.minus_one (Channel_map.bindings db)
let add ~id ~win ~max_pkt db =
match next_free db with
| None -> Error `No_channels_left
| Some key ->
let them = make_end id win max_pkt in
let us = make_end key Ssh.channel_win_len Ssh.channel_max_pkt_len in
let c = make ~us ~them in
Ok (c, Channel_map.add key c db)
let update c db = Channel_map.add c.us.id c db
let remove id db = Channel_map.remove id db
let lookup id db = Channel_map.find_opt id db
let id c = c.us.id
let their_id c = c.them.id