ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ssh_flow_control.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ssh_flow_control.cpp
6 * @brief SSH channel flow control - RFC 4254 sec 5.2 window arithmetic.
7 */
8
11#include "protocore_config.h" // SSH_CHAN_MAX_PACKET
12#include "shared_primitives/endian.h" // pc_wr32be - the one source of truth for wire integers
13#include <string.h>
14
15void pc_ssh_flow_init(SshFlow *f, uint32_t local_window, uint32_t peer_window, uint32_t peer_max_pkt)
16{
17 f->local_window = local_window;
18 f->local_max = local_window;
19 f->peer_window = peer_window;
20 f->peer_max_pkt = peer_max_pkt;
21}
22
23bool pc_ssh_flow_recv_take(SshFlow *f, uint32_t n)
24{
25 if (n > f->local_window)
26 {
27 return false; // peer overran the advertised window (RFC 4254 sec 5.2)
28 }
29 f->local_window -= n;
30 return true;
31}
32
33bool pc_ssh_flow_replenish_due(const SshFlow *f, uint32_t *add)
34{
35 if (f->local_window >= f->local_max / 2)
36 {
37 return false;
38 }
39 *add = f->local_max - f->local_window;
40 return true;
41}
42
43void pc_ssh_flow_local_credit(SshFlow *f, uint32_t add)
44{
45 f->local_window += add;
46}
47
48bool pc_ssh_flow_send_allows(const SshFlow *f, size_t len)
49{
50 return len <= f->peer_window && len <= f->peer_max_pkt;
51}
52
53uint32_t pc_ssh_flow_send_cap(const SshFlow *f, uint32_t want)
54{
55 uint32_t cap{want};
56 if (cap > f->peer_window)
57 {
58 cap = f->peer_window;
59 }
60 if (cap > f->peer_max_pkt)
61 {
62 cap = f->peer_max_pkt;
63 }
64 return cap;
65}
66
67void pc_ssh_flow_send_take(SshFlow *f, uint32_t n)
68{
69 f->peer_window -= n;
70}
71
72void pc_ssh_flow_peer_add(SshFlow *f, uint32_t add)
73{
74 uint32_t w{f->peer_window};
75 f->peer_window = (w + add < w) ? 0xFFFFFFFFu : (w + add);
76}
77
79{
80 return f->peer_window;
81}
82
83// ---------------------------------------------------------------------------
84// Channel signaling (RFC 4254 sec 5)
85// ---------------------------------------------------------------------------
86
87int32_t pc_ssh_sig_build_open_failure(uint8_t *out, size_t cap, uint32_t peer_id, uint32_t reason, size_t *out_len)
88{
89 if (cap < 17)
90 {
91 return -1;
92 }
94 pc_wr32be(out + 1, peer_id);
95 pc_wr32be(out + 5, reason);
96 pc_wr32be(out + 9, 0); // empty description
97 pc_wr32be(out + 13, 0); // empty language
98 *out_len = 17;
99 return 0;
100}
101
102int32_t pc_ssh_sig_build_open_confirm(const SshFlow *f, uint32_t peer_id, uint32_t local_id, uint8_t *out, size_t cap,
103 size_t *out_len)
104{
105 if (cap < 17)
106 {
107 return -1;
108 }
110 pc_wr32be(out + 1, peer_id);
111 pc_wr32be(out + 5, local_id);
112 pc_wr32be(out + 9, f->local_window);
114 *out_len = 17;
115 return 0;
116}
117
118int32_t pc_ssh_sig_build_data(SshFlow *f, uint32_t peer_id, const uint8_t *data, size_t len, uint8_t *out, size_t cap,
119 size_t *out_len)
120{
121 if (!pc_ssh_flow_send_allows(f, len))
122 {
123 return -1; // would exceed the peer's window or its maximum packet size
124 }
125 if (cap < 9 + len)
126 {
127 return -1;
128 }
129 out[0] = SSH_MSG_CHANNEL_DATA;
130 pc_wr32be(out + 1, peer_id);
131 pc_wr32be(out + 5, (uint32_t)len);
132 memcpy(out + 9, data, len);
133 *out_len = 9 + len;
134 pc_ssh_flow_send_take(f, (uint32_t)len);
135 return 0;
136}
137
138int32_t pc_ssh_sig_build_window_adjust(uint32_t peer_id, uint32_t add, uint8_t *out, size_t cap, size_t *out_len)
139{
140 if (cap < 9)
141 {
142 return -1;
143 }
145 pc_wr32be(out + 1, peer_id);
146 pc_wr32be(out + 5, add);
147 *out_len = 9;
148 return 0;
149}
150
151int32_t pc_ssh_sig_build_close(uint32_t peer_id, uint8_t *out, size_t cap, size_t *out_len)
152{
153 if (cap < 10)
154 {
155 return -1;
156 }
157 out[0] = SSH_MSG_CHANNEL_EOF;
158 pc_wr32be(out + 1, peer_id);
159 out[5] = SSH_MSG_CHANNEL_CLOSE;
160 pc_wr32be(out + 6, peer_id);
161 *out_len = 10;
162 return 0;
163}
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
size_t pc_wr32be(uint8_t *p, uint32_t v)
Write v big-endian at p.
Definition endian.h:93
User-facing configuration for ProtoCore.
#define SSH_CHAN_MAX_PACKET
Maximum SSH channel data payload the server advertises it can receive per message.
void pc_ssh_flow_init(SshFlow *f, uint32_t local_window, uint32_t peer_window, uint32_t peer_max_pkt)
Start a channel's windows: ours at local_window, the peer's at what it advertised.
void pc_ssh_flow_local_credit(SshFlow *f, uint32_t add)
Credit our window by add, once that WINDOW_ADJUST has actually been sent.
int32_t pc_ssh_sig_build_window_adjust(uint32_t peer_id, uint32_t add, uint8_t *out, size_t cap, size_t *out_len)
CHANNEL_WINDOW_ADJUST granting add more bytes. Credit the window only once this is sent.
bool pc_ssh_flow_recv_take(SshFlow *f, uint32_t n)
Account n inbound bytes against our window.
int32_t pc_ssh_sig_build_open_confirm(const SshFlow *f, uint32_t peer_id, uint32_t local_id, uint8_t *out, size_t cap, size_t *out_len)
CHANNEL_OPEN_CONFIRMATION, advertising our current window and maximum packet size.
int32_t pc_ssh_sig_build_open_failure(uint8_t *out, size_t cap, uint32_t peer_id, uint32_t reason, size_t *out_len)
CHANNEL_OPEN_FAILURE. reason: 1 admin-prohibited, 2 connect-failed, 3 unknown-type,...
uint32_t pc_ssh_flow_send_cap(const SshFlow *f, uint32_t want)
Clamp a would-be send to what the peer currently permits.
int32_t pc_ssh_sig_build_data(SshFlow *f, uint32_t peer_id, const uint8_t *data, size_t len, uint8_t *out, size_t cap, size_t *out_len)
CHANNEL_DATA carrying len bytes, and account them against the peer's window.
bool pc_ssh_flow_send_allows(const SshFlow *f, size_t len)
True if len bytes fit both the peer's remaining window and its maximum packet size.
void pc_ssh_flow_send_take(SshFlow *f, uint32_t n)
Account n outbound bytes against the peer's window (call only after send_allows()).
uint32_t pc_ssh_flow_peer_window(const SshFlow *f)
Bytes we may still send the peer - the bound a producer sizes its next read to.
void pc_ssh_flow_peer_add(SshFlow *f, uint32_t add)
Credit the peer's window from an inbound WINDOW_ADJUST.
bool pc_ssh_flow_replenish_due(const SshFlow *f, uint32_t *add)
Decide whether a WINDOW_ADJUST is due, and for how much. Does not mutate.
int32_t pc_ssh_sig_build_close(uint32_t peer_id, uint8_t *out, size_t cap, size_t *out_len)
CHANNEL_EOF followed by CHANNEL_CLOSE, as one 10-byte pair.
SSH channel flow control - the RFC 4254 sec 5.2 window pair and nothing else.
SSH binary packet protocol: framing, AES-256-CTR encryption, HMAC-SHA2-256 integrity,...
#define SSH_MSG_CHANNEL_EOF
Definition ssh_packet.h:122
#define SSH_MSG_CHANNEL_DATA
Definition ssh_packet.h:121
#define SSH_MSG_CHANNEL_OPEN_CONFIRM
Definition ssh_packet.h:118
#define SSH_MSG_CHANNEL_OPEN_FAILURE
Definition ssh_packet.h:119
#define SSH_MSG_CHANNEL_CLOSE
Definition ssh_packet.h:123
#define SSH_MSG_CHANNEL_WINDOW_ADJUST
Definition ssh_packet.h:120
One channel's flow-control state (RFC 4254 sec 5.2).
uint32_t peer_max_pkt
Peer's maximum packet size; caps a single send independently of the window.
uint32_t local_max
The window we advertised, and replenish back up to.
uint32_t peer_window
Bytes we may still send the peer.
uint32_t local_window
Bytes the peer may still send us before we WINDOW_ADJUST.