arduino-audio-tools
Loading...
Searching...
No Matches
HttpChunkReader.h
Go to the documentation of this file.
1#pragma once
2
3#include <cctype>
4#include "HttpHeader.h"
5#include "HttpLineReader.h"
6
7namespace audio_tools {
8
16 public:
20 has_ended = false;
21 }
22
25 http_header_ptr = &header;
27 has_ended = false;
28 }
29
31 bool open(Client& client) {
32 LOGD("HttpChunkReader: %s", "open");
33 has_ended = false;
34 return readChunkLen(client);
35 }
36
38 int read(Client& client, uint8_t* str, int len) {
39 LOGD("HttpChunkReader: %s", "read");
40 if (has_ended && open_chunk_len == 0) return 0;
41
42 // read the chunk data - but not more then available
43 int read_max = len < open_chunk_len ? len : open_chunk_len;
44 int len_processed = client.read(str, read_max);
45 if (len_processed == -1) {
46 LOGI("HttpChunkReader: client.read result -1, open: %d",open_chunk_len);
47 return 0;
48 }
49 // update current unprocessed chunk
50 open_chunk_len -= len_processed;
51
52 // remove traling CR LF from data
53 if (open_chunk_len <= 0) {
54 removeCRLF(client);
55 readChunkLen(client);
56 }
57
58 return len_processed;
59 }
60
62 int readln(Client& client, uint8_t* str, int len,
63 bool incl_nl = true) {
64 LOGD("HttpChunkReader: %s", "readln");
65 if (has_ended && open_chunk_len == 0) return 0;
66
67 int read_max = len < open_chunk_len ? len : open_chunk_len;
68 int len_processed = readlnInternal(client, str, read_max, incl_nl);
69 if (len_processed == -1) {
70 LOGD("HttpChunkReader: readln result -1");
71 return -1;
72 }
73 open_chunk_len -= len_processed;
74
75 // the chunks are terminated by a final CRLF
76 if (open_chunk_len <= 0) {
77 removeCRLF(client);
78 readChunkLen(client);
79 }
80
81 return len_processed;
82 }
83
85 int available() {
86 int result = has_ended ? 0 : open_chunk_len;
87 LOGD("HttpChunkReader: available=>%d", result);
88 return result;
89 }
90
91 protected:
93 bool has_ended = false;
96
97 // Every chunk's data is mandatorily followed by CR LF before the next
98 // chunk's length line (RFC 7230 4.1). client.peek() is non-blocking, so
99 // if those 2 bytes haven't physically arrived yet (common over TLS,
100 // where data shows up as discrete decrypted records, or just plain
101 // network jitter), a single peek() silently finds nothing and this
102 // returns having consumed neither byte - readChunkLen() then reads
103 // starting 1-2 bytes behind where it should, permanently desyncing
104 // chunk boundary tracking for the rest of the connection (this is what
105 // eventually surfaces as "readChunkLen: '' is not a valid chunk
106 // length"). Wait briefly for the bytes to actually show up instead of
107 // giving up on the first try.
108 void removeCRLF(Client& client) {
109 LOGD("HttpChunkReader: %s", "removeCRLF");
110 waitAndConsume(client, '\r');
111 waitAndConsume(client, '\n');
112 }
113
119 void waitAndConsume(Client& client, char expected) {
120 uint32_t timeout = millis() + 2000;
121 int peeked = client.peek();
122 while (peeked < 0 && millis() < timeout) {
123 delay(1);
124 peeked = client.peek();
125 }
126 if (peeked == expected) {
127 client.read();
128 } else {
129 LOGW("HttpChunkReader: removeCRLF expected '%d' but got '%d'", (int)expected, peeked);
130 }
131 }
132
133 // we read the chunk length which is indicated as hex value
134 bool readChunkLen(Client& client) {
135 LOGD("HttpChunkReader::readChunkLen");
136 uint8_t len_str[HTTP_CHUNKED_SIZE_MAX_LEN + 1] = {0};
137 if (readlnInternal(client, len_str, HTTP_CHUNKED_SIZE_MAX_LEN, false) < 0) {
138 LOGD("HttpChunkReader::readChunkLen readlnInternal result -1");
139 has_ended = true;
140 return false;
141 }
142
143 // strtol() silently returns 0 for a line that isn't a valid hex
144 // number at all, which is indistinguishable from a legitimate
145 // "0\r\n" last-chunk marker - if our read position has desynced from
146 // the server's actual chunk boundaries (e.g. after a partial/short
147 // read straddling a boundary), this line is actually raw audio
148 // data, not a chunk-length line. Without this check, that garbage
149 // gets accepted as "end of body", and the readExt() call below then
150 // tries to parse the *following* audio bytes as HTTP trailer
151 // headers too - cascading the corruption instead of surfacing it.
152 bool looksLikeHex = len_str[0] != 0;
153 for (uint8_t* p = len_str; *p != 0 && looksLikeHex; ++p) {
154 if (!isxdigit(*p)) looksLikeHex = false;
155 }
156 if (!looksLikeHex) {
157 LOGE("HttpChunkReader::readChunkLen: '%s' is not a valid chunk length - connection desynced", len_str);
158 has_ended = true;
159 open_chunk_len = 0;
160 return false;
161 }
162
163 open_chunk_len = strtol((char*)len_str, nullptr, 16);
164
165 if (open_chunk_len == 0) {
166 has_ended = true;
167 LOGD("HttpChunkReader::readChunkLen %s", "last chunk received");
168 // processing of additinal final headers after the chunk end
169 if (http_header_ptr != nullptr) {
170 http_header_ptr->readExt(client);
171 }
172 }
173 return true;
174 }
175};
176
177} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
#define LOGE(...)
Definition AudioLoggerIDF.h:30
#define HTTP_CHUNKED_SIZE_MAX_LEN
Definition AudioToolsConfig.h:153
Definition Arduino.h:162
virtual int read(uint8_t *buffer, size_t len)
Definition Arduino.h:165
Http might reply with chunks. So we need to dechunk the data. see https://en.wikipedia....
Definition HttpChunkReader.h:15
int readln(Client &client, uint8_t *str, int len, bool incl_nl=true)
reads a single line from the chunks
Definition HttpChunkReader.h:62
bool has_ended
Definition HttpChunkReader.h:93
int available()
returns the number of bytes which are still available in the current chunk
Definition HttpChunkReader.h:85
void removeCRLF(Client &client)
Definition HttpChunkReader.h:108
HttpReplyHeader * http_header_ptr
Definition HttpChunkReader.h:94
bool readChunkLen(Client &client)
Definition HttpChunkReader.h:134
HttpChunkReader(HttpReplyHeader &header)
constructor for processing final header information
Definition HttpChunkReader.h:24
bool open(Client &client)
opens the chunk reader and reads the first chunk length
Definition HttpChunkReader.h:31
int last_logged_stall_len
Definition HttpChunkReader.h:95
void waitAndConsume(Client &client, char expected)
Definition HttpChunkReader.h:119
int open_chunk_len
Definition HttpChunkReader.h:92
int read(Client &client, uint8_t *str, int len)
reads a block of data from the chunks
Definition HttpChunkReader.h:38
HttpChunkReader()
default constructor
Definition HttpChunkReader.h:18
We read a single line. A terminating 0 is added to the string to make it compliant for c string funct...
Definition HttpLineReader.h:14
virtual int readlnInternal(Stream &client, uint8_t *str, int len, bool incl_nl=true)
reads up the the next CR LF - but never more then the indicated len.
Definition HttpLineReader.h:20
Reading and Writing of Http Replys.
Definition HttpHeader.h:464
void readExt(Client &in)
Definition HttpHeader.h:478
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
void delay(uint32_t ms)
Definition Arduino.h:259
uint32_t millis()
Returns the milliseconds since the start.
Definition Arduino.h:260