/various/python/udpft.py

https://code.google.com/p/anacrolix/ · Python · 215 lines · 187 code · 13 blank · 15 comment · 38 complexity · 163d17a89ac2fc66274e9eb7ad317422 MD5 · raw file

  1. #!/usr/bin/env python
  2. import binascii
  3. import hashlib
  4. import io
  5. import os.path
  6. import pdb
  7. import select
  8. import socket
  9. import struct
  10. import sys
  11. import time
  12. class Hasher():
  13. def __init__(self, initial=None):
  14. self.__hash = hashlib.md5()
  15. if initial != None:
  16. if hasattr(initial, "read"):
  17. self.update_from(initial)
  18. else:
  19. self.update(initial)
  20. def update_from(self, fileobj):
  21. while True:
  22. buffer = fileobj.read(0x2000)
  23. self.update(buffer)
  24. if not buffer: break
  25. def __getattr__(self, name):
  26. return getattr(self.__hash, name)
  27. #@property
  28. #def digest_size(self):
  29. # return self.__hash.digest_size
  30. DIGESTSIZE = Hasher().digest_size
  31. PORT = 1337
  32. RECVBUFSIZE = 0x10000 # ~65k, tweak this later
  33. CHUNKSIZE = 512
  34. #TAG = "UDPFT"
  35. #BROADCAST = 1
  36. class Packet(object):
  37. __IDENT = "UDPFT"
  38. __HEADER = struct.Struct("!%dsH" % (len(__IDENT),))
  39. assert __HEADER.size == 7
  40. TYPE_OFFER = 1
  41. TYPE_REQUEST = 2
  42. @classmethod
  43. def from_bytes(class_, bytes):
  44. ident, type = class_.__HEADER.unpack(bytes[:class_.__HEADER.size])
  45. if ident != class_.__IDENT: return
  46. return {
  47. class_.TYPE_OFFER: OfferPacket,
  48. class_.TYPE_REQUEST: RequestPacket,
  49. }[type].from_bytes(bytes[class_.__HEADER.size:])
  50. def __init__(self, type):
  51. self.type = type
  52. def to_bytes(self):
  53. return self.__HEADER.pack(self.__IDENT, self.type)
  54. class OfferPacket(Packet):
  55. __HEADER = struct.Struct("!%dsQ" % (DIGESTSIZE,))
  56. @classmethod
  57. def from_bytes(class_, bytes):
  58. digest, filesize = class_.__HEADER.unpack(bytes[:class_.__HEADER.size])
  59. filename = bytes[class_.__HEADER.size:]
  60. return class_(digest, filesize, filename)
  61. def __init__(self, digest, filesize, filename):
  62. Packet.__init__(self, Packet.TYPE_OFFER)
  63. self.digest = digest
  64. self.filesize = filesize
  65. self.filename = filename
  66. def to_bytes(self):
  67. #pdb.set_trace()
  68. return super(self.__class__, self).to_bytes() \
  69. + self.__HEADER.pack(self.digest, self.filesize) + self.filename
  70. def __repr__(self):
  71. return "<%s filename=%s, filesize=%u, digest=%s>" % (
  72. self.__class__.__name__, self.filename, self.filesize, binascii.hexlify(self.digest))
  73. class RequestPacket(Packet):
  74. __HEADER = struct.Struct("!%ds" % (DIGESTSIZE,))
  75. __OFFSET_FIELD = struct.Struct("!I")
  76. def __init__(self, digest, offsets):
  77. Packet.__init__(self, Packet.TYPE_REQUEST)
  78. self.digest = digest
  79. self.offsets = offsets
  80. def to_bytes(self):
  81. packet = super(self.__class__, self).to_bytes()
  82. packet += self.__HEADER.pack(self.digest)
  83. for o in self.offsets:
  84. packet += self.__OFFSET_FIELD.pack(o)
  85. return packet
  86. @classmethod
  87. def from_bytes(class_, bytes):
  88. #pdb.set_trace()
  89. digest = class_.__HEADER.unpack(bytes[:class_.__HEADER.size])
  90. offsets = []
  91. bytes = bytes[class_.__HEADER.size:]
  92. while True:
  93. try:
  94. offsets.append(class_.__OFFSET_FIELD.unpack(bytes[:class_.__OFFSET_FIELD.size]))
  95. except struct.error:
  96. #pdb.set_trace()
  97. assert len(bytes) == 0
  98. break
  99. bytes = bytes[class_.__OFFSET_FIELD.size:]
  100. return class_(digest, offsets)
  101. def listen():
  102. """Listen for offers from any sender."""
  103. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  104. s.bind(('', PORT))
  105. offers = set()
  106. while True:
  107. data, address = s.recvfrom(0x10000)
  108. packet = Packet.from_bytes(data)
  109. if isinstance(packet, OfferPacket):
  110. print packet
  111. class SendOffer:
  112. def __init__(self, filepath):
  113. self.fileobj = open(filepath, "rb")
  114. self.digest = Hasher(self.fileobj).digest()
  115. assert self.fileobj.tell() == os.path.getsize(filepath)
  116. self.filesize = self.fileobj.tell()
  117. assert not os.path.isabs(filepath)
  118. self.filepath = filepath
  119. self.packet = OfferPacket(self.digest, self.filesize, self.filepath).to_bytes()
  120. def __repr__(self):
  121. return "<Offer digest=%s, size=%u, filepath=%s>" \
  122. % (binascii.hexlify(self.digest), self.filesize, self.filepath)
  123. def offer(recipient, paths):
  124. """Offer the specified paths to the recipient address."""
  125. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  126. s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  127. s.bind(('', 0))
  128. #port = s.getsockname()[1]
  129. offers = []
  130. for p in paths:
  131. if not os.path.isdir(p):
  132. offers.append(SendOffer(p))
  133. print offers
  134. assert len(offers) > 0
  135. while True:
  136. s.settimeout(None)
  137. for o in offers:
  138. #packet = Broadcast(o.digest, o.filesize, o.filepath).to_bytes()
  139. print repr(o.packet)
  140. s.sendto(o.packet, (recipient, PORT))
  141. next_offer = time.time() + 1.0
  142. while True:
  143. time_left = next_offer - time.time()
  144. if time_left < 0.0: break
  145. s.settimeout(next_offer - time.time())
  146. try:
  147. data, address = s.recvfrom(RECVBUFSIZE)
  148. except socket.timeout:
  149. break
  150. else:
  151. print repr(data)
  152. packet = Packet.from_bytes(data)
  153. assert isinstance(packet, RequestPacket)
  154. def divceil(a, b):
  155. q, r = divmod(a, b)
  156. return q + r and 1
  157. def receive(digests):
  158. """Listen for offers of the given digests, request them, and write them out."""
  159. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  160. s.bind(('', PORT))
  161. offers = {}
  162. while True:
  163. try:
  164. next_request = time.time() + 1.0
  165. while True:
  166. s.settimeout(next_request - time.time())
  167. data, address = s.recvfrom(RECVBUFSIZE)
  168. packet = Packet.from_bytes(data)
  169. if isinstance(packet, OfferPacket):
  170. if packet.digest in digests:
  171. if not packet.digest in offers:
  172. print "received new file digest: %s (%u bytes)" % (
  173. binascii.hexlify(packet.digest), packet.filesize)
  174. offers[packet.digest] = (
  175. set(),
  176. packet.filesize,
  177. set(xrange(divceil(packet.filesize, CHUNKSIZE))),
  178. open(binascii.hexlify(packet.digest), "w+b"))
  179. new_offer = (address, packet.filename)
  180. assert packet.filesize == offers[packet.digest][1]
  181. if not new_offer in offers[packet.digest][0]:
  182. print "new offer for %s from %s named %s" % (
  183. binascii.hexlify(packet.digest), address, packet.filename)
  184. offers[packet.digest][0].add(new_offer)
  185. except socket.timeout:
  186. s.settimeout(None)
  187. for digest, details in offers.iteritems():
  188. for offer in details[0]:
  189. packet = RequestPacket(digest, details[2]).to_bytes()
  190. print repr(packet)
  191. s.sendto(packet, offer[0])
  192. #elif isinstance(packet,
  193. if __name__ == "__main__":
  194. if sys.argv[1] == "send":
  195. offer(sys.argv[2], sys.argv[3:])
  196. elif sys.argv[1] == "listen":
  197. listen()
  198. elif sys.argv[1] == "receive":
  199. receive(map(binascii.unhexlify, sys.argv[2:]))
  200. elif sys.argv[1] == "broadcast":
  201. offer("<broadcast>", sys.argv[2:])
  202. else: sys.exit("unknown command: " + sys.argv[1])