nonces.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import os.path
  2. import pytest
  3. from ..crypto import nonces
  4. from ..crypto.nonces import NonceManager
  5. from ..crypto.key import bin_to_hex
  6. from ..helpers import get_security_dir
  7. from ..remote import InvalidRPCMethod
  8. class TestNonceManager:
  9. class MockRepository:
  10. class _Location:
  11. orig = '/some/place'
  12. _location = _Location()
  13. id = bytes(32)
  14. id_str = bin_to_hex(id)
  15. def get_free_nonce(self):
  16. return self.next_free
  17. def commit_nonce_reservation(self, next_unreserved, start_nonce):
  18. assert start_nonce == self.next_free
  19. self.next_free = next_unreserved
  20. class MockOldRepository(MockRepository):
  21. def get_free_nonce(self):
  22. raise InvalidRPCMethod("")
  23. def commit_nonce_reservation(self, next_unreserved, start_nonce):
  24. pytest.fail("commit_nonce_reservation should never be called on an old repository")
  25. def setUp(self):
  26. self.repository = None
  27. def cache_nonce(self):
  28. with open(os.path.join(get_security_dir(self.repository.id_str), 'nonce')) as fd:
  29. return fd.read()
  30. def set_cache_nonce(self, nonce):
  31. with open(os.path.join(get_security_dir(self.repository.id_str), 'nonce'), "w") as fd:
  32. assert fd.write(nonce)
  33. def test_empty_cache_and_old_server(self, monkeypatch):
  34. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  35. self.repository = self.MockOldRepository()
  36. manager = NonceManager(self.repository, 0x2000)
  37. next_nonce = manager.ensure_reservation(0x2000, 19)
  38. assert next_nonce == 0x2000
  39. assert self.cache_nonce() == "0000000000002033"
  40. def test_empty_cache(self, monkeypatch):
  41. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  42. self.repository = self.MockRepository()
  43. self.repository.next_free = 0x2000
  44. manager = NonceManager(self.repository, 0x2000)
  45. next_nonce = manager.ensure_reservation(0x2000, 19)
  46. assert next_nonce == 0x2000
  47. assert self.cache_nonce() == "0000000000002033"
  48. def test_empty_nonce(self, monkeypatch):
  49. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  50. self.repository = self.MockRepository()
  51. self.repository.next_free = None
  52. manager = NonceManager(self.repository, 0x2000)
  53. next_nonce = manager.ensure_reservation(0x2000, 19)
  54. assert next_nonce == 0x2000
  55. assert self.cache_nonce() == "0000000000002033"
  56. assert self.repository.next_free == 0x2033
  57. # enough space in reservation
  58. next_nonce = manager.ensure_reservation(0x2013, 13)
  59. assert next_nonce == 0x2013
  60. assert self.cache_nonce() == "0000000000002033"
  61. assert self.repository.next_free == 0x2033
  62. # just barely enough space in reservation
  63. next_nonce = manager.ensure_reservation(0x2020, 19)
  64. assert next_nonce == 0x2020
  65. assert self.cache_nonce() == "0000000000002033"
  66. assert self.repository.next_free == 0x2033
  67. # no space in reservation
  68. next_nonce = manager.ensure_reservation(0x2033, 16)
  69. assert next_nonce == 0x2033
  70. assert self.cache_nonce() == "0000000000002063"
  71. assert self.repository.next_free == 0x2063
  72. # spans reservation boundary
  73. next_nonce = manager.ensure_reservation(0x2043, 64)
  74. assert next_nonce == 0x2063
  75. assert self.cache_nonce() == "00000000000020c3"
  76. assert self.repository.next_free == 0x20c3
  77. def test_sync_nonce(self, monkeypatch):
  78. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  79. self.repository = self.MockRepository()
  80. self.repository.next_free = 0x2000
  81. self.set_cache_nonce("0000000000002000")
  82. manager = NonceManager(self.repository, 0x2000)
  83. next_nonce = manager.ensure_reservation(0x2000, 19)
  84. assert next_nonce == 0x2000
  85. assert self.cache_nonce() == "0000000000002033"
  86. assert self.repository.next_free == 0x2033
  87. def test_server_just_upgraded(self, monkeypatch):
  88. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  89. self.repository = self.MockRepository()
  90. self.repository.next_free = None
  91. self.set_cache_nonce("0000000000002000")
  92. manager = NonceManager(self.repository, 0x2000)
  93. next_nonce = manager.ensure_reservation(0x2000, 19)
  94. assert next_nonce == 0x2000
  95. assert self.cache_nonce() == "0000000000002033"
  96. assert self.repository.next_free == 0x2033
  97. def test_transaction_abort_no_cache(self, monkeypatch):
  98. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  99. self.repository = self.MockRepository()
  100. self.repository.next_free = 0x2000
  101. manager = NonceManager(self.repository, 0x2000)
  102. next_nonce = manager.ensure_reservation(0x1000, 19)
  103. assert next_nonce == 0x2000
  104. assert self.cache_nonce() == "0000000000002033"
  105. assert self.repository.next_free == 0x2033
  106. def test_transaction_abort_old_server(self, monkeypatch):
  107. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  108. self.repository = self.MockOldRepository()
  109. self.set_cache_nonce("0000000000002000")
  110. manager = NonceManager(self.repository, 0x2000)
  111. next_nonce = manager.ensure_reservation(0x1000, 19)
  112. assert next_nonce == 0x2000
  113. assert self.cache_nonce() == "0000000000002033"
  114. def test_transaction_abort_on_other_client(self, monkeypatch):
  115. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  116. self.repository = self.MockRepository()
  117. self.repository.next_free = 0x2000
  118. self.set_cache_nonce("0000000000001000")
  119. manager = NonceManager(self.repository, 0x2000)
  120. next_nonce = manager.ensure_reservation(0x1000, 19)
  121. assert next_nonce == 0x2000
  122. assert self.cache_nonce() == "0000000000002033"
  123. assert self.repository.next_free == 0x2033
  124. def test_interleaved(self, monkeypatch):
  125. monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
  126. self.repository = self.MockRepository()
  127. self.repository.next_free = 0x2000
  128. self.set_cache_nonce("0000000000002000")
  129. manager = NonceManager(self.repository, 0x2000)
  130. next_nonce = manager.ensure_reservation(0x2000, 19)
  131. assert next_nonce == 0x2000
  132. assert self.cache_nonce() == "0000000000002033"
  133. assert self.repository.next_free == 0x2033
  134. # somehow the clients unlocks, another client reserves and this client relocks
  135. self.repository.next_free = 0x4000
  136. # enough space in reservation
  137. next_nonce = manager.ensure_reservation(0x2013, 12)
  138. assert next_nonce == 0x2013
  139. assert self.cache_nonce() == "0000000000002033"
  140. assert self.repository.next_free == 0x4000
  141. # spans reservation boundary
  142. next_nonce = manager.ensure_reservation(0x201f, 21)
  143. assert next_nonce == 0x4000
  144. assert self.cache_nonce() == "0000000000004035"
  145. assert self.repository.next_free == 0x4035