libflute
File.cpp
Go to the documentation of this file.
1 // libflute - FLUTE/ALC library
2 //
3 // Copyright (C) 2021 Klaus Kühnhammer (Österreichische Rundfunksender GmbH & Co KG)
4 //
5 // Licensed under the License terms and conditions for use, reproduction, and
6 // distribution of 5G-MAG software (the “License”). You may not use this file
7 // except in compliance with the License. You may obtain a copy of the License at
8 // https://www.5g-mag.com/reference-tools. Unless required by applicable law or
9 // agreed to in writing, software distributed under the License is distributed on
10 // an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 // or implied.
12 //
13 // See the License for the specific language governing permissions and limitations
14 // under the License.
15 //
16 #include <iostream>
17 #include <string>
18 #include <cstring>
19 #include <cmath>
20 #include <cassert>
21 #include <algorithm>
22 #include <sstream>
23 #include <iomanip>
24 // Suppress warnings about MD5 being deprecated in later versions of OpenSSL
25 #define OPENSSL_SUPPRESS_DEPRECATED 1
26 #include <openssl/md5.h>
27 #include <zlib.h>
28 
29 #include "base64.h"
30 #include "spdlog/spdlog.h"
31 #include "Transmitter.h"
32 #include "File.h"
33 
34 namespace LibFlute {
35 
37  : _meta( std::move(entry) )
38  , _received_at( time(nullptr) )
39  , _file_description()
40 {
41  spdlog::debug("Creating File from FileEntry");
42  // Allocate a data buffer
43  spdlog::debug("Allocating buffer");
44  _buffer = (char*)malloc(_meta.fec_oti.transfer_length);
45  if (_buffer == nullptr)
46  {
47  throw "Failed to allocate file buffer";
48  }
49  _own_buffer = true;
50 
51  this->calculate_partitioning();
52  this->create_blocks();
53 }
54 
55 File::File(const std::shared_ptr<Transmitter::FileDescription> &file_description)
56  : _meta()
57  , _file_description(file_description)
58 {
59  spdlog::debug("Creating File from FileDescription");
60 
61  auto length = _file_description->data_length();
62  _buffer = (char*)malloc(length);
63  if (_buffer == nullptr)
64  {
65  throw "No data allocated";
66  }
67  _own_buffer = true;
68  memcpy(_buffer, _file_description->data(), length);
69  _meta = _file_description->file_entry();
70 
71  // for no-code
74  } else {
75  throw "Unsupported FEC scheme";
76  }
77 
78  encode();
79 
80  calculate_partitioning();
81  create_blocks();
82 }
83 
84 File::File(uint32_t toi,
85  FecOti fec_oti,
86  std::string content_location,
87  std::string content_type,
88  uint64_t expires,
89  char* data,
90  size_t length,
91  bool copy_data)
92  : _own_buffer(false)
93  , _meta()
94  , _file_description()
95 {
96  spdlog::debug("Creating File from data");
97  if (copy_data) {
98  spdlog::debug("Allocating buffer");
99  _buffer = (char*)malloc(length);
100  if (_buffer == nullptr)
101  {
102  throw "Failed to allocate file buffer";
103  }
104  memcpy(_buffer, data, length);
105  _own_buffer = true;
106  } else {
107  _buffer = data;
108  }
109 
110  unsigned char md5[MD5_DIGEST_LENGTH];
111  MD5((const unsigned char*)data, length, md5);
112 
113  _meta.toi = toi;
114  _meta.content_location = std::move(content_location);
115  _meta.content_type = std::move(content_type);
116  _meta.content_length = length;
117  _meta.content_md5 = base64_encode(md5, MD5_DIGEST_LENGTH);
118  _meta.expires = expires;
119  _meta.fec_oti = fec_oti;
120 
121  // for no-code
124  } else {
125  throw "Unsupported FEC scheme";
126  }
127 
128  this->calculate_partitioning();
129  this->create_blocks();
130 }
131 
133 {
134  spdlog::debug("Destroying File");
135  if (_own_buffer && _buffer != nullptr)
136  {
137  spdlog::debug("Freeing buffer");
138  free(_buffer);
139  }
140 }
141 
142 auto File::put_symbol( const EncodingSymbol& symbol ) -> void
143 {
144  if (symbol.source_block_number() > _source_blocks.size()) {
145  throw "Source Block number too high";
146  }
147 
148  SourceBlock& source_block = _source_blocks[ symbol.source_block_number() ];
149 
150  if (symbol.id() > source_block.symbols.size()) {
151  throw "Encoding Symbol ID too high";
152  }
153 
154  SourceBlock::Symbol& target_symbol = source_block.symbols[symbol.id()];
155 
156  if (!target_symbol.complete) {
157  symbol.decode_to(target_symbol.data, target_symbol.length);
158  target_symbol.complete = true;
159 
160  check_source_block_completion(source_block);
161  check_file_completion();
162  }
163 
164 }
165 
166 auto File::check_source_block_completion( SourceBlock& block ) -> void
167 {
168  block.complete = std::all_of(block.symbols.begin(), block.symbols.end(), [](const auto& symbol){ return symbol.second.complete; });
169 }
170 
171 auto File::check_file_completion() -> void
172 {
173  _complete = std::all_of(_source_blocks.begin(), _source_blocks.end(), [](const auto& block){ return block.second.complete; });
174 
175  if (_complete && !_meta.content_md5.empty() && _meta.content_encoding.empty()) {
176  //check MD5 sum if we haven't encoded the contents
177  unsigned char md5[MD5_DIGEST_LENGTH];
178  MD5((const unsigned char*)buffer(), length(), md5);
179 
180  auto content_md5 = base64_decode(_meta.content_md5);
181  if (memcmp(md5, content_md5.c_str(), MD5_DIGEST_LENGTH) != 0) {
182  spdlog::debug("MD5 mismatch for TOI {}, discarding", _meta.toi);
183 
184  // MD5 mismatch, try again
185  for (auto& block : _source_blocks) {
186  for (auto& symbol : block.second.symbols) {
187  symbol.second.complete = false;
188  }
189  block.second.complete = false;
190  }
191  _complete = false;
192  }
193  }
194 }
195 
196 auto File::calculate_partitioning() -> void
197 {
198  // Calculate source block partitioning (RFC5052 9.1)
199  _nof_source_symbols = ceil((double)_meta.fec_oti.transfer_length / (double)_meta.fec_oti.encoding_symbol_length);
200  _nof_source_blocks = ceil((double)_nof_source_symbols / (double)_meta.fec_oti.max_source_block_length);
201  _large_source_block_length = ceil((double)_nof_source_symbols / (double)_nof_source_blocks);
202  _small_source_block_length = floor((double)_nof_source_symbols / (double)_nof_source_blocks);
203  _nof_large_source_blocks = _nof_source_symbols - _small_source_block_length * _nof_source_blocks;
204 }
205 
206 auto File::create_blocks() -> void
207 {
208  // Create the required source blocks and encoding symbols
209  auto buffer_ptr = _buffer;
210  size_t remaining_size = _meta.fec_oti.transfer_length;
211  decltype(_nof_large_source_blocks) number = 0;
212  while (remaining_size > 0) {
213  SourceBlock block;
214  size_t symbol_id = 0;
215  auto block_length = ( number < _nof_large_source_blocks ) ? _large_source_block_length : _small_source_block_length;
216 
217  for (decltype(block_length) i = 0; i < block_length; i++) {
218  auto symbol_length = std::min(remaining_size, (size_t)_meta.fec_oti.encoding_symbol_length);
219  assert(buffer_ptr + symbol_length <= _buffer + _meta.fec_oti.transfer_length);
220 
221  SourceBlock::Symbol symbol{.data = buffer_ptr, .length = symbol_length, .complete = false};
222  block.symbols[ symbol_id++ ] = symbol;
223 
224  remaining_size -= symbol_length;
225  buffer_ptr += symbol_length;
226 
227  if (remaining_size <= 0) break;
228  }
229  _source_blocks[number++] = block;
230  }
231 }
232 
233 auto File::get_next_symbols(size_t max_size) -> std::vector<EncodingSymbol>
234 {
235  int nof_symbols = std::ceil((float)(max_size - 4) / (float)_meta.fec_oti.encoding_symbol_length);
236  auto cnt = 0;
237  std::vector<EncodingSymbol> symbols;
238 
239  for (auto& block : _source_blocks) {
240  if (cnt >= nof_symbols) break;
241 
242  if (!block.second.complete) {
243  for (auto& symbol : block.second.symbols) {
244  if (cnt >= nof_symbols) break;
245 
246  if (!symbol.second.complete && !symbol.second.queued) {
247  symbols.emplace_back(symbol.first, block.first, symbol.second.data, symbol.second.length, _meta.fec_oti.encoding_id);
248  symbol.second.queued = true;
249  cnt++;
250  }
251  }
252  }
253  }
254  return symbols;
255 
256 }
257 
258 auto File::mark_completed(const std::vector<EncodingSymbol>& symbols, bool success) -> void
259 {
260  for (auto& symbol : symbols) {
261  auto block = _source_blocks.find(symbol.source_block_number());
262  if (block != _source_blocks.end()) {
263  auto sym = block->second.symbols.find(symbol.id());
264  if (sym != block->second.symbols.end()) {
265  sym->second.queued = false;
266  sym->second.complete = success;
267  }
268  check_source_block_completion(block->second);
269  check_file_completion();
270  }
271  }
272 }
273 
274 auto File::encode() -> void
275 {
276  if (!_been_encoded && !_meta.content_encoding.empty()) {
277  if (_meta.content_encoding == "gzip" || _meta.content_encoding=="deflate") {
278  auto decomp_buffer = _buffer;
279  bool own_decomp = _own_buffer;
280  std::shared_ptr<unsigned char> comp_buffer(new unsigned char[16384]);
281  z_stream zs = {
282  .next_in = reinterpret_cast<unsigned char*>(decomp_buffer),
283  .avail_in = static_cast<uint32_t>(_meta.content_length),
284  .next_out = comp_buffer.get(),
285  .avail_out = 16384
286  };
287  spdlog::debug("Compressing contents with {}", _meta.content_encoding);
288 
289  if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
290  _buffer = nullptr;
291  auto zstate = deflate(&zs, Z_FINISH);
292  size_t last_out = 0;
293  while (zstate == Z_OK) {
294  spdlog::debug("Part compressed: {} bytes", 16384-zs.avail_out);
295  _buffer = reinterpret_cast<char*>(realloc(_buffer, zs.total_out));
296  memcpy(_buffer+last_out, comp_buffer.get(), 16384-zs.avail_out);
297  last_out = zs.total_out;
298  _own_buffer = true;
299  zs.avail_out = 16384;
300  zs.next_out = comp_buffer.get();
301  zstate = inflate(&zs, Z_FINISH);
302  }
303  if (zstate==Z_STREAM_END) {
304  if (last_out != zs.total_out) {
305  spdlog::debug("Finish compress, last block is {} bytes. Total {} bytes", 16384-zs.avail_out, zs.total_out);
306  _buffer = reinterpret_cast<char*>(realloc(_buffer, zs.total_out));
307  memcpy(_buffer+last_out, comp_buffer.get(), 16384-zs.avail_out);
308  _own_buffer = true;
309  }
310  _meta.fec_oti.transfer_length = zs.total_out;
311  } else {
312  spdlog::error("Error compressing file {}: {}", _meta.toi, zs.msg);
313  throw zs.msg;
314  }
315  deflateEnd(&zs);
316 
317  if (own_decomp) free(decomp_buffer);
318  }
319  } else {
320  spdlog::error("Unknown Content-Encoding {}", _meta.content_encoding);
321  throw "Content-Encoding not known";
322  }
323 
324  _been_encoded = true;
325  _been_decoded = false;
326  }
327 }
328 
329 auto File::decode() -> void
330 {
331  if (!_been_decoded && !_meta.content_encoding.empty()) {
332  if (_meta.content_encoding == "gzip" || _meta.content_encoding=="deflate") {
333  auto comp_buffer = _buffer;
334  bool own_comp = _own_buffer;
335  std::shared_ptr<unsigned char> decomp_buffer(new unsigned char[16384]);
336  z_stream zs = {
337  .next_in = reinterpret_cast<unsigned char*>(comp_buffer),
338  .avail_in = static_cast<uint32_t>(_meta.fec_oti.transfer_length),
339  .next_out = decomp_buffer.get(),
340  .avail_out = 16384
341  };
342  spdlog::debug("Decompressing contents with {}", _meta.content_encoding);
343 
344  inflateInit2(&zs, 15 | ((_meta.content_encoding == "gzip")?16:0));
345  _buffer = nullptr;
346  auto zstate = inflate(&zs, Z_FINISH);
347  size_t last_out = 0;
348  while (zstate == Z_OK) {
349  spdlog::debug("Part decompressed: {} bytes", 16384-zs.avail_out);
350  _buffer = reinterpret_cast<char*>(realloc(_buffer, zs.total_out));
351  memcpy(_buffer+last_out, decomp_buffer.get(), 16384-zs.avail_out);
352  last_out = zs.total_out;
353  _own_buffer = true;
354  zs.avail_out = 16384;
355  zs.next_out = decomp_buffer.get();
356  zstate = inflate(&zs, Z_FINISH);
357  }
358  if (zstate==Z_STREAM_END) {
359  if (last_out != zs.total_out) {
360  spdlog::debug("Finish decompress, last block is {} bytes. Total {} bytes", 16384-zs.avail_out, zs.total_out);
361  _buffer = reinterpret_cast<char*>(realloc(_buffer, zs.total_out));
362  memcpy(_buffer+last_out, decomp_buffer.get(), 16384-zs.avail_out);
363  _own_buffer = true;
364  }
365  if (!_meta.content_length) {
366  _meta.content_length = zs.total_out;
367  } else if (_meta.content_length != zs.total_out) {
368  spdlog::error("Decompressed length does not match expected Content-Length ({} != {})", _meta.content_length, zs.total_out);
369  }
370  } else {
371  spdlog::error("Error decompressing file {}: {}", _meta.toi, zs.msg);
372  throw zs.msg;
373  }
374 
375  if (own_comp) free(comp_buffer);
376  } else {
377  spdlog::error("Unknown Content-Encoding {}", _meta.content_encoding);
378  throw "Content-Encoding not known";
379  }
380 
381  _been_decoded = true;
382  _been_encoded = false;
383 
384  // Check MD5
385  if (!_meta.content_md5.empty()) {
386  unsigned char md5[MD5_DIGEST_LENGTH];
387  MD5((const unsigned char*)buffer(), length(), md5);
388 
389  auto content_md5 = base64_decode(_meta.content_md5);
390  if (memcmp(md5, content_md5.c_str(), MD5_DIGEST_LENGTH) != 0) {
391  spdlog::debug("MD5 mismatch for TOI {}, discarding", _meta.toi);
392 
393  // MD5 mismatch, try again
394  for (auto& block : _source_blocks) {
395  for (auto& symbol : block.second.symbols) {
396  symbol.second.complete = false;
397  }
398  block.second.complete = false;
399  }
400  _complete = false;
401  }
402  }
403  }
404 }
405 
406 } // end namespace LibFlute
A class for handling FEC encoding symbols.
const FecOti & fec_oti() const
Get the FEC OTI values.
Definition: File.h:112
void put_symbol(const EncodingSymbol &symbol)
Write the data from an encoding symbol into the appropriate place in the buffer.
Definition: File.cpp:142
size_t length() const
Get the data buffer length.
Definition: File.h:90
virtual ~File()
Default destructor.
Definition: File.cpp:132
void encode()
Encode the buffer using the Content-Encoding.
Definition: File.cpp:274
File(LibFlute::FileDeliveryTable::FileEntry entry)
Create a file from an FDT entry (used for reception)
Definition: File.cpp:36
void mark_completed(const std::vector< EncodingSymbol > &symbols, bool success)
Mark encoding symbols as completed.
Definition: File.cpp:258
std::vector< EncodingSymbol > get_next_symbols(size_t max_size)
Get the next encoding symbols that fit in max_size bytes.
Definition: File.cpp:233
void decode()
Decode the buffer using the Content-Encoding.
Definition: File.cpp:329
OTI values struct.
Definition: flute_types.h:51
uint64_t transfer_length
Definition: flute_types.h:54
FecScheme encoding_id
Definition: flute_types.h:52
An entry for a file in the FDT.