G+Smo  24.08.0
Geometry + Simulation Modules
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
gsBase64.h
1 #pragma once
2 
3 #include <gsCore/gsDebug.h>
4 #include <gsMatrix/gsMatrix.h>
5 
6 #include <algorithm>
7 #include <array>
8 #include <numeric>
9 #include <string>
10 #include <vector>
11 
12 namespace gismo {
13 
21 class GISMO_EXPORT Base64 {
22  private:
24  using ByteRepresentation = unsigned char;
25 
27  static char char_encode_table(const unsigned& index) {
28  // Create static array in function to avoid use of c++17 static member
29  // declaration
30  static const std::array<char, 64> encode_table{
31  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
32  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
33  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
34  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
35  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
36  GISMO_ASSERT((index < 64),
37  "Requested index out of range. Input invalid");
38  return encode_table[index];
39  }
40 
49  static const std::array<unsigned, 256> ReverseCharEncodeTable_() {
50  std::array<unsigned, 256> et_reversed{};
51  // Fill remainding array entries with values outside range to throw
52  // assertions easily
53  et_reversed.fill(256);
54  const unsigned n_chars_{64};
55  for (unsigned i{}; i < n_chars_; i++) {
56  et_reversed[static_cast<unsigned>(char_encode_table(i))] = i;
57  }
58  return et_reversed;
59  }
60 
62  static unsigned char_decode_table(const unsigned& index) {
63  static const std::array<unsigned, 256> decode_table{
64  ReverseCharEncodeTable_()};
65  GISMO_ASSERT((index < 256),
66  "Requested index out of range. Input invalid");
67  GISMO_ASSERT((decode_table[index] != 256),
68  "Invalid decode type, this should never occur!");
69  return decode_table[index];
70  }
71 
72  // Check if string fulfills minimum requirements for B64 encoded strings
73  static bool isValidBase64String(const std::string& s) {
74  // Check if size is feasible
75  bool is_valid{s.size() % 4 == 0};
76  // Check if string only contains alpha-numeric chars or `/`, `+` or `=`
77  return is_valid && std::all_of(s.begin(), s.end(), [](const char& c) {
78  return isalnum(c) || c == '+' || c == '/' || c == '=';
79  });
80  }
81 
88  static std::string trimWhitespaces(const std::string& s) {
89  const std::string delimiters(" \n\t");
90  size_t first = s.find_first_not_of(delimiters);
91  if (std::string::npos == first) {
92  GISMO_ERROR("Empty string cannot be converted into data-vector");
93  }
94  size_t last = s.find_last_not_of(delimiters);
95  return s.substr(first, (last - first + 1));
96  }
97 
106  static std::string Encode_(const ByteRepresentation* byte_vector_ptr,
107  const std::size_t& minimum_n_bytes_required) {
108  // Padding blocks
109  const std::size_t additional_padding_bytes =
110  (3 - minimum_n_bytes_required % 3) % 3;
111 
112  // Required groups of three
113  const std::size_t number_of_groups =
114  (minimum_n_bytes_required + additional_padding_bytes) / 3;
115 
116  // Initialize return value
117  std::string encoded_string;
118  encoded_string.reserve(number_of_groups * 4);
119 
120  // Loop over bytes and decode them
121  for (std::size_t i_group{}; i_group < number_of_groups; i_group++) {
122  const std::size_t buffer_index = i_group * 3;
123  std::array<ByteRepresentation, 3> buffer{};
124  buffer[0] = buffer_index < minimum_n_bytes_required
125  ? byte_vector_ptr[buffer_index + 0]
126  : 0;
127  buffer[1] = buffer_index < minimum_n_bytes_required
128  ? byte_vector_ptr[buffer_index + 1]
129  : 0;
130  buffer[2] = buffer_index < minimum_n_bytes_required
131  ? byte_vector_ptr[buffer_index + 2]
132  : 0;
133 
134  // Transform bytes into chars using above private encoder table
135  encoded_string.push_back(
136  char_encode_table(((buffer[0] & 0xfc) >> 2)));
137  encoded_string.push_back(char_encode_table(
138  ((buffer[0] & 0x03) << 4) + ((buffer[1] & 0xf0) >> 4)));
139  encoded_string.push_back(char_encode_table(
140  ((buffer[1] & 0x0f) << 2) + ((buffer[2] & 0xc0) >> 6)));
141  encoded_string.push_back(
142  char_encode_table(((buffer[2] & 0x3f) << 0)));
143  }
144 
145  // Replace trailing invalid data with `=`
146  for (size_t i = 0; i < additional_padding_bytes; ++i) {
147  encoded_string[number_of_groups * 4 - i - 1] = '=';
148  }
149 
150  // Safety check
151  GISMO_ASSERT(
152  isValidBase64String(encoded_string),
153  "Something went wrong in B64 encoding, please write an issue");
154  return encoded_string;
155  }
156 
171  template <typename BaseType, typename TargetType>
172  static void CopyIntoGsMatrix(const std::vector<BaseType>& base_vector,
173  gsMatrix<TargetType>& result) {
174  // Check for size
175  const unsigned rows = result.rows();
176  const unsigned cols = result.cols();
177  // Size check
178  if (base_vector.size() != (rows * cols)) {
179  GISMO_ERROR(
180  "Input array has the wrong size or could not be converted");
181  }
182  // Converting into gsMatrix (manipulating directly on gsMatrix is
183  // more efficient if T=InputType)
184  for (unsigned i = 0; i < rows; ++i) {
185  for (unsigned j = 0; j < cols; ++j) {
186  result(i, j) =
187  static_cast<TargetType>(base_vector[i * cols + j]);
188  }
189  }
190  }
191 
200  template <typename BaseType, typename TargetType>
201  static void CopyIntoVector(const std::vector<BaseType>& base_vector,
202  std::vector<TargetType>& result) {
203  std::transform(base_vector.cbegin(), base_vector.cend(),
204  std::back_inserter(result), [](const BaseType& c) {
205  return static_cast<TargetType>(c);
206  });
207  }
208 
209  public:
217  template <typename BaseType>
218  static std::string Encode(const std::vector<BaseType>& data_vector) {
219  const ByteRepresentation* vector_as_bytes_ptr =
220  reinterpret_cast<const ByteRepresentation*>(&data_vector[0]);
221 
222  // Number of bytes for an entry
223  constexpr const std::size_t length_of_entry{sizeof(BaseType{})};
224  // Minimum number of bytes required
225  const std::size_t minimum_n_bytes_required =
226  length_of_entry * data_vector.size();
227 
228  return Encode_(vector_as_bytes_ptr, minimum_n_bytes_required);
229  }
230 
239  template <typename BaseType>
240  static std::string Encode(const gsMatrix<BaseType>& data_vector,
241  const bool& row_wise = true) {
242  GISMO_ASSERT(std::is_arithmetic<BaseType>::value, // can be static
243  "Encoding is unsafe for non-arithmetic types.");
244  // We need to ensure that the export is in the demanded export order, if
245  // the data is only a vector (i.e. either col or row is 1) or if the
246  // storage scheme is coherent with the demanded export order
247  if ((data_vector.cols() == 1) || (data_vector.rows() == 1) ||
248  (gsMatrix<BaseType>::IsRowMajor == row_wise)) {
249  const ByteRepresentation* vector_as_bytes_ptr =
250  reinterpret_cast<const ByteRepresentation*>(data_vector.data());
251 
252  // Number of bytes for an entry
253  constexpr const std::size_t length_of_entry{sizeof(BaseType{})};
254  // Minimum number of bytes required
255  const std::size_t minimum_n_bytes_required =
256  length_of_entry * data_vector.size();
257 
258  return Encode_(vector_as_bytes_ptr, minimum_n_bytes_required);
259  } else {
260  // Here we need to flip the order of elements, for simplicity the
261  // data is copied into a temporary vector (works best with current
262  // implementation but might be slower than other approaches)
263  std::vector<BaseType> copy_of_matrix;
264  copy_of_matrix.reserve(data_vector.rows() * data_vector.cols());
265 
266  // For readability we manipulate the Matrix using (memory safe)
267  // operator() overloads
268  if (row_wise) {
269  for (index_t i = 0; i < data_vector.rows(); ++i) {
270  for (index_t j = 0; j < data_vector.cols(); ++j) {
271  copy_of_matrix.push_back(data_vector(i, j));
272  }
273  }
274  } else {
275  for (index_t j = 0; j < data_vector.cols(); ++j) {
276  for (index_t i = 0; i < data_vector.rows(); ++i) {
277  copy_of_matrix.push_back(data_vector(i, j));
278  }
279  }
280  }
281  // Use vector overload
282  return Encode(copy_of_matrix);
283  }
284  }
285 
294  template <typename OutputType>
295  static std::vector<OutputType> Decode(const std::string& base64string) {
296  // Safeguard
297  const std::string& base64string_trimmed = trimWhitespaces(base64string);
298  // Check validity of string
299  GISMO_ASSERT(isValidBase64String(base64string_trimmed),
300  "Validity check failed");
301 
302  // Init return value
303  const std::size_t number_of_groups{base64string_trimmed.size() / 4};
304  constexpr const std::size_t length_of_entry{sizeof(OutputType{})};
305  const std::size_t number_of_output_values{(number_of_groups * 3) /
306  length_of_entry};
307  std::vector<OutputType> return_value;
308  return_value.resize(number_of_output_values);
309 
310  // Access as byte stream
311  ByteRepresentation* vector_as_bytes =
312  reinterpret_cast<ByteRepresentation*>(&return_value[0]);
313 
314  // Start the reverse process
315  for (std::size_t i_group{}; i_group < number_of_groups; i_group++) {
316  const std::size_t buffer_index = i_group * 4;
317  std::array<unsigned, 4> buffer{};
318  for (unsigned i{}; i < 4; i++) {
319  buffer[i] = base64string_trimmed[buffer_index + i] != '='
320  ? char_decode_table(static_cast<unsigned>(
321  base64string_trimmed[buffer_index + i]))
322  : 255;
323  }
324 
325  // Write bytes into vector
326  if (buffer[1] != 255) {
327  vector_as_bytes[i_group * 3] =
328  ((buffer[0] & 0x3f) << 2) + ((buffer[1] & 0x30) >> 4);
329  }
330  if (buffer[2] != 255) {
331  vector_as_bytes[i_group * 3 + 1] =
332  ((buffer[1] & 0x0f) << 4) + ((buffer[2] & 0x3c) >> 2);
333  }
334  if (buffer[3] != 255) {
335  vector_as_bytes[i_group * 3 + 2] =
336  ((buffer[2] & 0x03) << 6) + ((buffer[3] & 0x3f) >> 0);
337  }
338  }
339  return return_value;
340  }
341 
354  template <typename ScalarType>
355  static void DecodeIntoGsType(const std::string& base64_string,
356  const std::string& base_type_flag_,
357  gsMatrix<ScalarType>& result) {
358  // Format flag in this function is case sensitive
359  GISMO_ASSERT(
360  std::none_of(base_type_flag_.begin(), base_type_flag_.end(),
361  isupper),
362  "Format flag {ascii, b64float64, ...} must be all lowercase.");
363 
364  // Perform type checks (no integral to floting point conversion)
365  if (std::is_integral<ScalarType>::value ^
366  (base_type_flag_.find("int") != std::string::npos)) {
367  GISMO_ERROR(
368  "Conversions from integral to floating type and vice-versa is "
369  "not allowed!");
370  }
371 
372  // Perform the actual input (using the proper encoding type)
373  if (base_type_flag_ == "b64uint16") { // Unsigned int 16
374  CopyIntoGsMatrix(Decode<uint16_t>(base64_string), result);
375  } else if (base_type_flag_ == "b64uint32") { // Unsigned int 32
376  CopyIntoGsMatrix(Decode<uint32_t>(base64_string), result);
377  } else if (base_type_flag_ == "b64bint64") { // Unsigned int 64
378  CopyIntoGsMatrix(Decode<uint64_t>(base64_string), result);
379  } else if (base_type_flag_ == "b64int16") { // Int 16
380  CopyIntoGsMatrix(Base64::Decode<int16_t>(base64_string), result);
381  } else if (base_type_flag_ == "b64int32") { // Int 32
382  CopyIntoGsMatrix(Base64::Decode<int32_t>(base64_string), result);
383  } else if (base_type_flag_ == "b64int64") { // Int 64
384  CopyIntoGsMatrix(Base64::Decode<int64_t>(base64_string), result);
385  } else if (base_type_flag_ == "b64float32") { // Float 32
386  CopyIntoGsMatrix(Base64::Decode<float>(base64_string), result);
387  } else if (base_type_flag_ == "b64float64") { // Float 64
388  CopyIntoGsMatrix(Base64::Decode<double>(base64_string), result);
389  } else {
390  GISMO_ERROR("Reading matrix from XML found unknown type");
391  }
392  }
393 
406  template <typename ScalarType>
407  static void DecodeIntoGsType(const std::string& base64_string,
408  const std::string& base_type_flag_,
409  std::vector<ScalarType>& result) {
410  // Format flag in this function is case sensitive
411  GISMO_ASSERT(
412  std::none_of(base_type_flag_.begin(), base_type_flag_.end(),
413  isupper),
414  "Format flag {ascii, b64float64, ...} must be all lowercase.");
415 
416  // Perform type checks (no integral to floting point conversion)
417  if (std::is_integral<ScalarType>::value ^
418  (base_type_flag_.find("int") != std::string::npos)) {
419  GISMO_ERROR(
420  "Conversions from integral to floating type and vice-versa is "
421  "not allowed!");
422  }
423 
424  // Perform the actual input (using the proper encoding type)
425  if (base_type_flag_ == "b64uint16") { // Unsigned int 16
426  CopyIntoVector(Decode<uint16_t>(base64_string), result);
427  } else if (base_type_flag_ == "b64uint32") { // Unsigned int 32
428  CopyIntoVector(Decode<uint32_t>(base64_string), result);
429  } else if (base_type_flag_ == "b64bint64") { // Unsigned int 64
430  CopyIntoVector(Decode<uint64_t>(base64_string), result);
431  } else if (base_type_flag_ == "b64int16") { // Int 16
432  CopyIntoVector(Base64::Decode<int16_t>(base64_string), result);
433  } else if (base_type_flag_ == "b64int32") { // Int 32
434  CopyIntoVector(Base64::Decode<int32_t>(base64_string), result);
435  } else if (base_type_flag_ == "b64int64") { // Int 64
436  CopyIntoVector(Base64::Decode<int64_t>(base64_string), result);
437  } else if (base_type_flag_ == "b64float32") { // Float 32
438  CopyIntoVector(Base64::Decode<float>(base64_string), result);
439  } else if (base_type_flag_ == "b64float64") { // Float 64
440  CopyIntoVector(Base64::Decode<double>(base64_string), result);
441  } else {
442  GISMO_ERROR("Reading matrix from XML found unknown type");
443  }
444  }
445 };
446 } // namespace gismo
static std::string trimWhitespaces(const std::string &s)
Trim trailing and preceding whitespaces.
Definition: gsBase64.h:88
Provides declaration of Matrix class.
static std::vector< OutputType > Decode(const std::string &base64string)
Reading a B64 string, transforming it into a vector of a specific type.
Definition: gsBase64.h:295
static const std::array< unsigned, 256 > ReverseCharEncodeTable_()
Reverse the encoding table in an array.
Definition: gsBase64.h:49
static void DecodeIntoGsType(const std::string &base64_string, const std::string &base_type_flag_, std::vector< ScalarType > &result)
Decode a string and copy into requested gismo Type.
Definition: gsBase64.h:407
static std::string Encode(const std::vector< BaseType > &data_vector)
Helper routine for std::vector data.
Definition: gsBase64.h:218
static void CopyIntoVector(const std::vector< BaseType > &base_vector, std::vector< TargetType > &result)
Cast a vector of a base type into a vector of TargetType.
Definition: gsBase64.h:201
static void DecodeIntoGsType(const std::string &base64_string, const std::string &base_type_flag_, gsMatrix< ScalarType > &result)
Decode a string and copy into requested gismo Type.
Definition: gsBase64.h:355
#define index_t
Definition: gsConfig.h:32
static char char_encode_table(const unsigned &index)
Look up table.
Definition: gsBase64.h:27
A matrix with arbitrary coefficient type and fixed or dynamic size.
Definition: gsMatrix.h:38
This file contains the debugging and messaging system of G+Smo.
#define GISMO_ASSERT(cond, message)
Definition: gsDebug.h:89
Encode for base64 export.
Definition: gsBase64.h:21
static std::string Encode(const gsMatrix< BaseType > &data_vector, const bool &row_wise=true)
Helper routine for gsMatrix Types (non-sparse)
Definition: gsBase64.h:240
static void CopyIntoGsMatrix(const std::vector< BaseType > &base_vector, gsMatrix< TargetType > &result)
Copy a read vector of type BaseType into a gsMatrix with ScalarType TargetType.
Definition: gsBase64.h:172
static unsigned char_decode_table(const unsigned &index)
Lookup Table for Decoding B64 string.
Definition: gsBase64.h:62
#define GISMO_ERROR(message)
Definition: gsDebug.h:118
static std::string Encode_(const ByteRepresentation *byte_vector_ptr, const std::size_t &minimum_n_bytes_required)
Actual encoding routine where byte-stream is transformed and encoded.
Definition: gsBase64.h:106
unsigned char ByteRepresentation
Alias for one byte type.
Definition: gsBase64.h:24