33import logging
44
55from ably .util .crypto import CipherData
6+ from ably .util .exceptions import AblyException
67
78
89log = logging .getLogger (__name__ )
910
11+ ENC_VCDIFF = "vcdiff"
12+
13+
14+ class DeltaExtras :
15+ def __init__ (self , extras ):
16+ self .from_id = None
17+ if extras and 'delta' in extras :
18+ delta_info = extras ['delta' ]
19+ if isinstance (delta_info , dict ):
20+ self .from_id = delta_info .get ('from' )
21+
22+
23+ class DecodingContext :
24+ def __init__ (self , base_payload = None , last_message_id = None , vcdiff_decoder = None ):
25+ self .base_payload = base_payload
26+ self .last_message_id = last_message_id
27+ self .vcdiff_decoder = vcdiff_decoder
28+
1029
1130class EncodeDataMixin :
1231
@@ -25,10 +44,12 @@ def encoding(self, encoding):
2544 self ._encoding_array = encoding .strip ('/' ).split ('/' )
2645
2746 @staticmethod
28- def decode (data , encoding = '' , cipher = None ):
47+ def decode (data , encoding = '' , cipher = None , context = None ):
2948 encoding = encoding .strip ('/' )
3049 encoding_list = encoding .split ('/' )
3150
51+ last_payload = data
52+
3253 while encoding_list :
3354 encoding = encoding_list .pop ()
3455 if not encoding :
@@ -46,10 +67,43 @@ def decode(data, encoding='', cipher=None):
4667 if isinstance (data , list ) or isinstance (data , dict ):
4768 continue
4869 data = json .loads (data )
49- elif encoding == 'base64' and isinstance (data , bytes ):
50- data = bytearray (base64 .b64decode (data ))
5170 elif encoding == 'base64' :
52- data = bytearray (base64 .b64decode (data .encode ('utf-8' )))
71+ data = bytearray (base64 .b64decode (data )) if isinstance (data , bytes ) \
72+ else bytearray (base64 .b64decode (data .encode ('utf-8' )))
73+ if not encoding_list :
74+ last_payload = data
75+ elif encoding == ENC_VCDIFF :
76+ if not context or not context .vcdiff_decoder :
77+ log .error ('Message cannot be decoded as no VCDiff decoder available' )
78+ raise AblyException ('VCDiff decoder not available' , 40019 , 40019 )
79+
80+ if not context .base_payload :
81+ log .error ('VCDiff decoding requires base payload' )
82+ raise AblyException ('VCDiff decode failure' , 40018 , 40018 )
83+
84+ try :
85+ # Convert base payload to bytes if it's a string
86+ base_data = context .base_payload
87+ if isinstance (base_data , str ):
88+ base_data = base_data .encode ('utf-8' )
89+ else :
90+ base_data = bytes (base_data )
91+
92+ # Convert delta to bytes if needed
93+ delta_data = data
94+ if isinstance (delta_data , (bytes , bytearray )):
95+ delta_data = bytes (delta_data )
96+ else :
97+ delta_data = str (delta_data ).encode ('utf-8' )
98+
99+ # Decode with VCDiff
100+ data = bytearray (context .vcdiff_decoder .decode (delta_data , base_data ))
101+ last_payload = data
102+
103+ except Exception as e :
104+ log .error (f'VCDiff decode failed: { e } ' )
105+ raise AblyException ('VCDiff decode failure' , 40018 , 40018 )
106+
53107 elif encoding .startswith ('%s+' % CipherData .ENCODING_ID ):
54108 if not cipher :
55109 log .error ('Message cannot be decrypted as the channel is '
@@ -67,9 +121,11 @@ def decode(data, encoding='', cipher=None):
67121 encoding_list .append (encoding )
68122 break
69123
124+ if context :
125+ context .base_payload = last_payload
70126 encoding = '/' .join (encoding_list )
71127 return {'encoding' : encoding , 'data' : data }
72128
73129 @classmethod
74- def from_encoded_array (cls , objs , cipher = None ):
75- return [cls .from_encoded (obj , cipher = cipher ) for obj in objs ]
130+ def from_encoded_array (cls , objs , cipher = None , context = None ):
131+ return [cls .from_encoded (obj , cipher = cipher , context = context ) for obj in objs ]
0 commit comments