putBytes method

void putBytes(
  1. Object keyExpr,
  2. ZBytes payload, {
  3. Encoding? encoding,
  4. ZBytes? attachment,
  5. Timestamp? timestamp,
  6. CongestionControl? congestionControl,
  7. Priority? priority,
  8. bool? isExpress,
  9. Locality? allowedDestination,
})

Publishes a ZBytes payload on the given keyExpr.

The payload is consumed by this call and must not be reused. Optionally set the encoding (MIME type) of the message. An optional attachment can be included; it is also consumed by this call. An optional timestamp can be attached to the message; borrowed, not consumed.

Send options

congestionControl, priority, isExpress and allowedDestination are each optional, and omitting one — or passing null — means the same thing: canon decides. This binding substitutes no value of its own. On this path canon's defaults are CongestionControl.drop (a push operation; get and declareQuerier default to CongestionControl.block instead), Priority.data, isExpress: false, and Locality.any.

⚠️ Read CongestionControl before selecting CongestionControl.block: it can park the calling thread for seconds and then close the transport.

Throws ZenohException if the key expression is invalid, the encoding is malformed, or the put fails. Throws StateError if the session has been closed, or the payload or attachment has been disposed or already consumed.

⛔ Throws ArgumentError if congestionControl is CongestionControl.blockFirst and the loaded native was built without Z_FEATURE_UNSTABLE_API. canon declares Z_CONGESTION_CONTROL_BLOCK_FIRST only under that flag, so on such a build there is no value to send. Pass CongestionControl.block or CongestionControl.drop, or select the unstable native through your app's user_defines.

⚠️ That refusal is raised BEFORE the session-closed check, so a closed session carrying CongestionControl.blockFirst reports the argument fault rather than StateError. This is the only entry point where both can apply, and the build-configuration fault is the one nothing else in the program will ever report; a closed session is discoverable from any other call on it.

Implementation

void putBytes(
  Object keyExpr,
  ZBytes payload, {
  Encoding? encoding,
  ZBytes? attachment,
  Timestamp? timestamp,
  CongestionControl? congestionControl,
  Priority? priority,
  bool? isExpress,
  Locality? allowedDestination,
}) {
  // FIRST STATEMENT, and that means AHEAD OF `_ensureOpen()` -- this is the
  // only one of the seven entry points carrying an explicit closed-session
  // check, so this is where the ordering is a choice rather than an
  // artefact of helper placement. A closed session is discoverable from any
  // other call on that session; a build-configuration fault is discoverable
  // from nothing else in the program, so where a call carries both, the
  // undiscoverable one is the one worth reporting. The alternative -- guard
  // after whatever state check happens to exist -- makes `put` and
  // `putBytes` answer differently on identical inputs, with the
  // discriminator invisible from the API.
  requireCongestionControlSupported(congestionControl);
  _ensureOpen();
  // Validate payload state before allocating KeyExpr
  final payloadPtr = payload.nativePtr;
  final attachmentPtr = attachment != null ? attachment.nativePtr : nullptr;
  // Two INDEPENDENT length-carried channels (R-2), fed from the RAW pair
  // (R-3a) -- never the derived `schema` getter, which would split a
  // composed mimeType and change what goes on the wire.
  final (mime, schema) = encoding != null
      ? encodingWireChannels(encoding)
      : (null, null);
  final encodingBuf = allocLengthCarriedUtf8(mime);
  final schemaBuf = allocLengthCarriedUtf8(schema);
  final tsPtr = timestamp != null ? _timestampToNative(timestamp) : nullptr;
  // OUTER-FINALLY: a wrong-typed or invalid key expression throws inside
  // _withKeyExprArg before the closure runs, so the release could not sit
  // inside it.
  try {
    _withKeyExprArg<void>(keyExpr, 'keyExpr', (loanedSession, loanedKe) {
      final rc = bindings.zd_put(
        loanedSession.cast(),
        loanedKe.cast(),
        payloadPtr.cast(),
        encodingBuf.ptr,
        encodingBuf.len,
        schemaBuf.ptr,
        schemaBuf.len,
        attachmentPtr.cast(),
        tsPtr.cast(),
        congestionControl?.value ?? -1,
        priority?.value ?? -1,
        isExpress == null ? -1 : (isExpress ? 1 : 0),
        allowedDestination?.value ?? -1,
      );
      // markConsumed is unconditional: z_bytes_move gravestones the owned
      // bytes regardless of the return code. The timestamp is borrowed (not
      // moved) -- it is never marked consumed.
      payload.markConsumed();
      if (attachment != null) attachment.markConsumed();
      if (rc != 0) {
        throw ZenohException('Put failed', rc);
      }
    });
  } finally {
    // allocLengthCarriedUtf8 allocates with calloc, so it is released with
    // calloc.free -- not the malloc.free the retired toNativeUtf8 site used.
    if (encodingBuf.ptr != nullptr) calloc.free(encodingBuf.ptr);
    if (schemaBuf.ptr != nullptr) calloc.free(schemaBuf.ptr);
    if (tsPtr != nullptr) calloc.free(tsPtr);
  }
}