1use chio_core::canonical::canonical_json_bytes;
2use chio_core::capability::scope::MonetaryAmount;
3use chio_core::crypto::{sha256_hex, PublicKey};
4use chio_core::receipt::body::ChioReceipt;
5use chio_core::receipt::economics::{
6 ChannelSettlementModeV1, SettlementStatus, CHIO_CHANNEL_RECEIPT_METADATA_SCHEMA,
7};
8use chio_credit::obligation::{
9 derive_obligation_payee_binding_digest, ObligationAtomV1, ObligationCreditElectionV1,
10};
11use serde::{Deserialize, Serialize};
12
13use super::validation::{
14 digest, parse_base_units, validate_currency, validate_digest, validate_text,
15 I_JSON_MAX_SAFE_INTEGER,
16};
17use super::{
18 ChannelError, ChannelLifecycleStatusV1, ChannelOpenTrustV1, ChannelSignatureV1,
19 VerifiedAdmittedChannelReservationV1, VerifiedChannelOpenConsentV1,
20};
21
22pub const CHANNEL_STATE_SCHEMA: &str = "chio.channel.state.v1";
23
24const CHANNEL_STATE_BODY_DIGEST_DOMAIN: &[u8] = b"chio.channel.state.body.digest.v1\0";
25const CHANNEL_STATE_DIGEST_DOMAIN: &[u8] = b"chio.channel.state.digest.v1\0";
26const CHANNEL_RECEIPT_ROOT_DOMAIN: &[u8] = b"chio.channel.receipt-root.v1\0";
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase", deny_unknown_fields)]
30pub struct ChannelStateBodyV1 {
31 pub schema: String,
32 pub channel_id: String,
33 pub seq: u64,
34 #[serde(
35 default,
36 skip_serializing_if = "Option::is_none",
37 deserialize_with = "super::validation::deserialize_present_option"
38 )]
39 pub prev_state_digest: Option<String>,
40 pub cumulative_owed: MonetaryAmount,
41 pub receipt_id_root: String,
42 pub receipt_count: u64,
43 #[serde(
44 default,
45 skip_serializing_if = "Option::is_none",
46 deserialize_with = "super::validation::deserialize_present_option"
47 )]
48 pub receipt_id: Option<String>,
49 #[serde(
50 default,
51 skip_serializing_if = "Option::is_none",
52 deserialize_with = "super::validation::deserialize_present_option"
53 )]
54 pub receipt_digest: Option<String>,
55 #[serde(
56 default,
57 skip_serializing_if = "Option::is_none",
58 deserialize_with = "super::validation::deserialize_present_option"
59 )]
60 pub receipt_authority_digest: Option<String>,
61 #[serde(
62 default,
63 skip_serializing_if = "Option::is_none",
64 deserialize_with = "super::validation::deserialize_present_option"
65 )]
66 pub obligation_atom_digest: Option<String>,
67 #[serde(
68 default,
69 skip_serializing_if = "Option::is_none",
70 deserialize_with = "super::validation::deserialize_present_option"
71 )]
72 pub reservation_digest: Option<String>,
73 #[serde(
74 default,
75 skip_serializing_if = "Option::is_none",
76 deserialize_with = "super::validation::deserialize_present_option"
77 )]
78 pub actual_charge: Option<MonetaryAmount>,
79 pub cumulative_token_base_units: String,
80 pub asset_binding_digest: String,
81}
82
83impl ChannelStateBodyV1 {
84 pub fn initial(
85 channel_id: String,
86 currency: String,
87 asset_binding_digest: String,
88 ) -> Result<Self, ChannelError> {
89 let state = Self {
90 schema: CHANNEL_STATE_SCHEMA.to_owned(),
91 channel_id,
92 seq: 0,
93 prev_state_digest: None,
94 cumulative_owed: MonetaryAmount { units: 0, currency },
95 receipt_id_root: empty_receipt_root()?,
96 receipt_count: 0,
97 receipt_id: None,
98 receipt_digest: None,
99 receipt_authority_digest: None,
100 obligation_atom_digest: None,
101 reservation_digest: None,
102 actual_charge: None,
103 cumulative_token_base_units: "0".to_owned(),
104 asset_binding_digest,
105 };
106 state.validate()?;
107 Ok(state)
108 }
109
110 pub fn validate(&self) -> Result<(), ChannelError> {
111 if self.schema != CHANNEL_STATE_SCHEMA {
112 return Err(ChannelError::InvalidField("channel_state_schema"));
113 }
114 validate_digest("state_channel_id", &self.channel_id)?;
115 validate_currency(&self.cumulative_owed.currency)?;
116 validate_digest("receipt_id_root", &self.receipt_id_root)?;
117 validate_digest("state_asset_binding_digest", &self.asset_binding_digest)?;
118 parse_base_units(&self.cumulative_token_base_units)?;
119 if self.seq > I_JSON_MAX_SAFE_INTEGER || self.receipt_count > I_JSON_MAX_SAFE_INTEGER {
120 return Err(ChannelError::InvalidField("channel_state_sequence"));
121 }
122 if self.cumulative_owed.units > I_JSON_MAX_SAFE_INTEGER {
123 return Err(ChannelError::InvalidField("channel_state_amount"));
124 }
125 for value in [
126 self.prev_state_digest.as_deref(),
127 self.receipt_digest.as_deref(),
128 self.receipt_authority_digest.as_deref(),
129 self.obligation_atom_digest.as_deref(),
130 self.reservation_digest.as_deref(),
131 ]
132 .into_iter()
133 .flatten()
134 {
135 validate_digest("channel_state_digest_binding", value)?;
136 }
137 if let Some(receipt_id) = self.receipt_id.as_deref() {
138 validate_text("channel_receipt_id", receipt_id)?;
139 }
140 if let Some(actual_charge) = &self.actual_charge {
141 validate_currency(&actual_charge.currency)?;
142 if actual_charge.currency != self.cumulative_owed.currency {
143 return Err(ChannelError::InvalidField("actual_charge_currency"));
144 }
145 if actual_charge.units > I_JSON_MAX_SAFE_INTEGER {
146 return Err(ChannelError::InvalidField("actual_charge_amount"));
147 }
148 }
149 let bindings = [
150 self.prev_state_digest.is_some(),
151 self.receipt_id.is_some(),
152 self.receipt_digest.is_some(),
153 self.receipt_authority_digest.is_some(),
154 self.obligation_atom_digest.is_some(),
155 self.reservation_digest.is_some(),
156 self.actual_charge.is_some(),
157 ];
158 if self.seq == 0 {
159 if self.receipt_count != 0
160 || self.receipt_id_root != empty_receipt_root()?
161 || self.cumulative_owed.units != 0
162 || self.cumulative_token_base_units != "0"
163 || bindings.into_iter().any(|present| present)
164 {
165 return Err(ChannelError::InvalidField("initial_channel_state"));
166 }
167 } else {
168 let required = [
169 self.prev_state_digest.is_some(),
170 self.receipt_id.is_some(),
171 self.receipt_digest.is_some(),
172 self.receipt_authority_digest.is_some(),
173 self.reservation_digest.is_some(),
174 self.actual_charge.is_some(),
175 ];
176 let obligation_matches_charge = self
177 .actual_charge
178 .as_ref()
179 .is_some_and(|charge| (charge.units == 0) == self.obligation_atom_digest.is_none());
180 if self.receipt_count != self.seq
181 || required.into_iter().any(|present| !present)
182 || !obligation_matches_charge
183 {
184 return Err(ChannelError::InvalidField("channel_state_binding"));
185 }
186 }
187 Ok(())
188 }
189
190 pub fn digest(&self) -> Result<String, ChannelError> {
191 self.validate()?;
192 digest(CHANNEL_STATE_BODY_DIGEST_DOMAIN, self)
193 }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase", deny_unknown_fields)]
198pub struct SignedChannelStateV1 {
199 pub body: ChannelStateBodyV1,
200 pub payee_signature: ChannelSignatureV1,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct VerifiedChannelStateV1 {
205 body: ChannelStateBodyV1,
206 payee_signature: Option<ChannelSignatureV1>,
207}
208
209impl VerifiedChannelStateV1 {
210 pub(super) fn initial(body: ChannelStateBodyV1) -> Self {
211 Self {
212 body,
213 payee_signature: None,
214 }
215 }
216
217 #[must_use]
218 pub const fn body(&self) -> &ChannelStateBodyV1 {
219 &self.body
220 }
221
222 #[must_use]
223 pub const fn payee_signature(&self) -> Option<&ChannelSignatureV1> {
224 self.payee_signature.as_ref()
225 }
226
227 pub fn digest(&self) -> Result<String, ChannelError> {
228 match &self.payee_signature {
229 Some(payee_signature) => SignedChannelStateV1 {
230 body: self.body.clone(),
231 payee_signature: payee_signature.clone(),
232 }
233 .digest(),
234 None => self.body.digest(),
235 }
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct VerifiedChannelReceiptBindingV1 {
241 channel_id: String,
242 open_digest: String,
243 reservation_digest: String,
244 sequence: u64,
245 receipt_id: String,
246 receipt_digest: String,
247 receipt_authority_digest: String,
248 receipt_timestamp_unix_ms: u64,
249 obligation_atom_id: Option<String>,
250 obligation_atom_digest: Option<String>,
251 obligation_atom: Option<ObligationAtomV1>,
252 actual_charge: MonetaryAmount,
253}
254
255impl VerifiedChannelReceiptBindingV1 {
256 #[must_use]
257 pub fn channel_id(&self) -> &str {
258 &self.channel_id
259 }
260
261 #[must_use]
262 pub fn open_digest(&self) -> &str {
263 &self.open_digest
264 }
265
266 #[must_use]
267 pub fn reservation_digest(&self) -> &str {
268 &self.reservation_digest
269 }
270
271 #[must_use]
272 pub const fn sequence(&self) -> u64 {
273 self.sequence
274 }
275
276 #[must_use]
277 pub fn receipt_id(&self) -> &str {
278 &self.receipt_id
279 }
280
281 #[must_use]
282 pub fn receipt_digest(&self) -> &str {
283 &self.receipt_digest
284 }
285
286 #[must_use]
287 pub fn receipt_authority_digest(&self) -> &str {
288 &self.receipt_authority_digest
289 }
290
291 #[must_use]
292 pub const fn receipt_timestamp_unix_ms(&self) -> u64 {
293 self.receipt_timestamp_unix_ms
294 }
295
296 #[must_use]
297 pub fn obligation_atom_id(&self) -> Option<&str> {
298 self.obligation_atom_id.as_deref()
299 }
300
301 #[must_use]
302 pub fn obligation_atom_digest(&self) -> Option<&str> {
303 self.obligation_atom_digest.as_deref()
304 }
305
306 #[must_use]
307 pub const fn obligation_atom(&self) -> Option<&ObligationAtomV1> {
308 self.obligation_atom.as_ref()
309 }
310
311 #[must_use]
312 pub const fn actual_charge(&self) -> &MonetaryAmount {
313 &self.actual_charge
314 }
315}
316
317pub fn derive_channel_receipt_authority_digest(
318 kernel_key: &PublicKey,
319) -> Result<String, ChannelError> {
320 digest(b"chio.channel.receipt-authority.digest.v1\0", kernel_key)
321}
322
323pub fn derive_channel_payee_binding_digest(
324 payee_id: &str,
325 settlement_destination_ref: &str,
326) -> Result<String, ChannelError> {
327 validate_text("channel_payee_id", payee_id)?;
328 super::validation::validate_evm_address(
329 "channel_payee_destination",
330 settlement_destination_ref,
331 )?;
332 derive_obligation_payee_binding_digest(payee_id, settlement_destination_ref)
333 .map_err(|_| ChannelError::AuthorityVerification)
334}
335
336pub fn verify_channel_receipt_binding(
337 receipt: &ChioReceipt,
338 trusted_kernel_key: &PublicKey,
339 reservation: &VerifiedAdmittedChannelReservationV1,
340 open: &VerifiedChannelOpenConsentV1,
341 obligation: Option<&ObligationAtomV1>,
342) -> Result<VerifiedChannelReceiptBindingV1, ChannelError> {
343 let reservation = reservation.artifact();
344 let intent = open.intent();
345 let open_artifact = open.artifact();
346 let receipt_digest = sha256_hex(
347 &canonical_json_bytes(receipt)
348 .map_err(|error| ChannelError::Canonicalization(error.to_string()))?,
349 );
350 let receipt_authority_digest = derive_channel_receipt_authority_digest(trusted_kernel_key)?;
351 let receipt_timestamp_unix_ms = receipt
352 .timestamp
353 .checked_mul(1_000)
354 .ok_or(ChannelError::ArithmeticOverflow)?;
355 let metadata = receipt
356 .channel_metadata()
357 .ok_or(ChannelError::AuthorityVerification)?;
358 let financial = receipt
359 .financial_metadata()
360 .ok_or(ChannelError::AuthorityVerification)?;
361 let actual_charge = MonetaryAmount {
362 units: financial.cost_charged,
363 currency: financial.currency.clone(),
364 };
365 let terminal_settlement_matches = if actual_charge.units == 0 {
366 (receipt.is_allowed() || receipt.is_denied())
367 && financial.settlement_status == SettlementStatus::NotApplicable
368 } else {
369 receipt.is_allowed() && financial.settlement_status == SettlementStatus::Pending
370 };
371 validate_currency(&actual_charge.currency)?;
372 if !metadata.is_valid()
373 || actual_charge.units > I_JSON_MAX_SAFE_INTEGER
374 || &receipt.kernel_key != trusted_kernel_key
375 || !receipt
376 .verify_signature()
377 .map_err(|_| ChannelError::AuthorityVerification)?
378 || reservation.body.receipt_authority_digest != receipt_authority_digest
379 || reservation.body.channel_id != open_artifact.body.channel_id
380 || reservation.body.open_digest != open_artifact.digest()?
381 || metadata.schema != CHIO_CHANNEL_RECEIPT_METADATA_SCHEMA
382 || metadata.channel_id != reservation.body.channel_id
383 || metadata.open_digest != reservation.body.open_digest
384 || metadata.reservation_id != reservation.body.reservation_id
385 || metadata.reservation_digest != reservation.digest()?
386 || metadata.sequence != reservation.body.next_sequence
387 || metadata.settlement_mode != ChannelSettlementModeV1::Channelized
388 || !terminal_settlement_matches
389 || financial.payment_reference.is_some()
390 || actual_charge.currency != reservation.body.maximum_charge.currency
391 || actual_charge.units > reservation.body.maximum_charge.units
392 {
393 return Err(ChannelError::AuthorityVerification);
394 }
395 let (obligation_atom_id, obligation_atom_digest) = match (actual_charge.units, obligation) {
396 (0, None) => (None, None),
397 (0, Some(_)) | (_, None) => return Err(ChannelError::AuthorityVerification),
398 (_, Some(atom)) => {
399 atom.validate()
400 .map_err(|_| ChannelError::AuthorityVerification)?;
401 let reservation_digest = reservation.digest()?;
402 let reservation_proposal_digest = reservation.body.proposal_digest()?;
403 let payee_binding_digest = derive_channel_payee_binding_digest(
404 &intent.body.payee_id,
405 &intent.body.payee_beneficiary_address,
406 )?;
407 if atom.source_receipt_id() != receipt.id
408 || atom.economic_intent_digest() != reservation_proposal_digest
409 || atom.source_receipt_digest() != receipt_digest
410 || atom.amount() != &actual_charge
411 || atom.debtor_id() != intent.body.payer_id
412 || atom.original_creditor_id() != intent.body.payee_id
413 || atom.original_settlement_destination_ref()
414 != intent.body.payee_beneficiary_address
415 || atom.payee_binding_digest() != payee_binding_digest
416 || atom.pre_action_authority_digest() != reservation_digest
417 || atom.credit_election() != &ObligationCreditElectionV1::NotCredit
418 {
419 return Err(ChannelError::AuthorityVerification);
420 }
421 (
422 Some(atom.obligation_id().to_owned()),
423 Some(
424 atom.digest()
425 .map_err(|_| ChannelError::AuthorityVerification)?,
426 ),
427 )
428 }
429 };
430 Ok(VerifiedChannelReceiptBindingV1 {
431 channel_id: metadata.channel_id,
432 open_digest: metadata.open_digest,
433 reservation_digest: metadata.reservation_digest,
434 sequence: metadata.sequence,
435 receipt_id: receipt.id.clone(),
436 receipt_digest,
437 receipt_authority_digest,
438 receipt_timestamp_unix_ms,
439 obligation_atom_id,
440 obligation_atom_digest,
441 obligation_atom: obligation.cloned(),
442 actual_charge,
443 })
444}
445
446pub fn build_channel_state_transition(
447 prior: &VerifiedChannelStateV1,
448 reservation: &VerifiedAdmittedChannelReservationV1,
449 receipt: &VerifiedChannelReceiptBindingV1,
450 open: &VerifiedChannelOpenConsentV1,
451) -> Result<ChannelStateBodyV1, ChannelError> {
452 let prior_digest = prior.digest()?;
453 let prior = prior.body();
454 let reservation = reservation.artifact();
455 let intent = open.intent();
456 prior.validate()?;
457 reservation.body.validate()?;
458 intent.body.validate()?;
459 let body = &reservation.body;
460 let sequence = next_sequence(prior.seq)?;
461 let reservation_digest = reservation.digest()?;
462 let open_digest = open.artifact().digest()?;
463 let cumulative_units = prior
464 .cumulative_owed
465 .units
466 .checked_add(receipt.actual_charge.units)
467 .filter(|units| *units <= I_JSON_MAX_SAFE_INTEGER)
468 .ok_or(ChannelError::ArithmeticOverflow)?;
469 let cumulative_owed = MonetaryAmount {
470 units: cumulative_units,
471 currency: intent.body.currency.clone(),
472 };
473 let cumulative_token_base_units = intent
474 .body
475 .asset_binding
476 .token_base_units(&cumulative_owed)?;
477 let actual_token_base_units = intent
478 .body
479 .asset_binding
480 .token_base_units(&receipt.actual_charge)?;
481 if receipt.channel_id != body.channel_id
482 || receipt.open_digest != open_digest
483 || receipt.reservation_digest != reservation_digest
484 || receipt.sequence != body.next_sequence
485 || body.channel_id != prior.channel_id
486 || body.prior_state_digest != prior_digest
487 || body.next_sequence != sequence
488 || body.receipt_authority_digest != receipt.receipt_authority_digest
489 || body.maximum_charge.currency != receipt.actual_charge.currency
490 || receipt.actual_charge.currency != intent.body.currency
491 || receipt.actual_charge.units > body.maximum_charge.units
492 || parse_base_units(&actual_token_base_units)?
493 > parse_base_units(&body.maximum_token_base_units)?
494 || cumulative_units > intent.body.bound.units
495 || parse_base_units(&cumulative_token_base_units)?
496 > parse_base_units(&intent.body.bound_token_base_units)?
497 || prior.asset_binding_digest != intent.body.asset_binding.digest()?
498 {
499 return Err(ChannelError::AuthorityVerification);
500 }
501 let state = ChannelStateBodyV1 {
502 schema: CHANNEL_STATE_SCHEMA.to_owned(),
503 channel_id: prior.channel_id.clone(),
504 seq: sequence,
505 prev_state_digest: Some(prior_digest),
506 cumulative_owed,
507 receipt_id_root: append_receipt_root(&prior.receipt_id_root, &receipt.receipt_id)?,
508 receipt_count: next_sequence(prior.receipt_count)?,
509 receipt_id: Some(receipt.receipt_id.clone()),
510 receipt_digest: Some(receipt.receipt_digest.clone()),
511 receipt_authority_digest: Some(receipt.receipt_authority_digest.clone()),
512 obligation_atom_digest: receipt.obligation_atom_digest.clone(),
513 reservation_digest: Some(reservation_digest),
514 actual_charge: Some(receipt.actual_charge.clone()),
515 cumulative_token_base_units,
516 asset_binding_digest: prior.asset_binding_digest.clone(),
517 };
518 state.validate()?;
519 Ok(state)
520}
521
522pub fn verify_channel_state_transition(
523 state: &SignedChannelStateV1,
524 prior: &VerifiedChannelStateV1,
525 reservation: &VerifiedAdmittedChannelReservationV1,
526 receipt: &VerifiedChannelReceiptBindingV1,
527 open: &VerifiedChannelOpenConsentV1,
528 trust: &ChannelOpenTrustV1,
529) -> Result<VerifiedChannelStateV1, ChannelError> {
530 let lifecycle = reservation.snapshot().lifecycle();
531 lifecycle.validate()?;
532 trust.validate()?;
533 if !trust.matches_intent(&open.intent().body) {
534 return Err(ChannelError::AuthorityVerification);
535 }
536 let prior_digest = prior.digest()?;
537 let expected_state_version = reservation
538 .artifact()
539 .body
540 .channel_state_expected_version
541 .checked_add(1)
542 .ok_or(ChannelError::ArithmeticOverflow)?;
543 let expected_fence = reservation
544 .artifact()
545 .body
546 .lifecycle_fence
547 .checked_add(1)
548 .ok_or(ChannelError::ArithmeticOverflow)?;
549 if lifecycle.status != ChannelLifecycleStatusV1::Open
550 || lifecycle.channel_id != open.artifact().body.channel_id
551 || lifecycle.latest_state_digest != prior_digest
552 || lifecycle.latest_sequence != prior.body().seq
553 || lifecycle.state_version != expected_state_version
554 || lifecycle.lifecycle_fence != expected_fence
555 || lifecycle.live_reservation_id.as_deref()
556 != Some(&reservation.artifact().body.reservation_id)
557 || lifecycle.operation_id.as_deref() != Some(&reservation.artifact().body.operation_id)
558 {
559 return Err(ChannelError::AuthorityVerification);
560 }
561 let expected = build_channel_state_transition(prior, reservation, receipt, open)?;
562 state.body.validate()?;
563 state.payee_signature.verify(
564 &state.body,
565 &trust.payee_id,
566 trust.payee_key_epoch,
567 &trust.payee_key,
568 )?;
569 if state.body != expected {
570 return Err(ChannelError::AuthorityVerification);
571 }
572 Ok(VerifiedChannelStateV1 {
573 body: state.body.clone(),
574 payee_signature: Some(state.payee_signature.clone()),
575 })
576}
577
578pub(super) fn verify_retained_signed_channel_state(
579 state: &SignedChannelStateV1,
580 open: &VerifiedChannelOpenConsentV1,
581 trust: &ChannelOpenTrustV1,
582) -> Result<VerifiedChannelStateV1, ChannelError> {
583 state.body.validate()?;
584 trust.validate()?;
585 state.payee_signature.verify(
586 &state.body,
587 &trust.payee_id,
588 trust.payee_key_epoch,
589 &trust.payee_key,
590 )?;
591 let intent = &open.intent().body;
592 if !trust.matches_intent(intent)
593 || state.body.seq == 0
594 || state.body.channel_id != open.artifact().body.channel_id
595 || state.body.cumulative_owed.currency != intent.currency
596 || state.body.cumulative_owed.units > intent.bound.units
597 || state.body.asset_binding_digest != intent.asset_binding.digest()?
598 || parse_base_units(&state.body.cumulative_token_base_units)?
599 > parse_base_units(&intent.bound_token_base_units)?
600 {
601 return Err(ChannelError::AuthorityVerification);
602 }
603 intent.asset_binding.verify_round_trip(
604 &state.body.cumulative_owed,
605 &state.body.cumulative_token_base_units,
606 )?;
607 Ok(VerifiedChannelStateV1 {
608 body: state.body.clone(),
609 payee_signature: Some(state.payee_signature.clone()),
610 })
611}
612
613impl SignedChannelStateV1 {
614 pub fn digest(&self) -> Result<String, ChannelError> {
615 self.body.validate()?;
616 digest(CHANNEL_STATE_DIGEST_DOMAIN, self)
617 }
618}
619
620pub fn empty_receipt_root() -> Result<String, ChannelError> {
621 digest(CHANNEL_RECEIPT_ROOT_DOMAIN, &Vec::<String>::new())
622}
623
624pub fn append_receipt_root(prior_root: &str, receipt_id: &str) -> Result<String, ChannelError> {
625 validate_digest("prior_receipt_root", prior_root)?;
626 validate_text("channel_receipt_id", receipt_id)?;
627 digest(CHANNEL_RECEIPT_ROOT_DOMAIN, &(prior_root, receipt_id))
628}
629
630pub(super) fn next_sequence(value: u64) -> Result<u64, ChannelError> {
631 value
632 .checked_add(1)
633 .filter(|value| *value <= I_JSON_MAX_SAFE_INTEGER)
634 .ok_or(ChannelError::ArithmeticOverflow)
635}