docs: def read_data(self, offsets):
          # type: (Descriptor, Offsets) -> Tuple[bytes, Offsets]
          """Read data from file
          :param Descriptor self: this
          :param Offsets offsets: offsets
          :rtype: tuple
          :return: (file data bytes, new Offsets if stdin)
          """
          newoffset = None
          if not self.local_path.use_stdin:
              if offsets.num_bytes == 0:
                  return None, None
              # compute start from view
              start = self.local_path.view.fd_start + offsets.range_start
              # encrypted offsets will read past the end of the file due
              # to padding, but will be accounted for after encryption+padding
              with self.local_path.absolute_path.open('rb') as fd:
                  fd.seek(start, 0)
                  data = fd.read(offsets.num_bytes)
          else:
              data = blobxfer.STDIN.read(self._chunk_size)
              if not data:
                  with self._meta_lock:
                      self._offset -= offsets.num_bytes
                      self._ase.size -= offsets.num_bytes
                      self._total_chunks -= 1
                      self._chunk_num -= 1
                      self._outstanding_ops -= 1
              else:
                  num_bytes = len(data)
                  with self._meta_lock:
                      self._offset -= offsets.num_bytes
                      self._ase.size -= offsets.num_bytes
                      newoffset = Offsets(
                          chunk_num=self._chunk_num - 1,
                          num_bytes=num_bytes,
                          range_start=self._offset,
                          range_end=self._offset + num_bytes - 1,
                          pad=False,
                      )
                      self._total_chunks += 1
                      self._outstanding_ops += 1
                      self._offset += num_bytes
                      self._ase.size += num_bytes
          if self.must_compute_md5 and data:
              with self._hasher_lock:
                  self.md5.update(data)
                  if self.is_resumable:
                      self._md5_cache[self._chunk_num - 1] = self.md5.hexdigest()
          return data, newoffset