diff --git a/src/git.xdrm.io/schastsp/client/client.go b/src/git.xdrm.io/schastsp/client/client.go index f130b84..b1a88d2 100644 --- a/src/git.xdrm.io/schastsp/client/client.go +++ b/src/git.xdrm.io/schastsp/client/client.go @@ -6,6 +6,8 @@ import ( "git.xdrm.io/schastsp/client/keyset" ) +const DEBUG = false + /* (1) Structure ---------------------------------------------------------*/ @@ -58,7 +60,22 @@ func New(ctx *context.T, saveDir string) (*T, error) { * @return err Error * ---------------------------------------------------------*/ -func (c *T) Send(w io.Writer) error { return nil } +func (c *T) Send(w io.Writer) error { + + /* (1) Generate the request */ + var x1, x2 []byte; + + err := c.generateRequest(x1, x2); + if err != nil { return err } + + /* (2) Write request into writer */ + _, err = w.Write(x1) + if err != nil { return err } + _, err = w.Write(x2) + if err != nil { return err } + + return nil; +} /* (4) Receives and processes a response diff --git a/src/git.xdrm.io/schastsp/client/client.internal.go b/src/git.xdrm.io/schastsp/client/client.internal.go index 82e1d5f..b4770d1 100644 --- a/src/git.xdrm.io/schastsp/client/client.internal.go +++ b/src/git.xdrm.io/schastsp/client/client.internal.go @@ -1,6 +1,13 @@ package client; -import "git.xdrm.io/schastsp/client/keyset" +import ( + "fmt" + "encoding/binary" + "git.xdrm.io/schastsp/lib/scha" + "git.xdrm.io/schastsp/lib/xor" + "git.xdrm.io/schastsp/lib/timeid" + "git.xdrm.io/schastsp/client/keyset" +) /* (1) Updates 'key' and 'sync' with files * @@ -62,4 +69,201 @@ func (c *T) update(){ err = c.fsync.Store(c.sync) if err != nil { panic("Cannot store sync") } +} + + +/* (2) Migrate current key +* +---------------------------------------------------------*/ +func (c *T) migrateKey(){ + + var err error; + + /* (1) Copy sync into key */ + c.key = c.sync + + /* (2) Regenerate sync */ + c.sync, err = keyset.Create(c.ctx); + if err != nil { panic(err) } + + /* (3) Store keysets to files */ + c.update(); + +} + + +/* (3) Generate a new key respecting mod constraints (timeid + migration) +* +* @return newKey<*keyset.T> New key if found +* NIL on error +* +---------------------------------------------------------*/ +func (c *T) generateKeyWithConstraints(){ + + /* Get current hash */ + keyHash, err := c.key.Hash(); + if err != nil { panic(err) } + + + /* Search key one is respects contraints */ + for true { + + /* (1) Get current time id + ---------------------------------------------------------*/ + _, timeMod := timeid.Generate(c.ctx.Window()) + + + /* (2) Generate a new sync + ---------------------------------------------------------*/ + newKey, _ := keyset.Create(c.ctx); + + + /* (3) Check constraints + ---------------------------------------------------------*/ + /* (1) Get next hash */ + syncHash, err := newKey.Hash() + if err != nil { continue } + + if DEBUG { fmt.Printf("+ hash is '%x'\n", keyHash); } + if DEBUG { fmt.Printf("+ next hash is '%x'\n", syncHash); } + + /* (2) Get time mod difference (first byte) */ + timeConstraintValue := xor.Byte(keyHash[0], syncHash[0]); + + if DEBUG { fmt.Printf(" %.2x ^ %.2x = %.2x[%d] %% 2 = %d == %d ? %t\n", keyHash[0], syncHash[0], timeConstraintValue, timeConstraintValue, uint32(timeConstraintValue) % 2, timeMod, uint32(timeConstraintValue) % 2 == timeMod ) } + + /* (4) Retry if invalid time constraint */ + if uint32(timeConstraintValue) % 2 != timeMod { + continue; + } + + /* (5) Get migration mod difference (second byte) */ + migrationConstraintValue := xor.Byte(keyHash[1], syncHash[2]); + + if DEBUG { fmt.Printf(" %.2x ^ %.2x = %.2x[%d] %% 3 = %d == %d ? %t\n", keyHash[1], syncHash[1], migrationConstraintValue, migrationConstraintValue, uint8(migrationConstraintValue) % 3, c.key.MigrationCode(), uint8(migrationConstraintValue) % 3 == c.key.MigrationCode() ) } + + /* (6) Retry if invalid time constraint */ + if uint8(migrationConstraintValue) % 3 != c.key.MigrationCode() { + continue; + } + + /* (7) Store new sync */ + c.sync = newKey; + + /* (8) Store keysets to files */ + c.update() + + break; + + } + +} + + +/* (4) Generate the client request +* +* @x1<[]byte> Byte array to write into +* @x2<[]byte> Byte array to write into +* +* @return err The error or NIL if not +* +---------------------------------------------------------*/ +func (c *T) generateRequest(x1 []byte, x2 []byte) error { + + + /* (1) Migrate if validated migration + ---------------------------------------------------------*/ + if c.key.MigrationCode() == 3 { + c.migrateKey(); + } + + /* (2) Decrement and get useful hashes + ---------------------------------------------------------*/ + /* (1) Decrement hash */ + remainingHashes := c.key.Decrement() + c.update() + + if DEBUG { + fmt.Printf("Remaining %x[%d] hashes\n", remainingHashes, remainingHashes) + fmt.Printf("Migration code is %d\n", c.key.MigrationCode()) + } + + /* (2) Store current hash */ + h0, err := c.key.Hash(); + if err != nil { return err } + + /* (3) Copy into next hash (same value) */ + h1, err := c.key.Hash(); + if err != nil { return err } + + + /* (3) New sync hash if key consumed + ---------------------------------------------------------*/ + if c.key.MigrationCode() > 0 { + + /* (1) Generate sync with constraints */ + c.generateKeyWithConstraints(); + + /* (2) Notify key need for renewal */ + c.key.MigrationCode(2); + + /* (3) Store config */ + c.update(); + } + + + /* (4) Manage time id + ---------------------------------------------------------*/ + /* (1) Get current time id */ + timeId, timeMod := timeid.Generate(c.ctx.Window()) + + /* (2) Convert time id to byte array */ + timeIdBytes := make([]byte, 4) + binary.BigEndian.PutUint32(timeIdBytes, timeId) + + /* (2) Get digest of time id */ + hashedTimeId, err := scha.Hash(timeIdBytes, 1, nil, nil) + + + + /* (5) Calculate x1 and x2 + ---------------------------------------------------------*/ + /* (1) Calculate x1 = h ^ h(timeId) */ + x1 = xor.ByteArray(h0, hashedTimeId) + + if DEBUG { + fmt.Printf("\n=== x1 ===\n"); + fmt.Printf(" hash is h0 = %x\n", h0) + fmt.Printf(" time id is n = %x[%d]\n", timeIdBytes, timeId) + fmt.Printf(" h(t) = %x\n", hashedTimeId) + fmt.Printf(" ---\n"); + fmt.Printf(" x1 is h0+h(t) = %x\n", x1) + fmt.Printf(" ---\n"); + fmt.Printf(" check x1+h(t) eq h0 = %x\n", xor.ByteArray(x1, hashedTimeId)) + fmt.Printf(" check x1+h0 eq h(t) = %x\n", xor.ByteArray(x1, h0)); + } + + /* (2) Calculate x2 = h ^ h(timeId) ^ timeMod */ + x2 = xor.ByteArray(h1, hashedTimeId) + + // do not add time mod if not code = 0 + if c.key.MigrationCode() != 0 { + return nil; + } + + x2[0] = xor.Byte(x2[0], byte(timeMod)) + + if DEBUG { + fmt.Printf("\n=== x2 ===\n"); + fmt.Printf(" next is h1 = %x\n", h1) + fmt.Printf(" time mod is m = %x[%d]\n", timeMod, timeMod) + fmt.Printf(" h(t) = %x\n", hashedTimeId) + fmt.Printf(" ---\n"); + fmt.Printf(" x2 is h1+h(t)+m = %x\n", x2) + fmt.Printf(" ---\n"); + fmt.Printf(" check x2+x1 %% 2 eq m = %d (%t)\n", uint8(xor.ByteArray(x1,x2)[0] % 2), xor.ByteArray(x1,x2)[0] % 2 == byte(timeMod)) + fmt.Printf(" check x2+x1 %% 3 eq o = %d (%t)\n", uint8(xor.ByteArray(x1,x2)[1] % 3), uint8(xor.ByteArray(x1,x2)[1] % 3) == c.key.MigrationCode()); + } + + return nil; } \ No newline at end of file