bmobject.py
1 """ 2 BMObject and it's exceptions. 3 """ 4 import logging 5 import time 6 7 import protocol 8 import state 9 from network import dandelion_ins 10 from highlevelcrypto import calculateInventoryHash 11 12 logger = logging.getLogger('default') 13 14 15 class BMObjectInsufficientPOWError(Exception): 16 """Exception indicating the object 17 doesn't have sufficient proof of work.""" 18 errorCodes = ("Insufficient proof of work") 19 20 21 class BMObjectExpiredError(Exception): 22 """Exception indicating the object's lifetime has expired.""" 23 errorCodes = ("Object expired") 24 25 26 class BMObjectUnwantedStreamError(Exception): 27 """Exception indicating the object is in a stream 28 we didn't advertise as being interested in.""" 29 errorCodes = ("Object in unwanted stream") 30 31 32 class BMObjectInvalidError(Exception): 33 """The object's data does not match object specification.""" 34 errorCodes = ("Invalid object") 35 36 37 class BMObjectAlreadyHaveError(Exception): 38 """We received a duplicate object (one we already have)""" 39 errorCodes = ("Already have this object") 40 41 42 class BMObject(object): 43 """Bitmessage Object as a class.""" 44 45 # max TTL, 28 days and 3 hours 46 maxTTL = 28 * 24 * 60 * 60 + 10800 47 # min TTL, 3 hour (in the past 48 minTTL = -3600 49 50 def __init__( 51 self, 52 nonce, 53 expiresTime, 54 objectType, 55 version, 56 streamNumber, 57 data, 58 payloadOffset 59 ): 60 self.nonce = nonce 61 self.expiresTime = expiresTime 62 self.objectType = objectType 63 self.version = version 64 self.streamNumber = streamNumber 65 self.inventoryHash = calculateInventoryHash(data) 66 # copy to avoid memory issues 67 self.data = bytearray(data) 68 self.tag = self.data[payloadOffset:payloadOffset + 32] 69 70 def checkProofOfWorkSufficient(self): 71 """Perform a proof of work check for sufficiency.""" 72 # Let us check to make sure that the proof of work is sufficient. 73 if not protocol.isProofOfWorkSufficient(self.data): 74 logger.info('Proof of work is insufficient.') 75 raise BMObjectInsufficientPOWError() 76 77 def checkEOLSanity(self): 78 """Check if object's lifetime 79 isn't ridiculously far in the past or future.""" 80 # EOL sanity check 81 if self.expiresTime - int(time.time()) > BMObject.maxTTL: 82 logger.info( 83 'This object\'s End of Life time is too far in the future.' 84 ' Ignoring it. Time is %i', self.expiresTime) 85 # .. todo:: remove from download queue 86 raise BMObjectExpiredError() 87 88 if self.expiresTime - int(time.time()) < BMObject.minTTL: 89 logger.info( 90 'This object\'s End of Life time was too long ago.' 91 ' Ignoring the object. Time is %i', self.expiresTime) 92 # .. todo:: remove from download queue 93 raise BMObjectExpiredError() 94 95 def checkStream(self): 96 import connectionpool 97 """Check if object's stream matches streams we are interested in""" 98 if self.streamNumber < protocol.MIN_VALID_STREAM \ 99 or self.streamNumber > protocol.MAX_VALID_STREAM: 100 logger.warning( 101 'The object has invalid stream: %s', self.streamNumber) 102 raise BMObjectInvalidError() 103 if self.streamNumber not in connectionpool.pool.streams: 104 logger.debug( 105 'The streamNumber %i isn\'t one we are interested in.', 106 self.streamNumber) 107 raise BMObjectUnwantedStreamError() 108 109 def checkAlreadyHave(self): 110 """ 111 Check if we already have the object 112 (so that we don't duplicate it in inventory 113 or advertise it unnecessarily) 114 """ 115 # if it's a stem duplicate, pretend we don't have it 116 if dandelion_ins.hasHash(self.inventoryHash): 117 return 118 if self.inventoryHash in state.Inventory: 119 raise BMObjectAlreadyHaveError() 120 121 def checkObjectByType(self): 122 """Call a object type specific check 123 (objects can have additional checks based on their types)""" 124 if self.objectType == protocol.OBJECT_GETPUBKEY: 125 self.checkGetpubkey() 126 elif self.objectType == protocol.OBJECT_PUBKEY: 127 self.checkPubkey() 128 elif self.objectType == protocol.OBJECT_MSG: 129 self.checkMessage() 130 elif self.objectType == protocol.OBJECT_BROADCAST: 131 self.checkBroadcast() 132 # other objects don't require other types of tests 133 134 def checkMessage(self): # pylint: disable=no-self-use 135 """"Message" object type checks.""" 136 return 137 138 def checkGetpubkey(self): 139 """"Getpubkey" object type checks.""" 140 if len(self.data) < 42: 141 logger.info( 142 'getpubkey message doesn\'t contain enough data. Ignoring.') 143 raise BMObjectInvalidError() 144 145 def checkPubkey(self): 146 """"Pubkey" object type checks.""" 147 # sanity check 148 if len(self.data) < 146 or len(self.data) > 440: 149 logger.info('pubkey object too short or too long. Ignoring.') 150 raise BMObjectInvalidError() 151 152 def checkBroadcast(self): 153 """"Broadcast" object type checks.""" 154 if len(self.data) < 180: 155 logger.debug( 156 'The payload length of this broadcast' 157 ' packet is unreasonably low. Someone is probably' 158 ' trying funny business. Ignoring message.') 159 raise BMObjectInvalidError() 160 161 # this isn't supported anymore 162 if self.version < 2: 163 raise BMObjectInvalidError()