]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
include/encoding: Fix encode/decode of float types on big-endian systems 36992/head
authorUlrich Weigand <ulrich.weigand@de.ibm.com>
Fri, 4 Sep 2020 13:42:41 +0000 (15:42 +0200)
committerUlrich Weigand <ulrich.weigand@de.ibm.com>
Sun, 6 Sep 2020 11:28:07 +0000 (13:28 +0200)
Currently, floating-point types use "raw" encoding, which means they're
simply copied as byte stream.

This means that if the decoding happens on a machine that differs in
byte order from the source machine, the returned value will be
incorrect. As one effect of this problem, a big-endian OSD node cannot
join a cluster where the MON node is little-endian (or vice versa),
because the OSDMap (incremental) structure contains floating-point
values, and as a result of this conversion problem, the OSD node will
crash with an assertion failure as soon as it receives any OSDMap update
from the MON.

This should be fixed by always encoding floating-point values in
little-endian byte order just as is done for integers. (Note that this
still assumes source and target machines used the same floating-point
format except for byte order. But given that nearly all platforms these
days use IEEE binary32/binary64 for float/double, that seems a
reasonable assumption.)

Fixes: https://tracker.ceph.com/issues/47302
Signed-off-by: Ulrich Weigand <ulrich.weigand@de.ibm.com>
src/include/encoding.h

index 2b3e723da22ab2cf170511cfe007b553a389bb60..5036f8710a459bb0b075587feff796c85c7b6895 100644 (file)
@@ -92,10 +92,6 @@ WRITE_RAW_ENCODER(ceph_le64)
 WRITE_RAW_ENCODER(ceph_le32)
 WRITE_RAW_ENCODER(ceph_le16)
 
-// FIXME: we need to choose some portable floating point encoding here
-WRITE_RAW_ENCODER(float)
-WRITE_RAW_ENCODER(double)
-
 inline void encode(const bool &v, bufferlist& bl) {
   __u8 vv = v;
   encode_raw(vv, bl);
@@ -129,6 +125,37 @@ WRITE_INTTYPE_ENCODER(int32_t, le32)
 WRITE_INTTYPE_ENCODER(uint16_t, le16)
 WRITE_INTTYPE_ENCODER(int16_t, le16)
 
+// -----------------------------------
+// float types
+//
+// NOTE: The following code assumes all supported platforms use IEEE binary32
+// as float and IEEE binary64 as double floating-point format.  The assumption
+// is verified by the assertions below.
+//
+// Under this assumption, we can use raw encoding of floating-point types
+// on little-endian machines, but we still need to perform a byte swap
+// on big-endian machines to ensure cross-architecture compatibility.
+// To achive that, we reinterpret the values as integers first, which are
+// byte-swapped via the ceph_le types as above.  The extra conversions
+// are optimized away on little-endian machines by the compiler.
+#define WRITE_FLTTYPE_ENCODER(type, itype, etype)                      \
+  static_assert(sizeof(type) == sizeof(itype));                                \
+  static_assert(std::numeric_limits<type>::is_iec559,                  \
+             "floating-point type not using IEEE754 format");          \
+  inline void encode(type v, ::ceph::bufferlist& bl, uint64_t features=0) { \
+    ceph_##etype e;                                                    \
+    e = *reinterpret_cast<itype *>(&v);                                        \
+    ::ceph::encode_raw(e, bl);                                         \
+  }                                                                    \
+  inline void decode(type &v, ::ceph::bufferlist::const_iterator& p) { \
+    ceph_##etype e;                                                    \
+    ::ceph::decode_raw(e, p);                                          \
+    *reinterpret_cast<itype *>(&v) = e;                                        \
+  }
+
+WRITE_FLTTYPE_ENCODER(float, uint32_t, le32)
+WRITE_FLTTYPE_ENCODER(double, uint64_t, le64)
+
 // see denc.h for ENCODE_DUMP_PATH discussion and definition.
 #ifdef ENCODE_DUMP_PATH
 # define ENCODE_DUMP_PRE()                     \