dimp 2.2.2__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. dimp-2.3.0/LICENSE +21 -0
  2. dimp-2.3.0/PKG-INFO +248 -0
  3. {dimp-2.2.2 → dimp-2.3.0}/dimp/__init__.py +8 -0
  4. {dimp-2.2.2 → dimp-2.3.0}/dimp/crypto/__init__.py +9 -0
  5. dimp-2.3.0/dimp/crypto/algorithms.py +57 -0
  6. {dimp-2.2.2 → dimp-2.3.0}/dimp/crypto/pnf.py +3 -1
  7. {dimp-2.2.2 → dimp-2.3.0}/dimp/crypto/ted.py +11 -10
  8. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/base.py +9 -8
  9. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/commands.py +24 -25
  10. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/contents.py +19 -18
  11. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/files.py +6 -7
  12. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/group_admins.py +8 -8
  13. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/groups.py +7 -7
  14. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/money.py +7 -7
  15. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/quote.py +8 -7
  16. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/receipt.py +1 -1
  17. {dimp-2.2.2 → dimp-2.3.0}/dimp/mkm/__init__.py +3 -0
  18. {dimp-2.2.2 → dimp-2.3.0}/dimp/mkm/docs.py +5 -5
  19. {dimp-2.2.2 → dimp-2.3.0}/dimp/mkm/document.py +14 -13
  20. {dimp-2.2.2 → dimp-2.3.0}/dimp/mkm/meta.py +9 -9
  21. {dimp-2.2.2 → dimp-2.3.0}/dimp/msg/base.py +1 -1
  22. {dimp-2.2.2 → dimp-2.3.0}/dimp/msg/envelope.py +3 -3
  23. {dimp-2.2.2 → dimp-2.3.0}/dimp/msg/instant.py +1 -1
  24. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/__init__.py +3 -0
  25. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/commands.py +14 -14
  26. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/contents.py +8 -44
  27. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/files.py +11 -10
  28. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/groups.py +8 -7
  29. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/money.py +6 -5
  30. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/quote.py +3 -21
  31. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/receipt.py +1 -1
  32. dimp-2.3.0/dimp/protocol/types.py +177 -0
  33. dimp-2.3.0/dimp.egg-info/PKG-INFO +248 -0
  34. {dimp-2.2.2 → dimp-2.3.0}/dimp.egg-info/SOURCES.txt +4 -1
  35. dimp-2.3.0/dimp.egg-info/requires.txt +2 -0
  36. {dimp-2.2.2 → dimp-2.3.0}/setup.py +3 -3
  37. dimp-2.2.2/PKG-INFO +0 -248
  38. dimp-2.2.2/dimp.egg-info/PKG-INFO +0 -248
  39. dimp-2.2.2/dimp.egg-info/requires.txt +0 -2
  40. {dimp-2.2.2 → dimp-2.3.0}/README.md +0 -0
  41. {dimp-2.2.2 → dimp-2.3.0}/dimp/crypto/keys.py +0 -0
  42. {dimp-2.2.2 → dimp-2.3.0}/dimp/dkd/__init__.py +0 -0
  43. {dimp-2.2.2 → dimp-2.3.0}/dimp/msg/__init__.py +0 -0
  44. {dimp-2.2.2 → dimp-2.3.0}/dimp/msg/reliable.py +0 -0
  45. {dimp-2.2.2 → dimp-2.3.0}/dimp/msg/secure.py +0 -0
  46. {dimp-2.2.2 → dimp-2.3.0}/dimp/plugins/__init__.py +0 -0
  47. {dimp-2.2.2 → dimp-2.3.0}/dimp/plugins/cmd.py +0 -0
  48. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/docs.py +0 -0
  49. {dimp-2.2.2 → dimp-2.3.0}/dimp/protocol/helpers.py +0 -0
  50. {dimp-2.2.2 → dimp-2.3.0}/dimp.egg-info/dependency_links.txt +0 -0
  51. {dimp-2.2.2 → dimp-2.3.0}/dimp.egg-info/top_level.txt +0 -0
  52. {dimp-2.2.2 → dimp-2.3.0}/setup.cfg +0 -0
dimp-2.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Albert Moky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
dimp-2.3.0/PKG-INFO ADDED
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.1
2
+ Name: dimp
3
+ Version: 2.3.0
4
+ Summary: Decentralized Instant Messaging Protocol
5
+ Home-page: https://github.com/dimchat/core-py
6
+ Author: Albert Moky
7
+ Author-email: albert.moky@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+
15
+ # Decentralized Instant Messaging Protocol (Python)
16
+
17
+ [![License](https://img.shields.io/github/license/dimchat/core-py)](https://github.com/dimchat/core-py/blob/master/LICENSE)
18
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/dimchat/core-py/pulls)
19
+ [![Platform](https://img.shields.io/badge/Platform-Python%203-brightgreen.svg)](https://github.com/dimchat/core-py/wiki)
20
+ [![Issues](https://img.shields.io/github/issues/dimchat/core-py)](https://github.com/dimchat/core-py/issues)
21
+ [![Repo Size](https://img.shields.io/github/repo-size/dimchat/core-py)](https://github.com/dimchat/core-py/archive/refs/heads/main.zip)
22
+ [![Tags](https://img.shields.io/github/tag/dimchat/core-py)](https://github.com/dimchat/core-py/tags)
23
+ [![Version](https://img.shields.io/pypi/v/dimp)](https://pypi.org/project/dimp)
24
+
25
+ [![Watchers](https://img.shields.io/github/watchers/dimchat/core-py)](https://github.com/dimchat/core-py/watchers)
26
+ [![Forks](https://img.shields.io/github/forks/dimchat/core-py)](https://github.com/dimchat/core-py/forks)
27
+ [![Stars](https://img.shields.io/github/stars/dimchat/core-py)](https://github.com/dimchat/core-py/stargazers)
28
+ [![Followers](https://img.shields.io/github/followers/dimchat)](https://github.com/orgs/dimchat/followers)
29
+
30
+ ## Dependencies
31
+
32
+ | Name | Version | Description |
33
+ |------|---------|-------------|
34
+ | [Ming Ke Ming (名可名)](https://github.com/dimchat/mkm-py) | [![Version](https://img.shields.io/pypi/v/mkm)](https://pypi.org/project/mkm) | Decentralized User Identity Authentication |
35
+ | [Dao Ke Dao (道可道)](https://github.com/dimchat/dkd-py) | [![Version](https://img.shields.io/pypi/v/dkd)](https://pypi.org/project/dkd) | Universal Message Module |
36
+
37
+ ## Examples
38
+
39
+ ### extends Command
40
+
41
+ * _Handshake Command Protocol_
42
+ 0. (C-S) handshake start
43
+ 1. (S-C) handshake again with new session
44
+ 2. (C-S) handshake restart with new session
45
+ 3. (S-C) handshake success
46
+
47
+ ```python
48
+ from enum import IntEnum
49
+ from typing import Optional, Any, Dict
50
+
51
+ from dimp import Command, BaseCommand
52
+
53
+
54
+ class HandshakeState(IntEnum):
55
+ Start = 0 # C -> S, without session key(or session expired)
56
+ Again = 1 # S -> C, with new session key
57
+ Restart = 2 # C -> S, with new session key
58
+ Success = 3 # S -> C, handshake accepted
59
+
60
+
61
+ class HandshakeCommand(BaseCommand):
62
+ """
63
+ Handshake Command
64
+ ~~~~~~~~~~~~~~~~~
65
+
66
+ data format: {
67
+ type : 0x88,
68
+ sn : 123,
69
+
70
+ command : "handshake", // command name
71
+ title : "Hello world!", // "DIM?", "DIM!"
72
+ session : "{SESSION_ID}", // session key
73
+ }
74
+ """
75
+ HANDSHAKE = 'handshake'
76
+
77
+ def __init__(self, content: Dict[str, Any] = None, title: str = None, session: str = None):
78
+ if content is None:
79
+ # 1. new command with title & session key
80
+ assert title is not None, 'handshake command error: %s' % session
81
+ cmd = self.HANDSHAKE
82
+ super().__init__(cmd=cmd)
83
+ self['title'] = title
84
+ self['message'] = title # TODO: remove after all clients upgraded
85
+ if session is not None:
86
+ self['session'] = session
87
+ else:
88
+ # 2. command info from network
89
+ assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
90
+ super().__init__(content)
91
+
92
+ @property
93
+ def title(self) -> str:
94
+ # TODO: modify after all clients upgraded
95
+ text = self.get_str(key='title', default=None)
96
+ if text is None:
97
+ # compatible with v1.0
98
+ text = self.get_str(key='message', default='')
99
+ return text
100
+
101
+ @property
102
+ def session(self) -> Optional[str]:
103
+ return self.get_str(key='session', default=None)
104
+
105
+ @property
106
+ def state(self) -> HandshakeState:
107
+ return handshake_state(title=self.title, session=self.session)
108
+
109
+ @classmethod
110
+ def offer(cls, session: str = None) -> Command:
111
+ """
112
+ Create client-station handshake offer
113
+
114
+ :param session: Old session key
115
+ :return: HandshakeCommand object
116
+ """
117
+ return HandshakeCommand(title='Hello world!', session=session)
118
+
119
+ @classmethod
120
+ def ask(cls, session: str) -> Command:
121
+ """
122
+ Create station-client handshake again with new session
123
+
124
+ :param session: New session key
125
+ :return: HandshakeCommand object
126
+ """
127
+ return HandshakeCommand(title='DIM?', session=session)
128
+
129
+ @classmethod
130
+ def accepted(cls, session: str = None) -> Command:
131
+ """
132
+ Create station-client handshake success notice
133
+
134
+ :return: HandshakeCommand object
135
+ """
136
+ return HandshakeCommand(title='DIM!', session=session)
137
+
138
+ start = offer # (1. C->S) first handshake, without session
139
+ again = ask # (2. S->C) ask client to handshake with new session key
140
+ restart = offer # (3. C->S) handshake with new session key
141
+ success = accepted # (4. S->C) notice the client that handshake accepted
142
+
143
+
144
+ def handshake_state(title: str, session: str = None) -> HandshakeState:
145
+ # Server -> Client
146
+ if title == 'DIM!': # or title == 'OK!':
147
+ return HandshakeState.Success
148
+ if title == 'DIM?':
149
+ return HandshakeState.Again
150
+ # Client -> Server: "Hello world!"
151
+ if session is None or len(session) == 0:
152
+ return HandshakeState.Start
153
+ else:
154
+ return HandshakeState.Restart
155
+ ```
156
+
157
+ ### extends Content
158
+
159
+ ```python
160
+ from abc import ABC, abstractmethod
161
+ from typing import Any, Dict
162
+
163
+ from dimp import ContentType
164
+ from dimp import Content
165
+ from dimp import BaseContent
166
+
167
+
168
+ class CustomizedContent(Content, ABC):
169
+ """
170
+ Application Customized message
171
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
172
+
173
+ data format: {
174
+ type : 0xCC,
175
+ sn : 123,
176
+
177
+ app : "{APP_ID}", // application (e.g.: "chat.dim.sechat")
178
+ mod : "{MODULE}", // module name (e.g.: "drift_bottle")
179
+ act : "{ACTION}", // action name (e.g.: "throw")
180
+ extra : info // action parameters
181
+ }
182
+ """
183
+
184
+ @property
185
+ @abstractmethod
186
+ def application(self) -> str:
187
+ """ App ID """
188
+ raise NotImplemented
189
+
190
+ @property
191
+ @abstractmethod
192
+ def module(self) -> str:
193
+ """ Module Name """
194
+ raise NotImplemented
195
+
196
+ @property
197
+ @abstractmethod
198
+ def action(self) -> str:
199
+ """ Action Name """
200
+ raise NotImplemented
201
+
202
+ @classmethod
203
+ def create(cls, app: str, mod: str, act: str):
204
+ return AppCustomizedContent(app=app, mod=mod, act=act)
205
+
206
+
207
+ class AppCustomizedContent(BaseContent, CustomizedContent):
208
+
209
+ def __init__(self, content: Dict[str, Any] = None,
210
+ msg_type: int = None,
211
+ app: str = None, mod: str = None, act: str = None):
212
+ if content is None:
213
+ # 1. new content with type, application, module & action
214
+ assert app is not None and mod is not None and act is not None, \
215
+ 'customized content error: %s, %s, %s, %s' % (msg_type, app, mod, act)
216
+ if msg_type is None:
217
+ msg_type = ContentType.CUSTOMIZED.value
218
+ super().__init__(None, msg_type)
219
+ self['app'] = app
220
+ self['mod'] = mod
221
+ self['act'] = act
222
+ else:
223
+ # 2. content info from network
224
+ assert msg_type is None and app is None and mod is None and act is None, \
225
+ 'params error: %s, %s, %s, %s, %s' % (content, msg_type, app, mod, act)
226
+ super().__init__(content)
227
+
228
+ @property # Override
229
+ def application(self) -> str:
230
+ return self.get_str(key='app', default='')
231
+
232
+ @property # Override
233
+ def module(self) -> str:
234
+ return self.get_str(key='mod', default='')
235
+
236
+ @property # Override
237
+ def action(self) -> str:
238
+ return self.get_str(key='act', default='')
239
+ ```
240
+
241
+ ### extends ID Address
242
+
243
+ * Examples in [dim plugins](https://pypi.org/project/dimplugins)
244
+
245
+ ----
246
+
247
+ Copyright © 2018 Albert Moky
248
+ [![Followers](https://img.shields.io/github/followers/moky)](https://github.com/moky?tab=followers)
@@ -102,6 +102,12 @@ __all__ = [
102
102
  'BaseDataWrapper',
103
103
  'BaseFileWrapper',
104
104
 
105
+ #
106
+ # Algorithm
107
+ #
108
+ 'AsymmetricAlgorithms', 'SymmetricAlgorithms',
109
+ 'EncodeAlgorithms',
110
+
105
111
  #
106
112
  # MingKeMing
107
113
  #
@@ -125,6 +131,8 @@ __all__ = [
125
131
  'BaseMeta',
126
132
  'BaseDocument', 'BaseVisa', 'BaseBulletin',
127
133
 
134
+ 'MetaType', 'DocumentType',
135
+
128
136
  #
129
137
  # DaoKeDao
130
138
  #
@@ -30,6 +30,9 @@
30
30
 
31
31
  from mkm.crypto import *
32
32
 
33
+ from .algorithms import AsymmetricAlgorithms, SymmetricAlgorithms
34
+ from .algorithms import EncodeAlgorithms
35
+
33
36
  from .keys import BaseKey, BaseSymmetricKey
34
37
  from .keys import BaseAsymmetricKey, BasePublicKey, BasePrivateKey
35
38
 
@@ -57,6 +60,12 @@ __all__ = [
57
60
 
58
61
  'SymmetricKeyFactory', 'PrivateKeyFactory', 'PublicKeyFactory',
59
62
 
63
+ #
64
+ # Algorithm
65
+ #
66
+ 'AsymmetricAlgorithms', 'SymmetricAlgorithms',
67
+ 'EncodeAlgorithms',
68
+
60
69
  #
61
70
  # Base Keys
62
71
  #
@@ -0,0 +1,57 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # DIMP : Decentralized Instant Messaging Protocol
4
+ #
5
+ # Written in 2025 by Moky <albert.moky@gmail.com>
6
+ #
7
+ # ==============================================================================
8
+ # MIT License
9
+ #
10
+ # Copyright (c) 2025 Albert Moky
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ # of this software and associated documentation files (the "Software"), to deal
14
+ # in the Software without restriction, including without limitation the rights
15
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ # copies of the Software, and to permit persons to whom the Software is
17
+ # furnished to do so, subject to the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be included in all
20
+ # copies or substantial portions of the Software.
21
+ #
22
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ # SOFTWARE.
29
+ # ==============================================================================
30
+
31
+
32
+ class AsymmetricAlgorithms:
33
+ """ Algorithms for Asymmetric Key """
34
+
35
+ RSA = 'RSA' # -- "RSA/ECB/PKCS1Padding", "SHA256withRSA"
36
+ ECC = 'ECC'
37
+
38
+
39
+ class SymmetricAlgorithms:
40
+ """ Algorithms for Symmetric Key """
41
+
42
+ AES = 'AES' # -- "AES/CBC/PKCS7Padding"
43
+ DES = 'DES'
44
+
45
+ # Symmetric key algorithm for broadcast message,
46
+ # which will do nothing when en/decoding message data
47
+ PLAIN = 'PLAIN'
48
+
49
+
50
+ class EncodeAlgorithms:
51
+ """ Algorithms for Encoding Data """
52
+
53
+ DEFAULT = 'base64'
54
+
55
+ BASE_64 = 'base64'
56
+ BASE_58 = 'base58'
57
+ HEX = 'hex'
@@ -35,6 +35,8 @@ from mkm.types import URI
35
35
  from mkm.types import Dictionary
36
36
  from mkm.crypto import DecryptKey, SymmetricKey
37
37
 
38
+ from .algorithms import EncodeAlgorithms
39
+
38
40
 
39
41
  class BaseFileWrapper(Dictionary):
40
42
  """
@@ -90,7 +92,7 @@ class BaseFileWrapper(Dictionary):
90
92
  ted = None
91
93
  self.pop('data', None)
92
94
  else:
93
- ted = TransportableData.create(data=binary)
95
+ ted = TransportableData.create(data=binary, algorithm=EncodeAlgorithms.DEFAULT)
94
96
  self['data'] = ted.object
95
97
  self.__attachment = ted
96
98
 
@@ -33,9 +33,10 @@ from typing import Optional, Any, Dict
33
33
  from mkm.format import base64_encode, base64_decode
34
34
  from mkm.format import base58_encode, base58_decode
35
35
  from mkm.format import hex_encode, hex_decode
36
- from mkm.format import TransportableData
37
36
  from mkm.types import Dictionary
38
37
 
38
+ from .algorithms import EncodeAlgorithms
39
+
39
40
 
40
41
  class BaseDataWrapper(Dictionary):
41
42
  """
@@ -72,7 +73,7 @@ class BaseDataWrapper(Dictionary):
72
73
  if len(text) == 0:
73
74
  return text
74
75
  alg = self.get_str(key='algorithm', default='')
75
- if alg == TransportableData.DEFAULT:
76
+ if alg == EncodeAlgorithms.DEFAULT:
76
77
  alg = ''
77
78
  if len(alg) == 0:
78
79
  # 0. "{BASE64_ENCODE}"
@@ -99,12 +100,12 @@ class BaseDataWrapper(Dictionary):
99
100
  def algorithm(self) -> str:
100
101
  alg = self.get_str(key='algorithm', default='')
101
102
  if len(alg) == 0:
102
- alg = TransportableData.DEFAULT
103
+ alg = EncodeAlgorithms.DEFAULT
103
104
  return alg
104
105
 
105
106
  @algorithm.setter
106
107
  def algorithm(self, name: str):
107
- if name is None: # or name == TransportableData.DEFAULT:
108
+ if name is None: # or name == EncodeAlgorithms.DEFAULT:
108
109
  self.pop('algorithm', None)
109
110
  else:
110
111
  self['algorithm'] = name
@@ -120,11 +121,11 @@ class BaseDataWrapper(Dictionary):
120
121
  text = self.get_str(key='data', default='')
121
122
  if len(text) > 0:
122
123
  alg = self.algorithm
123
- if alg == TransportableData.BASE_64:
124
+ if alg == EncodeAlgorithms.BASE_64:
124
125
  binary = base64_decode(string=text)
125
- elif alg == TransportableData.BASE_58:
126
+ elif alg == EncodeAlgorithms.BASE_58:
126
127
  binary = base58_decode(string=text)
127
- elif alg == TransportableData.HEX:
128
+ elif alg == EncodeAlgorithms.HEX:
128
129
  binary = hex_decode(string=text)
129
130
  else:
130
131
  assert False, 'data algorithm not support: %s' % alg
@@ -137,11 +138,11 @@ class BaseDataWrapper(Dictionary):
137
138
  self.pop('data', None)
138
139
  else:
139
140
  alg = self.algorithm
140
- if alg == TransportableData.BASE_64:
141
+ if alg == EncodeAlgorithms.BASE_64:
141
142
  text = base64_encode(data=binary)
142
- elif alg == TransportableData.BASE_58:
143
+ elif alg == EncodeAlgorithms.BASE_58:
143
144
  text = base58_encode(data=binary)
144
- elif alg == TransportableData.HEX:
145
+ elif alg == EncodeAlgorithms.HEX:
145
146
  text = hex_encode(data=binary)
146
147
  else:
147
148
  assert False, 'data algorithm not support: %s' % alg
@@ -33,10 +33,11 @@ from typing import Optional, Any, Dict
33
33
  from mkm.types import DateTime
34
34
  from mkm.types import Dictionary
35
35
  from mkm import ID
36
- from dkd import ContentType, Content
36
+ from dkd import Content
37
37
  from dkd import InstantMessage
38
38
  from dkd.plugins import SharedMessageExtensions
39
39
 
40
+ from ..protocol import ContentType
40
41
  from ..protocol import Command
41
42
 
42
43
  from ..plugins import SharedCommandExtensions
@@ -52,11 +53,11 @@ from ..plugins import SharedCommandExtensions
52
53
 
53
54
  class BaseContent(Dictionary, Content):
54
55
 
55
- def __init__(self, content: Dict[str, Any] = None, msg_type: int = None):
56
+ def __init__(self, content: Dict[str, Any] = None, msg_type: str = None):
56
57
  # check parameters
57
58
  if content is None:
58
59
  # 1. new content with type
59
- assert msg_type is not None and msg_type > 0, 'content type error: %s' % msg_type
60
+ assert msg_type is not None and len(msg_type) > 0, 'content type error: %s' % msg_type
60
61
  time = DateTime.now()
61
62
  sn = InstantMessage.generate_serial_number(msg_type, time)
62
63
  content = {
@@ -77,11 +78,11 @@ class BaseContent(Dictionary, Content):
77
78
  self.__time = time
78
79
 
79
80
  @property # Override
80
- def type(self) -> int:
81
+ def type(self) -> str:
81
82
  """ message content type: text, image, ... """
82
83
  if self.__type is None:
83
84
  ext = SharedMessageExtensions()
84
- self.__type = ext.helper.get_content_type(content=self.dictionary, default=0)
85
+ self.__type = ext.helper.get_content_type(content=self.dictionary, default='')
85
86
  # self.__type = self.get_int(key='type', default=0)
86
87
  return self.__type
87
88
 
@@ -114,18 +115,18 @@ class BaseContent(Dictionary, Content):
114
115
 
115
116
  class BaseCommand(BaseContent, Command):
116
117
 
117
- def __init__(self, content: Dict[str, Any] = None, msg_type: int = None, cmd: str = None):
118
+ def __init__(self, content: Dict[str, Any] = None, msg_type: str = None, cmd: str = None):
118
119
  # check parameters
119
120
  if content is None:
120
121
  # 1. new command with type & name
121
122
  if msg_type is None:
122
- msg_type = ContentType.COMMAND.value
123
+ msg_type = ContentType.COMMAND
123
124
  assert cmd is not None and len(cmd) > 0, 'command name should not empty'
124
125
  super().__init__(None, msg_type)
125
126
  self['command'] = cmd
126
127
  else:
127
128
  # 2. command info from network
128
- assert msg_type is None and cmd is None, 'params error: %d, %s' % (msg_type, cmd)
129
+ assert msg_type is None and cmd is None, 'params error: %s, %s' % (msg_type, cmd)
129
130
  super().__init__(content)
130
131
 
131
132
  @property # Override
@@ -32,11 +32,11 @@
32
32
  Meta Command Protocol
33
33
  ~~~~~~~~~~~~~~~~~~~~~
34
34
 
35
- 1. contains 'ID' only, means query meta for ID
35
+ 1. contains 'did' only, means query meta for ID
36
36
  2. contains 'meta' (must match), means reply
37
37
  """
38
38
 
39
- from typing import Optional, Any, Dict
39
+ from typing import Optional, Any, List, Dict
40
40
 
41
41
  from mkm.types import DateTime
42
42
  from mkm import ID, Meta, Document
@@ -52,11 +52,11 @@ class BaseMetaCommand(BaseCommand, MetaCommand):
52
52
  ~~~~~~~~~~~~
53
53
 
54
54
  data format: {
55
- type : 0x88,
55
+ type : i2s(0x88),
56
56
  sn : 123,
57
57
 
58
58
  command : "meta", // command name
59
- ID : "{ID}", // contact's ID
59
+ did : "{ID}", // contact's ID
60
60
  meta : {...} // When meta is empty, means query meta for ID
61
61
  }
62
62
  """
@@ -71,7 +71,7 @@ class BaseMetaCommand(BaseCommand, MetaCommand):
71
71
  if cmd is None:
72
72
  cmd = Command.META
73
73
  super().__init__(cmd=cmd)
74
- self.set_string(key='ID', value=identifier)
74
+ self.set_string(key='did', value=identifier)
75
75
  if meta is not None:
76
76
  self.set_map(key='meta', value=meta)
77
77
  else:
@@ -89,7 +89,7 @@ class BaseMetaCommand(BaseCommand, MetaCommand):
89
89
  @property # Override
90
90
  def identifier(self) -> ID:
91
91
  if self.__id is None:
92
- self.__id = ID.parse(identifier=self.get('ID'))
92
+ self.__id = ID.parse(identifier=self.get('did'))
93
93
  return self.__id
94
94
 
95
95
  #
@@ -108,14 +108,14 @@ class BaseDocumentCommand(BaseMetaCommand, DocumentCommand):
108
108
  ~~~~~~~~~~~~~~~~
109
109
 
110
110
  data format: {
111
- type : 0x88,
111
+ type : i2s(0x88),
112
112
  sn : 123,
113
113
 
114
114
  command : "document", // command name
115
- ID : "{ID}", // entity ID
115
+ did : "{ID}", // entity ID
116
116
  meta : {...}, // only for handshaking with new friend
117
- document : {...}, // when document is empty, means query for ID
118
- signature : "..." // old document's signature for querying
117
+ documents : [...], // when document is empty, means query for ID
118
+ last_time : 12345 // old document time for querying
119
119
  }
120
120
 
121
121
  """
@@ -123,38 +123,37 @@ class BaseDocumentCommand(BaseMetaCommand, DocumentCommand):
123
123
  def __init__(self, content: Dict[str, Any] = None,
124
124
  identifier: ID = None,
125
125
  meta: Optional[Meta] = None,
126
- document: Optional[Document] = None,
126
+ documents: List[Document] = None,
127
127
  last_time: Optional[DateTime] = None):
128
128
  if content is None:
129
129
  # 1. new command with ID, meta, document & signature
130
- if identifier is None:
131
- assert document is not None, 'document command error: %s, %s' % (meta, last_time)
132
- identifier = document.identifier
133
- assert identifier is not None, 'document command error: %s, %s, %s' % (meta, document, last_time)
134
- cmd = Command.DOCUMENT
130
+ assert identifier is not None, 'document command error: %s, %s, %s' % (meta, documents, last_time)
131
+ cmd = Command.DOCUMENTS
135
132
  super().__init__(cmd=cmd, identifier=identifier, meta=meta)
136
133
  # respond with document info
137
- if document is not None:
138
- self['document'] = document.dictionary
134
+ if documents is not None:
135
+ self['documents'] = Document.revert(documents=documents)
139
136
  # query with last document time
140
137
  if last_time is not None:
141
138
  self.set_datetime(key='last_time', value=last_time)
142
139
  else:
143
140
  # 2. command info from network
144
- assert identifier is None and meta is None and document is None and last_time is None, \
145
- 'params error: %s, %s, %s, %s, %s' % (content, identifier, meta, document, last_time)
141
+ assert identifier is None and meta is None and documents is None and last_time is None, \
142
+ 'params error: %s, %s, %s, %s, %s' % (content, identifier, meta, documents, last_time)
146
143
  super().__init__(content)
147
144
  # lazy load
148
- self.__doc = document
145
+ self.__docs = documents
149
146
 
150
147
  #
151
148
  # document
152
149
  #
153
150
  @property # Override
154
- def document(self) -> Optional[Document]:
155
- if self.__doc is None:
156
- self.__doc = Document.parse(document=self.get('document'))
157
- return self.__doc
151
+ def documents(self) -> Optional[List[Document]]:
152
+ if self.__docs is None:
153
+ docs = self.get('documents')
154
+ if docs is not None:
155
+ self.__docs = Document.convert(array=docs)
156
+ return self.__docs
158
157
 
159
158
  @property # Override
160
159
  def last_time(self) -> Optional[DateTime]: