Change password on a peer and it's storage

This commit is contained in:
Dan Ballard 2019-12-12 12:21:14 -08:00
parent 1cc60bdfdd
commit 4ecc7c0f2b
12 changed files with 222 additions and 42 deletions

View File

@ -18,13 +18,14 @@ import (
"sync" "sync"
) )
// AttributeTag is a const name for a peer attribute that can be set at creation time, for example for versioning info
const AttributeTag = "tag" const AttributeTag = "tag"
type applicationCore struct { type applicationCore struct {
eventBuses map[string]event.Manager eventBuses map[string]event.Manager
directory string directory string
mutex sync.Mutex acmutex sync.Mutex
} }
type application struct { type application struct {
@ -32,9 +33,10 @@ type application struct {
appletPeers appletPeers
appletACN appletACN
appletPlugins appletPlugins
storage map[string]storage.ProfileStore storage map[string]storage.ProfileStore
engines map[string]connections.Engine engines map[string]connections.Engine
appBus event.Manager appBus event.Manager
appmutex sync.Mutex
} }
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers // Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
@ -44,6 +46,7 @@ type Application interface {
CreateTaggedPeer(name string, password string, tag string) CreateTaggedPeer(name string, password string, tag string)
DeletePeer(onion string) DeletePeer(onion string)
AddPeerPlugin(onion string, pluginID plugins.PluginID) AddPeerPlugin(onion string, pluginID plugins.PluginID)
ChangePeerPassword(onion, oldpass, newpass string)
LaunchPeers() LaunchPeers()
GetPrimaryBus() event.Manager GetPrimaryBus() event.Manager
@ -82,8 +85,8 @@ func (ac *applicationCore) CreatePeer(name string) (*model.Profile, error) {
profile := storage.NewProfile(name) profile := storage.NewProfile(name)
ac.mutex.Lock() ac.acmutex.Lock()
defer ac.mutex.Unlock() defer ac.acmutex.Unlock()
_, exists := ac.eventBuses[profile.Onion] _, exists := ac.eventBuses[profile.Onion]
if exists { if exists {
@ -97,8 +100,8 @@ func (ac *applicationCore) CreatePeer(name string) (*model.Profile, error) {
} }
func (ac *applicationCore) DeletePeer(onion string) { func (ac *applicationCore) DeletePeer(onion string) {
ac.mutex.Lock() ac.acmutex.Lock()
defer ac.mutex.Unlock() defer ac.acmutex.Unlock()
ac.eventBuses[onion].Shutdown() ac.eventBuses[onion].Shutdown()
delete(ac.eventBuses, onion) delete(ac.eventBuses, onion)
@ -133,7 +136,6 @@ func (app *application) CreateTaggedPeer(name string, password string, tag strin
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion})) app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}))
} }
// CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine) // CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine)
func (app *application) CreatePeer(name string, password string) { func (app *application) CreatePeer(name string, password string) {
app.CreateTaggedPeer(name, password, "") app.CreateTaggedPeer(name, password, "")
@ -141,7 +143,8 @@ func (app *application) CreatePeer(name string, password string) {
func (app *application) DeletePeer(onion string) { func (app *application) DeletePeer(onion string) {
log.Infof("DeletePeer called on %v\n", onion) log.Infof("DeletePeer called on %v\n", onion)
app.mutex.Lock() app.appmutex.Lock()
defer app.appmutex.Lock()
app.appletPlugins.ShutdownPeer(onion) app.appletPlugins.ShutdownPeer(onion)
app.plugins.Delete(onion) app.plugins.Delete(onion)
@ -157,12 +160,15 @@ func (app *application) DeletePeer(onion string) {
delete(app.storage, onion) delete(app.storage, onion)
app.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion)) app.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion))
app.mutex.Unlock()
app.applicationCore.DeletePeer(onion) app.applicationCore.DeletePeer(onion)
log.Debugf("Delete peer for %v Done\n", onion) log.Debugf("Delete peer for %v Done\n", onion)
} }
func (app *application) ChangePeerPassword(onion, oldpass, newpass string) {
app.eventBuses[onion].Publish(event.NewEventList(event.ChangePassword, event.Password, oldpass, event.NewPassword, newpass))
}
func (app *application) AddPeerPlugin(onion string, pluginID plugins.PluginID) { func (app *application) AddPeerPlugin(onion string, pluginID plugins.PluginID) {
app.AddPlugin(onion, pluginID, app.eventBuses[onion], app.acn) app.AddPlugin(onion, pluginID, app.eventBuses[onion], app.acn)
} }
@ -192,9 +198,9 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf
continue continue
} }
ac.mutex.Lock() ac.acmutex.Lock()
ac.eventBuses[profile.Onion] = eventBus ac.eventBuses[profile.Onion] = eventBus
ac.mutex.Unlock() ac.acmutex.Unlock()
loadProfileFn(profile, profileStore) loadProfileFn(profile, profileStore)
} }
@ -211,11 +217,11 @@ func (app *application) LoadProfiles(password string) {
blockedPeers := profile.BlockedPeers() blockedPeers := profile.BlockedPeers()
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers)
app.mutex.Lock() app.appmutex.Lock()
app.peers[profile.Onion] = peer app.peers[profile.Onion] = peer
app.storage[profile.Onion] = profileStore app.storage[profile.Onion] = profileStore
app.engines[profile.Onion] = engine app.engines[profile.Onion] = engine
app.mutex.Unlock() app.appmutex.Unlock()
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion})) app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}))
count++ count++
}) })
@ -255,8 +261,8 @@ func (app *application) QueryACNStatus() {
// ShutdownPeer shuts down a peer and removes it from the app's management // ShutdownPeer shuts down a peer and removes it from the app's management
func (app *application) ShutdownPeer(onion string) { func (app *application) ShutdownPeer(onion string) {
app.mutex.Lock() app.appmutex.Lock()
defer app.mutex.Unlock() defer app.appmutex.Unlock()
app.eventBuses[onion].Shutdown() app.eventBuses[onion].Shutdown()
delete(app.eventBuses, onion) delete(app.eventBuses, onion)
app.peers[onion].Shutdown() app.peers[onion].Shutdown()

View File

@ -108,6 +108,11 @@ func (ac *applicationClient) DeletePeer(onion string) {
ac.bridge.Write(&message) ac.bridge.Write(&message)
} }
func (ac *applicationClient) ChangePeerPassword(onion, oldpass, newpass string) {
message := event.IPCMessage{Dest: onion, Message: event.NewEventList(event.ChangePassword, event.Password, oldpass, event.NewPassword, newpass)}
ac.bridge.Write(&message)
}
func (ac *applicationClient) handleDeletedPeer(onion string) { func (ac *applicationClient) handleDeletedPeer(onion string) {
ac.mutex.Lock() ac.mutex.Lock()
ac.peers[onion].Shutdown() ac.peers[onion].Shutdown()

View File

@ -153,8 +153,6 @@ func (as *applicationService) deletePeer(onion string) {
as.appletPlugins.ShutdownPeer(onion) as.appletPlugins.ShutdownPeer(onion)
as.plugins.Delete(onion) as.plugins.Delete(onion)
as.engines[onion].Shutdown() as.engines[onion].Shutdown()
delete(as.engines, onion) delete(as.engines, onion)

View File

@ -131,7 +131,7 @@ func main() {
profileStore, _ := storage.NewProfileStore(nil, os.Args[2], pw) profileStore, _ := storage.NewProfileStore(nil, os.Args[2], pw)
err = profileStore.Load() err = profileStore.Read()
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
os.Exit(1) os.Exit(1)

View File

@ -82,6 +82,15 @@ const (
// RemotePeer [eg ""] // RemotePeer [eg ""]
PeerCreated = Type("PeerCreated") PeerCreated = Type("PeerCreated")
// Password, NewPassword
ChangePassword = Type("ChangePassword")
// Error(err), EventID
ChangePasswordError = Type("ChangePasswordError")
// EventID
ChangePasswordSuccess = Type("ChangePasswordSuccess")
// a group has been successfully added or newly created // a group has been successfully added or newly created
// attributes: // attributes:
// Data [serialized *model.Group] // Data [serialized *model.Group]
@ -159,6 +168,7 @@ const (
Shutdown = Type("Shutdown") Shutdown = Type("Shutdown")
// Error(err) // Error(err)
// Error creating peer
PeerError = Type("PeerError") PeerError = Type("PeerError")
// Error(err) // Error(err)
@ -200,6 +210,7 @@ const (
ProfileName = Field("ProfileName") ProfileName = Field("ProfileName")
Password = Field("Password") Password = Field("Password")
NewPassword = Field("NewPassword")
ConnectionState = Field("ConnectionState") ConnectionState = Field("ConnectionState")

View File

@ -36,7 +36,7 @@ type Group struct {
// NewGroup initializes a new group associated with a given CwtchServer // NewGroup initializes a new group associated with a given CwtchServer
func NewGroup(server string) (*Group, error) { func NewGroup(server string) (*Group, error) {
group := new(Group) group := new(Group)
group.LocalID = generateRandomID() group.LocalID = GenerateRandomID()
if utils.IsValidHostname(server) == false { if utils.IsValidHostname(server) == false {
return nil, errors.New("Server is not a valid v3 onion") return nil, errors.New("Server is not a valid v3 onion")

View File

@ -44,7 +44,8 @@ type Profile struct {
// TODO: Should this be per server? // TODO: Should this be per server?
const MaxGroupMessageLength = 1800 const MaxGroupMessageLength = 1800
func generateRandomID() string { // GenerateRandomID generates a random 16 byte hex id code
func GenerateRandomID() string {
randBytes := make([]byte, 16) randBytes := make([]byte, 16)
rand.Read(randBytes) rand.Read(randBytes)
return filepath.Join(hex.EncodeToString(randBytes)) return filepath.Join(hex.EncodeToString(randBytes))
@ -55,7 +56,7 @@ func (p *PublicProfile) init() {
p.Attributes = make(map[string]string) p.Attributes = make(map[string]string)
} }
p.unacknowledgedMessages = make(map[string]Message) p.unacknowledgedMessages = make(map[string]Message)
p.LocalID = generateRandomID() p.LocalID = GenerateRandomID()
} }
// SetAttribute allows applications to store arbitrary configuration info at the profile level. // SetAttribute allows applications to store arbitrary configuration info at the profile level.
@ -371,7 +372,7 @@ func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, err
if err == nil { if err == nil {
group := new(Group) group := new(Group)
group.GroupID = gci.GetGroupName() group.GroupID = gci.GetGroupName()
group.LocalID = generateRandomID() group.LocalID = GenerateRandomID()
group.SignedGroupID = gci.GetSignedGroupId() group.SignedGroupID = gci.GetSignedGroupId()
copy(group.GroupKey[:], gci.GetGroupSharedKey()[:]) copy(group.GroupKey[:], gci.GetGroupSharedKey()[:])
group.GroupServer = gci.GetServerHost() group.GroupServer = gci.GetServerHost()

View File

@ -1,8 +1,8 @@
package storage package storage
import ( import (
"io/ioutil"
"git.openprivacy.ca/openprivacy/libricochet-go/log" "git.openprivacy.ca/openprivacy/libricochet-go/log"
"io/ioutil"
"os" "os"
"path" "path"
) )
@ -16,9 +16,10 @@ type fileStore struct {
// FileStore is a primitive around storing encrypted files // FileStore is a primitive around storing encrypted files
type FileStore interface { type FileStore interface {
Save([]byte) error Write([]byte) error
Load() ([]byte, error) Read() ([]byte, error)
Delete() Delete()
ChangePassword(newpass string)
} }
// NewFileStore instantiates a fileStore given a filename and a password // NewFileStore instantiates a fileStore given a filename and a password
@ -30,8 +31,8 @@ func NewFileStore(directory string, filename string, password string) FileStore
return filestore return filestore
} }
// save serializes a cwtchPeer to a file // write serializes a cwtchPeer to a file
func (fps *fileStore) Save(data []byte) error { func (fps *fileStore) Write(data []byte) error {
key, salt, _ := createKey(fps.password) key, salt, _ := createKey(fps.password)
encryptedbytes, err := encryptFileData(data, key) encryptedbytes, err := encryptFileData(data, key)
if err != nil { if err != nil {
@ -44,7 +45,7 @@ func (fps *fileStore) Save(data []byte) error {
return err return err
} }
func (fps *fileStore) Load() ([]byte, error) { func (fps *fileStore) Read() ([]byte, error) {
return readEncryptedFile(fps.directory, fps.filename, fps.password) return readEncryptedFile(fps.directory, fps.filename, fps.password)
} }
@ -53,4 +54,8 @@ func (fps *fileStore) Delete() {
if err != nil { if err != nil {
log.Errorf("Deleting file %v\n", err) log.Errorf("Deleting file %v\n", err)
} }
} }
func (fps *fileStore) ChangePassword(newpass string) {
fps.password = newpass
}

View File

@ -9,11 +9,13 @@ import (
"time" "time"
) )
const groupIDLen = 32
const peerIDLen = 56
const profileFilename = "profile" const profileFilename = "profile"
type profileStore struct { type profileStore struct {
fs FileStore fs FileStore
streamStores map[string]StreamStore streamStores map[string]StreamStore // map [groupId|onion] StreamStore
directory string directory string
password string password string
profile *model.Profile profile *model.Profile
@ -59,6 +61,7 @@ func NewProfileWriterStore(eventManager event.Manager, directory, password strin
ps.eventManager.Subscribe(event.ServerStateChange, ps.queue) ps.eventManager.Subscribe(event.ServerStateChange, ps.queue)
ps.eventManager.Subscribe(event.DeleteContact, ps.queue) ps.eventManager.Subscribe(event.DeleteContact, ps.queue)
ps.eventManager.Subscribe(event.DeleteGroup, ps.queue) ps.eventManager.Subscribe(event.DeleteGroup, ps.queue)
ps.eventManager.Subscribe(event.ChangePassword, ps.queue)
return ps return ps
} }
@ -116,17 +119,64 @@ func (ps *profileStore) GetStatusMessages() []*event.Event {
return messages return messages
} }
func (ps *profileStore) ChangePassword(oldpass, newpass, eventID string) {
if oldpass != ps.password {
ps.eventManager.Publish(event.NewEventList(event.ChangePasswordError, event.Error, "Supplied current password does not match", event.EventID, eventID))
return
}
newStreamStores := map[string]StreamStore{}
idToNewLocalID := map[string]string{}
// Generate all new StreamStores with the new password and write all the old StreamStore data into these ones
for ssid, ss := range ps.streamStores {
// New ss with new pass and new localID
newlocalID := model.GenerateRandomID()
idToNewLocalID[ssid] = newlocalID
newSS := NewStreamStore(ps.directory, newlocalID, newpass)
newStreamStores[ssid] = newSS
// write whole store
messages := ss.Read()
newSS.WriteN(messages)
}
// Switch over
oldStreamStores := ps.streamStores
ps.streamStores = newStreamStores
for ssid, newLocalID := range idToNewLocalID {
if len(ssid) == groupIDLen {
ps.profile.Groups[ssid].LocalID = newLocalID
} else {
ps.profile.Contacts[ssid].LocalID = newLocalID
}
}
ps.password = newpass
ps.fs.ChangePassword(newpass)
ps.save()
// Clean up
for _, oldss := range oldStreamStores {
oldss.Delete()
}
ps.eventManager.Publish(event.NewEventList(event.ChangePasswordSuccess, event.EventID, eventID))
return
}
func (ps *profileStore) save() error { func (ps *profileStore) save() error {
if ps.writer { if ps.writer {
bytes, _ := json.Marshal(ps.profile) bytes, _ := json.Marshal(ps.profile)
return ps.fs.Save(bytes) return ps.fs.Write(bytes)
} }
return nil return nil
} }
// Load instantiates a cwtchPeer from the file store // Read instantiates a cwtchPeer from the file store
func (ps *profileStore) Load() error { func (ps *profileStore) Load() error {
decrypted, err := ps.fs.Load() decrypted, err := ps.fs.Read()
if err != nil { if err != nil {
return err return err
} }
@ -137,6 +187,7 @@ func (ps *profileStore) Load() error {
for gid, group := range cp.Groups { for gid, group := range cp.Groups {
ss := NewStreamStore(ps.directory, group.LocalID, ps.password) ss := NewStreamStore(ps.directory, group.LocalID, ps.password)
cp.Groups[gid].Timeline.SetMessages(ss.Read()) cp.Groups[gid].Timeline.SetMessages(ss.Read())
ps.streamStores[group.GroupID] = ss ps.streamStores[group.GroupID] = ss
} }
@ -150,8 +201,11 @@ func (ps *profileStore) GetProfileCopy(timeline bool) *model.Profile {
} }
func (ps *profileStore) eventHandler() { func (ps *profileStore) eventHandler() {
log.Infoln("eventHandler()!")
for { for {
ev := ps.queue.Next() ev := ps.queue.Next()
log.Infof("eventHandler event %v\n", ev)
switch ev.EventType { switch ev.EventType {
case event.BlockPeer: case event.BlockPeer:
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer]) contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
@ -254,6 +308,10 @@ func (ps *profileStore) eventHandler() {
ss.Delete() ss.Delete()
delete(ps.streamStores, groupID) delete(ps.streamStores, groupID)
} }
case event.ChangePassword:
oldpass := ev.Data[event.Password]
newpass := ev.Data[event.NewPassword]
ps.ChangePassword(oldpass, newpass, ev.EventID)
default: default:
return return
} }

View File

@ -4,6 +4,8 @@ package storage
import ( import (
"cwtch.im/cwtch/event" "cwtch.im/cwtch/event"
"fmt"
"log"
"os" "os"
"testing" "testing"
"time" "time"
@ -16,6 +18,7 @@ const testInitialMessage = "howdy"
const testMessage = "Hello from storage" const testMessage = "Hello from storage"
func TestProfileStoreWriteRead(t *testing.T) { func TestProfileStoreWriteRead(t *testing.T) {
log.Println("profile store test!")
os.RemoveAll(testingDir) os.RemoveAll(testingDir)
eventBus := event.NewEventManager() eventBus := event.NewEventManager()
profile := NewProfile(testProfileName) profile := NewProfile(testProfileName)
@ -68,3 +71,82 @@ func TestProfileStoreWriteRead(t *testing.T) {
} }
} }
func TestProfileStoreChangePassword(t *testing.T) {
os.RemoveAll(testingDir)
eventBus := event.NewEventManager()
queue := event.NewQueue()
eventBus.Subscribe(event.ChangePasswordSuccess, queue)
profile := NewProfile(testProfileName)
ps1 := NewProfileWriterStore(eventBus, testingDir, password, profile)
groupid, invite, err := profile.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
if err != nil {
t.Errorf("Creating group: %v\n", err)
}
if err != nil {
t.Errorf("Creating group invite: %v\n", err)
}
eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: ps1.GetProfileCopy(true).Onion, event.GroupInvite: string(invite)}))
time.Sleep(1 * time.Second)
fmt.Println("Sending 200 messages...")
for i := 0; i < 200; i++ {
eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{
event.GroupID: groupid,
event.TimestampSent: time.Now().Format(time.RFC3339Nano),
event.TimestampReceived: time.Now().Format(time.RFC3339Nano),
event.RemotePeer: ps1.GetProfileCopy(true).Onion,
event.Data: testMessage,
}))
}
newPass := "qwerty123"
fmt.Println("Sending Change Passwords event...")
eventBus.Publish(event.NewEventList(event.ChangePassword, event.Password, password, event.NewPassword, newPass))
ev := queue.Next()
if ev.EventType != event.ChangePasswordSuccess {
t.Errorf("Unexpected event response detected %v\n", ev.EventType)
return
}
fmt.Println("Sending 10 more messages...")
for i := 0; i < 10; i++ {
eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{
event.GroupID: groupid,
event.TimestampSent: time.Now().Format(time.RFC3339Nano),
event.TimestampReceived: time.Now().Format(time.RFC3339Nano),
event.RemotePeer: ps1.GetProfileCopy(true).Onion,
event.Data: testMessage,
}))
}
time.Sleep(3 * time.Second)
fmt.Println("Shutdown profile store...")
ps1.Shutdown()
fmt.Println("New Profile store...")
ps2 := NewProfileWriterStore(eventBus, testingDir, newPass, nil)
err = ps2.Load()
if err != nil {
t.Errorf("Error createing new profileStore with new password: %v\n", err)
return
}
profile2 := ps2.GetProfileCopy(true)
if profile2.Groups[groupid] == nil {
t.Errorf("Failed to load group %v\n", groupid)
return
}
if len(profile2.Groups[groupid].Timeline.Messages) != 210 {
t.Errorf("Failed to load group's 210 messages, instead got %v\n", len(profile2.Groups[groupid].Timeline.Messages))
}
}

View File

@ -33,8 +33,10 @@ type streamStore struct {
// StreamStore provides a stream like interface to encrypted storage // StreamStore provides a stream like interface to encrypted storage
type StreamStore interface { type StreamStore interface {
Write(message model.Message) Write(message model.Message)
WriteN(messages []model.Message)
Read() []model.Message Read() []model.Message
Delete() Delete()
ChangePassword(newpass string)
} }
// NewStreamStore returns an initialized StreamStore ready for reading and writing // NewStreamStore returns an initialized StreamStore ready for reading and writing
@ -138,7 +140,7 @@ func (ss *streamStore) Read() (messages []model.Message) {
return resp return resp
} }
// AddMessage adds a GroupMessage to the store // Write adds a GroupMessage to the store
func (ss *streamStore) Write(m model.Message) { func (ss *streamStore) Write(m model.Message) {
ss.lock.Lock() ss.lock.Lock()
defer ss.lock.Unlock() defer ss.lock.Unlock()
@ -151,3 +153,21 @@ func (ss *streamStore) Write(m model.Message) {
ss.initBuffer() ss.initBuffer()
} }
} }
func (ss *streamStore) WriteN(messages []model.Message) {
ss.lock.Lock()
defer ss.lock.Unlock()
for _, m := range messages {
ss.updateBuffer(m)
if ss.bufferByteCount > bytesPerFile {
ss.updateFile()
log.Debugf("rotating log file")
ss.rotateFileStore()
ss.initBuffer()
}
}
}
func (ss *streamStore) ChangePassword(newpass string) {}

View File

@ -2,7 +2,6 @@ package storage
import ( import (
"cwtch.im/cwtch/model" "cwtch.im/cwtch/model"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"os" "os"
"testing" "testing"
) )
@ -13,9 +12,6 @@ const password = "asdfqwer"
const line1 = "Hello from storage!" const line1 = "Hello from storage!"
func TestStreamStoreWriteRead(t *testing.T) { func TestStreamStoreWriteRead(t *testing.T) {
log.SetLevel(log.LevelDebug)
os.Remove(".test.json") os.Remove(".test.json")
os.RemoveAll(testingDir) os.RemoveAll(testingDir)
os.Mkdir(testingDir, 0777) os.Mkdir(testingDir, 0777)
@ -34,8 +30,6 @@ func TestStreamStoreWriteRead(t *testing.T) {
} }
func TestStreamStoreWriteReadRotate(t *testing.T) { func TestStreamStoreWriteReadRotate(t *testing.T) {
log.SetLevel(log.LevelDebug)
os.Remove(".test.json") os.Remove(".test.json")
os.RemoveAll(testingDir) os.RemoveAll(testingDir)
os.Mkdir(testingDir, 0777) os.Mkdir(testingDir, 0777)