@@ -43,7 +43,7 @@ namespace snapshots
4343 struct ContentRangeHeader
4444 {
4545 size_t range_start;
46- size_t range_end ;
46+ size_t inclusive_range_end ;
4747 size_t total_size;
4848 };
4949
@@ -93,7 +93,7 @@ namespace snapshots
9393
9494 {
9595 const auto [p, ec] = std::from_chars (
96- range_end.begin (), range_end.end (), parsed_values.range_end );
96+ range_end.begin (), range_end.end (), parsed_values.inclusive_range_end );
9797 if (ec != std::errc ())
9898 {
9999 throw std::runtime_error (fmt::format (
@@ -115,6 +115,65 @@ namespace snapshots
115115 }
116116 }
117117
118+ {
119+ // Use content-length to determine whether the sender used an exclusive or
120+ // inclusive range end
121+ auto length_it = headers.find (ccf::http::headers::CONTENT_LENGTH);
122+ if (length_it == headers.end ())
123+ {
124+ throw std::runtime_error (
125+ " Response is missing expected content-length header" );
126+ }
127+
128+ size_t content_length = 0 ;
129+
130+ {
131+ const auto & length_s = length_it->second ;
132+
133+ const auto [p, ec] = std::from_chars (
134+ length_s.data (), length_s.data () + length_s.size (), content_length);
135+
136+ if (ec != std::errc ())
137+ {
138+ throw std::runtime_error (fmt::format (
139+ " Could not parse length from content-length header: {}" ,
140+ length_it->second ));
141+ }
142+ }
143+
144+ const auto range_length =
145+ parsed_values.inclusive_range_end - parsed_values.range_start ;
146+ if (range_length == content_length)
147+ {
148+ LOG_INFO_FMT (
149+ " Server sent an exclusive-end content-range header. "
150+ " content-length={}, content-range={}. Adjusting this to local "
151+ " inclusive-end representation. This should be a temporary "
152+ " mismatch, between 6.x and 7.x nodes in a mixed network" ,
153+ length_it->second ,
154+ it->second );
155+ parsed_values.inclusive_range_end -= 1 ;
156+ }
157+ else if (range_length + 1 == content_length)
158+ {
159+ LOG_DEBUG_FMT (
160+ " Server sent an inclusive-end content-range header. "
161+ " content-length={}, content-range={}. This is expected for 7.x to "
162+ " 7.x nodes" ,
163+ length_it->second ,
164+ it->second );
165+ }
166+ else
167+ {
168+ throw std::runtime_error (fmt::format (
169+ " content-range ({}, {} bytes) and content-length ({}) headers do not "
170+ " agree" ,
171+ it->second ,
172+ range_length + 1 ,
173+ length_it->second ));
174+ }
175+ }
176+
118177 return parsed_values;
119178 }
120179
@@ -142,6 +201,7 @@ namespace snapshots
142201 constexpr size_t range_size = 4L * 1024 * 1024 ;
143202 size_t range_start = 0 ;
144203 size_t range_end = range_size;
204+ size_t inclusive_range_end = range_end - 1 ;
145205 bool fetched_all = false ;
146206
147207 auto process_partial_response =
@@ -160,26 +220,29 @@ namespace snapshots
160220
161221 // The server may give us _less_ than we requested (since they know
162222 // where the file ends), but should never give us more
163- if (content_range.range_end > range_end )
223+ if (content_range.inclusive_range_end > inclusive_range_end )
164224 {
165225 throw std::runtime_error (fmt::format (
166226 " Unexpected range response. Requested bytes {}-{}, received "
167227 " range ending at {}" ,
168228 range_start,
169- range_end ,
170- content_range.range_end ));
229+ inclusive_range_end ,
230+ content_range.inclusive_range_end ));
171231 }
172232
233+ const auto content_range_exclusive_range_end =
234+ content_range.inclusive_range_end + 1 ;
235+
173236 const auto range_size =
174- content_range. range_end - content_range.range_start ;
237+ content_range_exclusive_range_end - content_range.range_start ;
175238 LOG_TRACE_FMT (
176239 " Received {}-byte chunk from {}. Now have {}/{}" ,
177240 range_size,
178241 request.get_url (),
179- content_range. range_end ,
242+ content_range_exclusive_range_end ,
180243 content_range.total_size );
181244
182- if (content_range. range_end == content_range.total_size )
245+ if (content_range_exclusive_range_end == content_range.total_size )
183246 {
184247 fetched_all = true ;
185248 }
@@ -188,6 +251,7 @@ namespace snapshots
188251 // Advance range for next request
189252 range_start = range_end;
190253 range_end = range_start + range_size;
254+ inclusive_range_end = range_end - 1 ;
191255 }
192256 };
193257
@@ -203,7 +267,8 @@ namespace snapshots
203267
204268 ccf::curl::UniqueSlist headers;
205269 headers.append (
206- " Range" , fmt::format (" bytes={}-{}" , range_start, range_end));
270+ ccf::http::headers::RANGE,
271+ fmt::format (" bytes={}-{}" , range_start, inclusive_range_end));
207272
208273 CURLcode curl_response = CURLE_FAILED_INIT;
209274 long status_code = 0 ;
@@ -281,7 +346,8 @@ namespace snapshots
281346 {
282347 ccf::curl::UniqueSlist headers;
283348 headers.append (
284- " Range" , fmt::format (" bytes={}-{}" , range_start, range_end));
349+ ccf::http::headers::RANGE,
350+ fmt::format (" bytes={}-{}" , range_start, inclusive_range_end));
285351
286352 std::unique_ptr<ccf::curl::CurlRequest> snapshot_range_request;
287353 CURLcode curl_response = CURLE_OK;
0 commit comments