def __init__(self, bzz, name, account): self.name = clean_name(name) topic = new_topic_mask(self.name, "", "\x02") self.feed_room = Feed(bzz, account, topic) self.bzz = bzz self.participants = {} self.hsh_room = ""
def __init__(self, bzz, name, acc): roomname = clean_name(name) self.name = copy.copy(roomname) for i in range(32 - len(roomname)): roomname += "\x00" roomnamelist = list(roomname) roomnamelist[31] = "\x02" roomname = "".join(roomnamelist) self.feed_room = Feed(bzz.agent, acc, roomname, False) self.agent = bzz.agent self.bzz = bzz self.participants = {} self.feedcollection_in = FeedCollection() self.hsh_out = zerohsh.decode("hex")
def start(self, nick, srckey=None): senderfeed = Feed(self.bzz, self.feed_room.account, new_topic_mask(self.name, "", "\x06")) self.feedcollection = FeedCollection("room:" + self.name, senderfeed) participant = Participant(nick, srckey) participant.set_from_account(self.feed_room.account) self.add(nick, participant)
def add(self, nick, participant, save=True): topic = new_topic_mask(self.name, "", "\x06") participantfeed = Feed(self.bzz, participant, topic) self.feedcollection.add(participant.nick, participantfeed) self.participants[nick] = participant if save: self.hsh_room = self.save()
def start(self, nick): pubkey = "\x04" + self.feed_room.account.publickeybytes self.add( nick, Participant(clean_nick(nick), pubkey.encode("hex"), self.feed_room.account.address.encode("hex"), pubkey.encode("hex"))) self.feed_out = Feed(self.agent, self.feed_room.account, self.name, True) self.hsh_room = self.save() # create initial update so the feed lookup will succeed before first send update = zerohsh update += struct.pack(">I", now_int()) update += self.hsh_room update += "\x00\x00\x00" hsh = self.bzz.add(update) self.feed_out.update(hsh.decode("hex"))
def add_contact_feed(self, contact, srcnode): if not srcnode.can_write(): raise AttributeError("can't create contact feed for '" + contact.get_nick() + "@" + srcnode.get_name() + ", missing private key") senderfeed = Feed(self.get_active_bzz(), srcnode.get_account(), chattopic) peerfeed = Feed(self.get_active_bzz(), contact, chattopic) coll = FeedCollection(srcnode.get_name() + "." + contact.get_nick(), senderfeed) coll.add(contact.get_nick(), peerfeed) if not contact.get_public_key() in self.chats: self.chats[contact.get_public_key()] = {} sys.stderr.write("wrote to " + contact.get_nick()) self.chats[contact.get_public_key()][srcnode.get_name()] = coll
def add(self, nick, participant): # account reflects the peer's address / key acc = Account() # \todo fix this 04 prefix ambiguity bullshit acc.set_public_key(clean_pubkey(participant.key).decode("hex")) # incoming feed user is peer participantfeed_in = Feed(self.agent, acc, self.name, False) self.feedcollection_in.add(participant.nick, participantfeed_in) self.participants[nick] = participant self.hsh_room = self.save()
def load(self, hsh, owneraccount=None): savedJson = self.bzz.get(hsh.encode("hex")) print "savedj " + repr(savedJson) self.hsh_room = hsh r = json.loads(savedJson) self.name = clean_name(r['name']) for pubkeyhx in r['participants']: acc = Account() acc.set_public_key(clean_pubkey(pubkeyhx).decode("hex")) nick = acc.address.encode("hex") p = Participant(nick, "04" + acc.publickeybytes.encode("hex"), acc.address.encode("hex"), "") self.add(nick, p) # outgoing feed user is room publisher if owneraccount == None: owneraccount = self.feed_room.account self.feed_out = Feed(self.agent, owneraccount, self.name, True) hd = self.feed_out.head() if len(hd) == 64: self.hsh_out = hd.decode("hex")
def add_feed(self, name, nodename, topicseed=""): contact = self.idx_nick_contact[name] publickey = self.psses[nodename].get_account().get_public_key() if publickey in self.feeds: return None if topicseed == "": topicseed = contact.get_address() if not publickey in self.feeds: self.feeds[publickey] = {} self.feeds[publickey][name] = Feed(self.get_active_bzz(), self.psses[nodename].get_account(), topicseed) return self.feeds[publickey][name]
def load(self, hsh, owneraccount=None): savedJson = self.bzz.get(hsh.encode("hex")) #sys.stderr.write("savedj " + repr(savedJson) + " from hash " + hsh.encode("hex") + "\n") self.hsh_room = hsh r = json.loads(savedJson) self.name = clean_name(r['name']) # outgoing feed user is room publisher if owneraccount == None: owneraccount = self.feed_room.account senderfeed = Feed(self.bzz, owneraccount, new_topic_mask(self.name, "", "\x06")) self.feedcollection = FeedCollection("room:" + self.name, senderfeed) for pubkeyhx in r['participants']: pubkey = clean_pubkey(pubkeyhx).decode("hex") nick = publickey_to_address(pubkey) p = Participant(nick.encode("hex"), None) p.set_public_key(pubkey) try: self.add(nick, p, False) except Exception as e: sys.stderr.write("skipping already added feed: '" + pubkey.encode("hex"))
class Room: # name of room, used for feed topic name = "" # swarm hsh of serialized representation of room hsh_room = "" # swarm hash of previous update hsh_out = "" # time of last send lasttime = 0 # Agent object, http transport agent = None # bzz object bzz = None # room parameters feed feed_room = None # Output feed feed_out = None # Object representation of participants, Participant type participants = None # Participant type # Input feed aggregator feedcollection_in = None #def __init__(self, bzz, feed): def __init__(self, bzz, name, acc): roomname = clean_name(name) self.name = copy.copy(roomname) for i in range(32 - len(roomname)): roomname += "\x00" roomnamelist = list(roomname) roomnamelist[31] = "\x02" roomname = "".join(roomnamelist) self.feed_room = Feed(bzz.agent, acc, roomname, False) self.agent = bzz.agent self.bzz = bzz self.participants = {} self.feedcollection_in = FeedCollection() self.hsh_out = zerohsh.decode("hex") # sets the name and the room parameter feed # used to instantiate a new room # \todo valid src parameter def start(self, nick): pubkey = "\x04" + self.feed_room.account.publickeybytes self.add( nick, Participant(clean_nick(nick), pubkey.encode("hex"), self.feed_room.account.address.encode("hex"), pubkey.encode("hex"))) self.feed_out = Feed(self.agent, self.feed_room.account, self.name, True) self.hsh_room = self.save() # create initial update so the feed lookup will succeed before first send update = zerohsh update += struct.pack(">I", now_int()) update += self.hsh_room update += "\x00\x00\x00" hsh = self.bzz.add(update) self.feed_out.update(hsh.decode("hex")) def can_write(self): return self.feed_room.account.is_owner() def get_state_hash(self): return self.feed_room.head() # loads a room from an existing saved record # used to reinstantiate an existing room # hsh is binary hash # \todo avoid double encoding of account address # \todo get output update head hash at time of load def load(self, hsh, owneraccount=None): savedJson = self.bzz.get(hsh.encode("hex")) print "savedj " + repr(savedJson) self.hsh_room = hsh r = json.loads(savedJson) self.name = clean_name(r['name']) for pubkeyhx in r['participants']: acc = Account() acc.set_public_key(clean_pubkey(pubkeyhx).decode("hex")) nick = acc.address.encode("hex") p = Participant(nick, "04" + acc.publickeybytes.encode("hex"), acc.address.encode("hex"), "") self.add(nick, p) # outgoing feed user is room publisher if owneraccount == None: owneraccount = self.feed_room.account self.feed_out = Feed(self.agent, owneraccount, self.name, True) hd = self.feed_out.head() if len(hd) == 64: self.hsh_out = hd.decode("hex") # adds a new participant to the room # \todo do we really need nick in addition to participant.nick here # \todo add save updated participant list to swarm def add(self, nick, participant): # account reflects the peer's address / key acc = Account() # \todo fix this 04 prefix ambiguity bullshit acc.set_public_key(clean_pubkey(participant.key).decode("hex")) # incoming feed user is peer participantfeed_in = Feed(self.agent, acc, self.name, False) self.feedcollection_in.add(participant.nick, participantfeed_in) self.participants[nick] = participant self.hsh_room = self.save() # create new update on outfeed # an update has the following format, where p is number of participants: # 0 - 31 swarm hash pointing to previous update # 32 - 35 4 bytes time of update # 36 one byte serial # 37 - 68 swarm hash pointing to participant list at time of the update # 69 - (69+(p*3)) 3 bytes data offset per participant # (68+(p*3)) - tightly packed update data per participant, in order of offsets # # if filters are used, zero-length update entries will be made for the participants filtered out def send(self, msg, fltrdefaultallow=True, fltr=[]): if not is_message(msg): raise ValueError("invalid message") # record update time now = now_int() # update will hold the actual update data update_header = self.hsh_out update_header += struct.pack(">I", now) # \todo implement serial update_header += "\x00" update_header += self.hsh_room update_body = "" crsr = 0 for k, v in self.participants.iteritems(): ciphermsg = "" filtered = False if k in fltr and fltrdefaultallow: filtered = True elif not fltrdefaultallow and not k in fltr: filtered = True if filtered: sys.stderr.write("Skipping filtered " + k) else: ciphermsg = v.encrypt_to(msg) update_header += struct.pack("<I", crsr)[:3] update_body += ciphermsg crsr += len(ciphermsg) #sys.stderr.write("group send header: " + update_header.encode("hex")) #sys.stderr.write("group send body: " + update_body.encode("hex")) hsh = self.bzz.add(update_header + update_body) self.feed_out.update(hsh) self.hsh_out = hsh.decode("hex") self.lasttime = now return hsh # returns a tuple with previous update hash (in binary) and last time (8 byte int) def extract_meta(self, body): # two hashes, 8 byte time, 3 byte offset (and no data) if len(body) < 72: raise ValueError("invalid update data") hsh = body[:32] tim = struct.unpack(">I", body[32:36])[0] serial = body[36] return hsh, tim, serial # extracts an update message matching the recipient pubkey # \todo do not use string literals of offset calcs def extract_message(self, body, contact): participantcount = 0 payloadoffset = -1 payloadlength = 0 ciphermsg = "" # retrieve update from swarm # body = self.bzz.get(hsh.encode("hex")) # if recipient list for the update matches the one in memory # we use the position of the participant in the list as the body offset index matchidx = -1 idx = 0 if self.hsh_room == body[37:69]: participantcount = len(self.participants) for p in self.participants.values(): if p.key == contact.key: matchidx = idx idx += 1 # if not we need to retrieve the one that was relevant at the time of update # and match the index against that else: savedroom = json.loads(self.bzz.get(body[37:69].encode("hex"))) participantcount = len(savedroom['participants']) for p in savedroom['participants']: if clean_hex(p) == clean_pubkey(contact.key): matchidx = idx idx += 1 # if no matches then this pubkey is not relevant for the room at that particular update if matchidx == -1: raise ValueError("pubkey " + contact.pubkey + " not valid for this update") # parse the position of the update and extract it payloadthreshold = 69 + (participantcount * 3) payloadoffsetcrsr = 69 + (3 * matchidx) payloadoffsetbytes = body[payloadoffsetcrsr:payloadoffsetcrsr + 3] payloadoffset = struct.unpack("<I", payloadoffsetbytes + "\x00")[0] if participantcount - 1 == matchidx: ciphermsg = body[69 + (participantcount * 3) + payloadoffset:] else: payloadoffsetcrsr += 3 payloadoffsetbytes = body[payloadoffsetcrsr:payloadoffsetcrsr + 3] payloadstop = struct.unpack("<I", payloadoffsetbytes + "\x00")[0] ciphermsg = body[payloadthreshold + payloadoffset:payloadthreshold + payloadstop] return ciphermsg # removes a participant from the room # \todo add save updated participant list to swarm # \todo pass participant instead? def remove(self, nick): self.feedcollection_in.remove(nick) del self.participants[nick] self.save() # outputs the serialized format in which the room parameters are stored in swarm # \todo proper nested json serialize def serialize(self): jsonStr = """{ "name":\"""" + self.name + """\", "pubkey":\"0x04""" + self.feed_room.account.publickeybytes.encode( "hex") + """\", "participants":[""" #participantList = "" for k, p in self.participants.iteritems(): jsonStr += "\"" + p.key + "\",\n" # participantList += p.serialize() jsonStr = jsonStr[0:len(jsonStr) - 2] jsonStr += """ ] }""" return jsonStr def save(self): s = self.serialize() self.hsh_room = self.bzz.add(s).decode("hex") self.feed_room.update(self.hsh_room) return self.hsh_room
class Room: ## \brief Sets up new feed for room # # Creates a Feed object from given parameters # # \param bzz Bzz handler object # \param name Name for feed (used as value in high-order bytes of feed topic) # \param account Account object containing the key to create the feed with def __init__(self, bzz, name, account): self.name = clean_name(name) topic = new_topic_mask(self.name, "", "\x02") self.feed_room = Feed(bzz, account, topic) self.bzz = bzz self.participants = {} self.hsh_room = "" ## \brief Activates room # # Sets up FeedCollection object for the participants' feeds, and publishes participant list (with self as participant) # # sets the name and the room parameter feed # \param nick Name to advertise for self # \param srckey Public key to register for the outgoing feed for self def start(self, nick, srckey=None): senderfeed = Feed(self.bzz, self.feed_room.account, new_topic_mask(self.name, "", "\x06")) self.feedcollection = FeedCollection("room:" + self.name, senderfeed) participant = Participant(nick, srckey) participant.set_from_account(self.feed_room.account) self.add(nick, participant) ## Human name of room # #\return take a wild guess... def get_name(self): return self.name ## Check if write to room is possible # # \return True; if private key for room is available def can_write(self): return self.feed_room.account.is_owner() ## Swarm hash of current participant list # ## \return hash, binary format def get_state_hash(self): return self.feed_room.head() ## \brief Get all participants in your participant list for the room # # \return Array of Participant objects def get_participants(self): return self.participants.values() ## Loads a room from an existing saved record # # used to reinstantiate an existing room # \param hsh Swarm hash of participant list in binary # \param owneraccount If Account object with private key write access will be enabled # \exception Exception on load fail # \todo avoid double encoding of account address # \todo get output update head hash at time of load # \todo evaluate whether these todos are stale :D def load(self, hsh, owneraccount=None): savedJson = self.bzz.get(hsh.encode("hex")) #sys.stderr.write("savedj " + repr(savedJson) + " from hash " + hsh.encode("hex") + "\n") self.hsh_room = hsh r = json.loads(savedJson) self.name = clean_name(r['name']) # outgoing feed user is room publisher if owneraccount == None: owneraccount = self.feed_room.account senderfeed = Feed(self.bzz, owneraccount, new_topic_mask(self.name, "", "\x06")) self.feedcollection = FeedCollection("room:" + self.name, senderfeed) for pubkeyhx in r['participants']: pubkey = clean_pubkey(pubkeyhx).decode("hex") nick = publickey_to_address(pubkey) p = Participant(nick.encode("hex"), None) p.set_public_key(pubkey) try: self.add(nick, p, False) except Exception as e: sys.stderr.write("skipping already added feed: '" + pubkey.encode("hex")) ## Add new participant to room # # \param nick Name to add participant under # \param participant Participant object containing public key of new participant # \param save True; save participant list to swarm # \exception ValueError if Feed instantiate fails # \todo do we really need nick in addition to participant.nick here # \todo is nick relevant here? it's not stored in participant list # \todo evaluate whether these todos are stale, too def add(self, nick, participant, save=True): topic = new_topic_mask(self.name, "", "\x06") participantfeed = Feed(self.bzz, participant, topic) self.feedcollection.add(participant.nick, participantfeed) self.participants[nick] = participant if save: self.hsh_room = self.save() ## \brief Create new update in room # # Adds a new update to the room feed. # # An update has the following format, where p is number of participants: # 0 - 31 swarm hash pointing to participant list at time of the update # 32 - (32+(p*3)) 3 bytes data offset per participant # (32+(p*3)) - tightly packed update data per participant, in order of offsets # # if filters are used, zero-length update entries will be made for the participants filtered out # \param msg Raw message data # \param filtrdefaultallow True; activate filter # \param fltr Participants Array of Participant objects to omit updates to # \todo implement content filtering # \todo evaluate if fltrdefaultallow is needed (why not just empty array) # \todo filtered content should be 0x800000 in content length instead (that would allow for updates up to 8MB, which is plenty more than should be posted # \return Swarm chunk hash of update, binary format def send(self, msg, fltrdefaultallow=True, fltr=[]): if not is_message(msg): raise ValueError("invalid message") # update will hold the actual update data update_header = self.hsh_room update_body = "" crsr = 0 for k, v in self.participants.iteritems(): ciphermsg = "" filtered = False if k in fltr and fltrdefaultallow: filtered = True elif not fltrdefaultallow and not k in fltr: filtered = True if filtered: sys.stderr.write("Skipping filtered " + k) else: ciphermsg = v.encrypt_to(msg) update_header += struct.pack("<I", crsr)[:3] update_body += ciphermsg crsr += len(ciphermsg) hsh = self.feedcollection.write(update_header + update_body) return hsh ## Extract metadata from response body # # \param body Raw response body data to parse # \return a tuple with previous update hash (in binary), timestamp (4 byte int) and serial (sub-timestamp sequence) # \exception ValueError on invalid update data def extract_meta(self, body): # two hashes, 8 byte time, 3 byte offset (and no data) if len(body) < 72: raise ValueError("invalid update data") hsh = body[:32] tim = struct.unpack(">I", body[32:36])[0] serial = body[36] return hsh, tim, serial ## \brief Extract a participant's message # # Extracts an update message matching the recipient pubkey # # \param body Raw response body data to parse # \param account Account with participant's key # \return Ciphertext of message (plaintext message as long as crypto is not implemented) # \exception ValueError if participant doesn't exist in update # \todo do not use string literals of offset calcs def extract_message(self, body, account): participantcount = 0 payloadoffset = -1 payloadlength = 0 ciphermsg = "" # if recipient list for the update matches the one in memory # we use the position of the participant in the list as the body offset index matchidx = -1 idx = 0 if self.hsh_room == body[:32]: participantcount = len(self.participants) for p in self.participants.values(): if p.get_public_key() == account.get_public_key(): matchidx = idx idx += 1 # if not we need to retrieve the one that was relevant at the time of update # and match the index against that else: roomhshhx = self.bzz.get(body[:32].encode("hex")) savedroom = json.loads(roomhshhx) participantcount = len(savedroom['participants']) for p in savedroom['participants']: if clean_hex(p) == clean_pubkey( account.get_public_key().encode("hex")): matchidx = idx idx += 1 # if no matches then this pubkey is not relevant for the room at that particular update if matchidx == -1: raise ValueError("pubkey " + account.get_public_key().encode("hex") + " not valid for this update") # parse the position of the update and extract it payloadthreshold = 32 + (participantcount * 3) payloadoffsetcrsr = 32 + (3 * matchidx) payloadoffsetbytes = body[payloadoffsetcrsr:payloadoffsetcrsr + 3] payloadoffset = struct.unpack("<I", payloadoffsetbytes + "\x00")[0] if participantcount - 1 == matchidx: ciphermsg = body[32 + (participantcount * 3) + payloadoffset:] else: payloadoffsetcrsr += 3 payloadoffsetbytes = body[payloadoffsetcrsr:payloadoffsetcrsr + 3] payloadstop = struct.unpack("<I", payloadoffsetbytes + "\x00")[0] ciphermsg = body[payloadthreshold + payloadoffset:payloadthreshold + payloadstop] return ciphermsg ## Remove participant from room # # \param nick Name key of participant to remove # \todo pass participant instead def remove(self, nick): self.feedcollection.remove(nick) del self.participants[nick] self.save() ## \brief Create participant list data # # outputs the serialized format in which the room parameters are stored in swarm # # \return json string of participant list # \todo binary serialization instead of json def serialize(self): jsonStr = """{ "name":\"""" + self.name + """\", "pubkey":\"0x""" + self.feed_room.account.publickeybytes.encode( "hex") + """\", "participants":[""" #participantList = "" for k, p in self.participants.iteritems(): jsonStr += "\"" + p.get_public_key().encode("hex") + "\",\n" # participantList += p.serialize() jsonStr = jsonStr[0:len(jsonStr) - 2] jsonStr += """ ] }""" return jsonStr ## Save current participant list to swarm # # \return New swarm hash of participant list def save(self): s = self.serialize() self.hsh_room = self.bzz.add(s).decode("hex") self.feed_room.update(self.hsh_room) return self.hsh_room