text:
package gossip

import (
	"bytes"
	"fmt"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/hyperledger/fabric/gossip/api"
	"github.com/hyperledger/fabric/gossip/common"
	"github.com/hyperledger/fabric/gossip/discovery"
	"github.com/hyperledger/fabric/gossip/gossip/algo"
	"github.com/hyperledger/fabric/gossip/identity"
	"github.com/hyperledger/fabric/gossip/util"
	proto "github.com/hyperledger/fabric/protos/gossip"
	"github.com/stretchr/testify/assert"
)

var timeout = time.Second * time.Duration(180)

var testWG = sync.WaitGroup{}

func init() {
	aliveTimeInterval := time.Duration(1000) * time.Millisecond
	discovery.SetAliveTimeInternal(aliveTimeInterval)
	discovery.SetAliveExpirationCheckInterval(aliveTimeInterval)
	discovery.SetExpirationTimeout(aliveTimeInterval * 10)
	discovery.SetReconnectInterval(aliveTimeInterval * 5)

	testWG.Add(7)

}

var orgInChannelA = api.OrgIdentityType("ORG1")
var anchorPeerIdentity = api.PeerIdentityType("identityInOrg1")

func acceptData(m interface{}) bool {
	if dataMsg := m.(*proto.GossipMessage).GetDataMsg(); dataMsg != nil {
		return true
	}
	return false
}

func acceptLeadershp(message interface{}) bool {
	validMsg := message.(*proto.GossipMessage).Tag == proto.GossipMessage_CHAN_AND_ORG &&
		message.(*proto.GossipMessage).IsLeadershipMsg()

	return validMsg
}

type joinChanMsg struct {
	anchorPeers []api.AnchorPeer
}

// SequenceNumber returns the sequence number of the block this joinChanMsg
// is derived from
func (*joinChanMsg) SequenceNumber() uint64 {
	return uint64(time.Now().UnixNano())
}

// AnchorPeers returns all the anchor peers that are in the channel
func (jcm *joinChanMsg) AnchorPeers() []api.AnchorPeer {
	if len(jcm.anchorPeers) == 0 {
		return []api.AnchorPeer{{Cert: anchorPeerIdentity}}
	}
	return jcm.anchorPeers
}

type naiveCryptoService struct {
}

type orgCryptoService struct {
}

// OrgByPeerIdentity returns the OrgIdentityType
// of a given peer identity
func (*orgCryptoService) OrgByPeerIdentity(identity api.PeerIdentityType) api.OrgIdentityType {
	return orgInChannelA
}

// Verify verifies a JoinChanMessage, returns nil on success,
// and an error on failure
func (*orgCryptoService) Verify(joinChanMsg api.JoinChannelMessage) error {
	return nil
}

// VerifyByChannel verifies a peer's signature on a message in the context
// of a specific channel
func (*naiveCryptoService) VerifyByChannel(_ common.ChainID, _ api.PeerIdentityType, _, _ []byte) error {
	return nil
}

func (*naiveCryptoService) ValidateIdentity(peerIdentity api.PeerIdentityType) error {
	return nil
}

// GetPKIidOfCert returns the PKI-ID of a peer's identity
func (*naiveCryptoService) GetPKIidOfCert(peerIdentity api.PeerIdentityType) common.PKIidType {
	return common.PKIidType(peerIdentity)
}

// VerifyBlock returns nil if the block is properly signed,
// else returns error
func (*naiveCryptoService) VerifyBlock(chainID common.ChainID, signedBlock api.SignedBlock) error {
	return nil
}

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
func (*naiveCryptoService) Sign(msg []byte) ([]byte, error) {
	return msg, nil
}

// Verify checks that signature is a valid signature of message under a peer's verification key.
// If the verification succeeded, Verify returns nil meaning no error occurred.
// If peerCert is nil, then the signature is verified against this peer's verification key.
func (*naiveCryptoService) Verify(peerIdentity api.PeerIdentityType, signature, message []byte) error {
	equal := bytes.Equal(signature, message)
	if !equal {
		return fmt.Errorf("Wrong signature:%v, %v", signature, message)
	}
	return nil
}

func bootPeers(portPrefix int, ids ...int) []string {
	peers := []string{}
	for _, id := range ids {
		peers = append(peers, fmt.Sprintf("localhost:%d", (id+portPrefix)))
	}
	return peers
}

func newGossipInstance(portPrefix int, id int, maxMsgCount int, boot ...int) Gossip {
	port := id + portPrefix
	conf := &Config{
		BindPort:                   port,
		BootstrapPeers:             bootPeers(portPrefix, boot...),
		ID:                         fmt.Sprintf("p%d", id),
		MaxBlockCountToStore:       maxMsgCount,
		MaxPropagationBurstLatency: time.Duration(500) * time.Millisecond,
		MaxPropagationBurstSize:    20,
		PropagateIterations:        1,
		PropagatePeerNum:           3,
		PullInterval:               time.Duration(2) * time.Second,
		PullPeerNum:                5,
		InternalEndpoint:           fmt.Sprintf("localhost:%d", port),
		ExternalEndpoint:           fmt.Sprintf("1.2.3.4:%d", port),
		PublishCertPeriod:          time.Duration(4) * time.Second,
		PublishStateInfoInterval:   time.Duration(1) * time.Second,
		RequestStateInfoInterval:   time.Duration(1) * time.Second,
	}
	cryptoService := &naiveCryptoService{}
	idMapper := identity.NewIdentityMapper(cryptoService)

	g := NewGossipServiceWithServer(conf, &orgCryptoService{}, cryptoService, idMapper, api.PeerIdentityType(conf.InternalEndpoint))

	return g
}

func newGossipInstanceWithOnlyPull(portPrefix int, id int, maxMsgCount int, boot ...int) Gossip {
	port := id + portPrefix
	conf := &Config{
		BindPort:                   port,
		BootstrapPeers:             bootPeers(portPrefix, boot...),
		ID:                         fmt.Sprintf("p%d", id),
		MaxBlockCountToStore:       maxMsgCount,
		MaxPropagationBurstLatency: time.Duration(1000) * time.Millisecond,
		MaxPropagationBurstSize:    10,
		PropagateIterations:        0,
		PropagatePeerNum:           0,
		PullInterval:               time.Duration(1000) * time.Millisecond,
		PullPeerNum:                20,
		InternalEndpoint:           fmt.Sprintf("localhost:%d", port),
		ExternalEndpoint:           fmt.Sprintf("1.2.3.4:%d", port),
		PublishCertPeriod:          time.Duration(0) * time.Second,
		PublishStateInfoInterval:   time.Duration(1) * time.Second,
		RequestStateInfoInterval:   time.Duration(1) * time.Second,
	}

	cryptoService := &naiveCryptoService{}
	idMapper := identity.NewIdentityMapper(cryptoService)

	g := NewGossipServiceWithServer(conf, &orgCryptoService{}, cryptoService, idMapper, api.PeerIdentityType(conf.InternalEndpoint))
	return g
}

func TestPull(t *testing.T) {
	t.Parallel()

	portPrefix := 5610
	t1 := time.Now()
	// Scenario: Turn off forwarding and use only pull-based gossip.
	// First phase: Ensure full membership view for all nodes
	// Second phase: Disseminate 10 messages and ensure all nodes got them

	shortenedWaitTime := time.Duration(200) * time.Millisecond
	algo.SetDigestWaitTime(shortenedWaitTime)
	algo.SetRequestWaitTime(shortenedWaitTime)
	algo.SetResponseWaitTime(shortenedWaitTime)

	defer func() {
		algo.SetDigestWaitTime(time.Duration(1) * time.Second)
		algo.SetRequestWaitTime(time.Duration(1) * time.Second)
		algo.SetResponseWaitTime(time.Duration(2) * time.Second)
	}()

	stopped := int32(0)
	go waitForTestCompletion(&stopped, t)

	n := 5
	msgsCount2Send := 10

	peers := make([]Gossip, n)
	wg := sync.WaitGroup{}
	wg.Add(n)
	for i := 1; i <= n; i++ {
		go func(i int) {
			defer wg.Done()
			pI := newGossipInstanceWithOnlyPull(portPrefix, i, 100, 0)
			pI.JoinChan(&joinChanMsg{}, common.ChainID("A"))
			pI.UpdateChannelMetadata([]byte{}, common.ChainID("A"))
			peers[i-1] = pI
		}(i)
	}
	wg.Wait()

	time.Sleep(time.Second)

	boot := newGossipInstanceWithOnlyPull(portPrefix, 0, 100)
	boot.JoinChan(&joinChanMsg{}, common.ChainID("A"))
	boot.UpdateChannelMetadata([]byte("bla bla"), common.ChainID("A"))

	knowAll := func() bool {
		for i := 1; i <= n; i++ {
			neighborCount := len(peers[i-1].Peers())
			if n != neighborCount {
				return false
			}
		}
		return true
	}

	receivedMessages := make([]int, n)
	wg = sync.WaitGroup{}
	wg.Add(n)
	for i := 1; i <= n; i++ {
		go func(i int) {
			acceptChan, _ := peers[i-1].Accept(acceptData, false)
			go func(index int, ch <-chan *proto.GossipMessage) {
				defer wg.Done()
				for j := 0; j < msgsCount2Send; j++ {
					<-ch
					receivedMessages[index]++
				}
			}(i-1, acceptChan)
		}(i)
	}

	for i := 1; i <= msgsCount2Send; i++ {
		boot.Gossip(createDataMsg(uint64(i), []byte{}, "", common.ChainID("A")))
	}

	waitUntilOrFail(t, knowAll)
	waitUntilOrFailBlocking(t, wg.Wait)

	receivedAll := func() bool {
		for i := 0; i < n; i++ {
			if msgsCount2Send != receivedMessages[i] {
				return false
			}
		}
		return true
	}
	waitUntilOrFail(t, receivedAll)

	stop := func() {
		stopPeers(append(peers, boot))
	}

	waitUntilOrFailBlocking(t, stop)

	t.Log("Took", time.Since(t1))
	atomic.StoreInt32(&stopped, int32(1))
	fmt.Println("<<<TestPull>>>")
	testWG.Done()
}

func TestConnectToAnchorPeers(t *testing.T) {
	t.Parallel()
	portPrefix := 8610
	// Scenario: Spawn 5 peers, and make each of them connect to
	// the other 2 using join channel.
	stopped := int32(0)
	go waitForTestCompletion(&stopped, t)
	n := 5

	jcm := &joinChanMsg{anchorPeers: []api.AnchorPeer{}}
	for i := 0; i < n; i++ {
		pkiID := fmt.Sprintf("localhost:%d", portPrefix+i)
		ap := api.AnchorPeer{
			Port: portPrefix + i,
			Host: "localhost",
			Cert: []byte(pkiID),
		}
		jcm.anchorPeers = append(jcm.anchorPeers, ap)
	}

	peers := make([]Gossip, n)
	wg := sync.WaitGroup{}
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func(i int) {
			peers[i] = newGossipInstance(portPrefix, i, 100)
			peers[i].JoinChan(jcm, common.ChainID("A"))
			peers[i].UpdateChannelMetadata([]byte("bla bla"), common.ChainID("A"))
			wg.Done()
		}(i)
	}
	waitUntilOrFailBlocking(t, wg.Wait)
	waitUntilOrFail(t, checkPeersMembership(t, peers, n-1))

	channelMembership := func() bool {
		for _, peer := range peers {
			if len(peer.PeersOfChannel(common.ChainID("A"))) != n-1 {
				return false
			}
		}
		return true
	}
	waitUntilOrFail(t, channelMembership)

	stop := func() {
		stopPeers(peers)
	}
	waitUntilOrFailBlocking(t, stop)

	fmt.Println("<<<TestConnectToAnchorPeers>>>")
	atomic.StoreInt32(&stopped, int32(1))
	testWG.Done()
}

func TestMembership(t *testing.T) {
	t.Parallel()
	portPrefix := 4610
	t1 := time.Now()
	// Scenario: spawn 20 nodes and a single bootstrap node and then:
	// 1) Check full membership views for all nodes but the bootstrap node.
	// 2) Update metadata of last peer and ensure it propagates to all peers

	stopped := int32(0)
	go waitForTestCompletion(&stopped, t)

	n := 10
	var lastPeer = fmt.Sprintf("localhost:%d", (n + portPrefix))
	boot := newGossipInstance(portPrefix, 0, 100)
	boot.JoinChan(&joinChanMsg{}, common.ChainID("A"))
	boot.UpdateChannelMetadata([]byte{}, common.ChainID("A"))

	peers := make([]Gossip, n)
	wg := sync.WaitGroup{}
	wg.Add(n)
	for i := 1; i <= n; i++ {
		go func(i int) {
			defer wg.Done()
			pI := newGossipInstance(portPrefix, i, 100, 0)
			peers[i-1] = pI
			pI.JoinChan(&joinChanMsg{}, common.ChainID("A"))
			pI.UpdateChannelMetadata([]byte{}, common.ChainID("A"))
		}(i)
	}

	waitUntilOrFailBlocking(t, wg.Wait)
	t.Log("Peers started")

	seeAllNeighbors := func() bool {
		for i := 1; i <= n; i++ {
			neighborCount := len(peers[i-1].Peers())
			if neighborCount != n {
				return false
			}
		}
		return true
	}

	membershipEstablishTime := time.Now()
	waitUntilOrFail(t, seeAllNeighbors)
	t.Log("membership established in", time.Since(membershipEstablishTime))

	t.Log("Updating metadata...")
	// Change metadata in last node
	peers[len(peers)-1].UpdateMetadata([]byte("bla bla"))

	metaDataUpdated := func() bool {
		if "bla bla" != string(metadataOfPeer(boot.Peers(), lastPeer)) {
			return false
		}
		for i := 0; i < n-1; i++ {
			if "bla bla" != string(metadataOfPeer(peers[i].Peers(), lastPeer)) {
				return false
			}
		}
		return true
	}
	metadataDisseminationTime := time.Now()
	waitUntilOrFail(t, metaDataUpdated)
	fmt.Println("Metadata updated")
	t.Log("Metadata dissemination took", time.Since(metadataDisseminationTime))

	stop := func() {
		stopPeers(append(peers, boot))
	}

	stopTime := time.Now()
	waitUntilOrFailBlocking(t, stop)
	t.Log("Stop took", time.Since(stopTime))

	t.Log("Took", time.Since(t1))
	atomic.StoreInt32(&stopped, int32(1))
	fmt.Println("<<<TestMembership>>>")
	testWG.Done()
}

func TestDissemination(t *testing.T) {
	t.Parallel()
	portPrefix := 3610
	t1 := time.Now()
	// Scenario: 20 nodes and a bootstrap node.
	// The bootstrap node sends 10 messages and we count
	// that each node got 10 messages after a few seconds

	stopped := int32(0)
	go waitForTestCompletion(&stopped, t)

	n := 10
	msgsCount2Send := 10
	boot := newGossipInstance(portPrefix, 0, 100)
	boot.JoinChan(&joinChanMsg{}, common.ChainID("A"))
	boot.UpdateChannelMetadata([]byte{}, common.ChainID("A"))

	peers := make([]Gossip, n)
	receivedMessages := make([]int, n)
	wg := sync.WaitGroup{}
	wg.Add(n)
	for i := 1; i <= n; i++ {
		pI := newGossipInstance(portPrefix, i, 100, 0)
		peers[i-1] = pI
		pI.JoinChan(&joinChanMsg{}, common.ChainID("A"))
		pI.UpdateChannelMetadata([]byte{}, common.ChainID("A"))
		acceptChan, _ := pI.Accept(acceptData, false)
		go func(index int, ch <-chan *proto.GossipMessage) {
			defer wg.Done()
			for j := 0; j < msgsCount2Send; j++ {
				<-ch
				receivedMessages[index]++
			}
		}(i-1, acceptChan)
		// Change metadata in last node
		if i == n {
			pI.UpdateChannelMetadata([]byte("bla bla"), common.ChainID("A"))
		}
	}
	var lastPeer = fmt.Sprintf("localhost:%d", (n + portPrefix))
	metaDataUpdated := func() bool {
		if "bla bla" != string(metadataOfPeer(boot.PeersOfChannel(common.ChainID("A")), lastPeer)) {
			return false
		}
		for i := 0; i < n-1; i++ {
			if "bla bla" != string(metadataOfPeer(peers[i].PeersOfChannel(common.ChainID("A")), lastPeer)) {
				return false
			}
		}
		return true
	}

	membershipTime := time.Now()
	waitUntilOrFail(t, checkPeersMembership(t, peers, n))
	t.Log("Membership establishment took", time.Since(membershipTime))

	for i := 1; i <= msgsCount2Send; i++ {
		boot.Gossip(createDataMsg(uint64(i), []byte{}, "", common.ChainID("A")))
	}

	t2 := time.Now()
	waitUntilOrFailBlocking(t, wg.Wait)
	t.Log("Block dissemination took", time.Since(t2))
	t2 = time.Now()
	waitUntilOrFail(t, metaDataUpdated)
	t.Log("Metadata dissemination took", time.Since(t2))

	for i := 0; i < n; i++ {
		assert.Equal(t, msgsCount2Send, receivedMessages[i])
	}

	//Sending leadership messages
	receivedLeadershipMessages := make([]int, n)
	wgLeadership := sync.WaitGroup{}
	wgLeadership.Add(n)
	for i := 1; i <= n; i++ {
		leadershipChan, _ := peers[i-1].Accept(acceptLeadershp, false)
		go func(index int, ch <-chan *proto.GossipMessage) {
			defer wgLeadership.Done()
			<-ch
			receivedLeadershipMessages[index]++
		}(i-1, leadershipChan)
	}

	seqNum := 0
	incTime := uint64(time.Now().UnixNano())
	t3 := time.Now()

	leadershipMsg := createLeadershipMsg(true, common.ChainID("A"), incTime, uint64(seqNum), boot.(*gossipServiceImpl).conf.InternalEndpoint, boot.(*gossipServiceImpl).comm.GetPKIid())
	boot.Gossip(leadershipMsg)

	waitUntilOrFailBlocking(t, wgLeadership.Wait)
	t.Log("Leadership message dissemination took", time.Since(t3))

	for i := 0; i < n; i++ {
		assert.Equal(t, 1, receivedLeadershipMessages[i])
	}

	t.Log("Stopping peers")

	stop := func() {
		stopPeers(append(peers, boot))
	}

	stopTime := time.Now()
	waitUntilOrFailBlocking(t, stop)
	t.Log("Stop took", time.Since(stopTime))
	t.Log("Took", time.Since(t1))
	atomic.StoreInt32(&stopped, int32(1))
	fmt.Println("<<<TestDissemination>>>")
	testWG.Done()
}

func TestMembershipConvergence(t *testing.T) {
	t.Parallel()
	portPrefix := 2610
	// Scenario: Spawn 12 nodes and 3 bootstrap peers
	// but assign each node to its bootstrap peer group modulo 3.
	// Then:
	// 1) Check all groups know only themselves in the view and not others.
	// 2) Bring up a node that will connect to all bootstrap peers.
	// 3) Wait a few seconds and check that all views converged to a single one
	// 4) Kill that last node, wait a while and:
	// 4)a) Ensure all nodes consider it as dead
	// 4)b) Ensure all node still know each other

	t1 := time.Now()

	stopped := int32(0)
	go waitForTestCompletion(&stopped, t)
	boot0 := newGossipInstance(portPrefix, 0, 100)
	boot1 := newGossipInstance(portPrefix, 1, 100)
	boot2 := newGossipInstance(portPrefix, 2, 100)

	peers := []Gossip{boot0, boot1, boot2}
	// 0: {3, 6, 9, 12}
	// 1: {4, 7, 10, 13}
	// 2: {5, 8, 11, 14}
	for i := 3; i < 15; i++ {
		pI := newGossipInstance(portPrefix, i, 100, i%3)
		peers = append(peers, pI)
	}

	waitUntilOrFail(t, checkPeersMembership(t, peers, 4))
	t.Log("Sets of peers connected successfully")

	connectorPeer := newGossipInstance(portPrefix, 15, 100, 0, 1, 2)
	connectorPeer.UpdateMetadata([]byte("Connector"))

	fullKnowledge := func() bool {
		for i := 0; i < 15; i++ {
			if 15 != len(peers[i].Peers()) {
				return false
			}
			if "Connector" != string(metadataOfPeer(peers[i].Peers(), "localhost:2625")) {
				return false
			}
		}
		return true
	}

	waitUntilOrFail(t, fullKnowledge)

	t.Log("Stopping connector...")
	waitUntilOrFailBlocking(t, connectorPeer.Stop)
	t.Log("Stopped")
	time.Sleep(time.Duration(15) * time.Second)

	ensureForget := func() bool {
		for i := 0; i < 15; i++ {
			if 14 != len(peers[i].Peers()) {
				return false
			}
		}
		return true
	}

	waitUntilOrFail(t, ensureForget)

	connectorPeer = newGossipInstance(portPrefix, 15, 100)
	connectorPeer.UpdateMetadata([]byte("Connector2"))
	t.Log("Started connector")

	ensureResync := func() bool {
		for i := 0; i < 15; i++ {
			if 15 != len(peers[i].Peers()) {
				return false
			}
			if "Connector2" != string(metadataOfPeer(peers[i].Peers(), "localhost:2625")) {
				return false
			}
		}
		return true
	}

	waitUntilOrFail(t, ensureResync)

	waitUntilOrFailBlocking(t, connectorPeer.Stop)

	t.Log("Stopping peers")
	stop := func() {
		stopPeers(peers)
	}

	waitUntilOrFailBlocking(t, stop)
	atomic.StoreInt32(&stopped, int32(1))
	t.Log("Took", time.Since(t1))
	fmt.Println("<<<TestMembershipConvergence>>>")
	testWG.Done()
}

func TestDataLeakage(t *testing.T) {
	t.Parallel()
	portPrefix := 1610
	// Scenario: spawn some nodes and let them all
	// establish full membership.
	// Then, have half be in channel A and half be in channel B.
	// Ensure nodes only get messages of their channels.

	totalPeers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // THIS MUST BE EVEN AND NOT ODD

	stopped := int32(0)
	go waitForTestCompletion(&stopped, t)

	n := len(totalPeers)
	peers := make([]Gossip, n)
	wg := sync.WaitGroup{}
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func(i int) {
			totPeers := append([]int(nil), totalPeers[:i]...)
			bootPeers := append(totPeers, totalPeers[i+1:]...)
			peers[i] = newGossipInstance(portPrefix, i, 100, bootPeers...)
			wg.Done()
		}(i)
	}

	waitUntilOrFailBlocking(t, wg.Wait)
	waitUntilOrFail(t, checkPeersMembership(t, peers, n-1))

	channels := []common.ChainID{common.ChainID("A"), common.ChainID("B")}

	for i, channel := range channels {
		for j := 0; j < (n / 2); j++ {
			instanceIndex := (n/2)*i + j
			peers[instanceIndex].JoinChan(&joinChanMsg{}, channel)
			t.Log(instanceIndex, "joined", string(channel))
		}
	}

	channelAmetadata := []byte("some metadata of channel A")
	channelBmetadata := []byte("some metadata on channel B")

	peers[0].UpdateChannelMetadata(channelAmetadata, channels[0])
	peers[n/2].UpdateChannelMetadata(channelBmetadata, channels[1])

	// Wait until all peers have at least 1 peer in the per-channel view
	seeChannelMetadata := func() bool {
		for i, channel := range channels {
			for j := 1; j < (n / 2); j++ {
				instanceIndex := (n/2)*i + j
				if len(peers[instanceIndex].PeersOfChannel(channel)) == 0 {
					return false
				}
			}
		}
		return true
	}
	t1 := time.Now()
	waitUntilOrFail(t, seeChannelMetadata)
	t.Log("Metadata sync took", time.Since(t1))

	for i, channel := range channels {
		for j := 1; j < (n / 2); j++ {
			instanceIndex := (n/2)*i + j
			assert.Len(t, peers[instanceIndex].PeersOfChannel(channel), 1)
			if i == 0 {
				assert.Equal(t, channelAmetadata, peers[instanceIndex].PeersOfChannel(channel)[0].Metadata)
			} else {
				assert.Equal(t, channelBmetadata, peers[instanceIndex].PeersOfChannel(channel)[0].Metadata)
			}
		}
	}

	gotMessages := func() {
		var wg sync.WaitGroup
		wg.Add(n - 2)
		for i, channel := range channels {
			for j := 1; j < (n / 2); j++ {
				instanceIndex := (n/2)*i + j
				go func(instanceIndex int, channel common.ChainID) {
					incMsgChan, _ := peers[instanceIndex].Accept(acceptData, false)
					msg := <-incMsgChan
					assert.Equal(t, []byte(channel), []byte(msg.Channel))
					wg.Done()
				}(instanceIndex, channel)
			}
		}
		wg.Wait()
	}

	t1 = time.Now()
	peers[0].Gossip(createDataMsg(1, []byte{}, "", channels[0]))
	peers[n/2].Gossip(createDataMsg(2, []byte{}, "", channels[1]))
	waitUntilOrFailBlocking(t, gotMessages)
	t.Log("Dissemination took", time.Since(t1))
	stop := func() {
		stopPeers(peers)
	}
	stopTime := time.Now()
	waitUntilOrFailBlocking(t, stop)
	t.Log("Stop took", time.Since(stopTime))
	atomic.StoreInt32(&stopped, int32(1))
	fmt.Println("<<<TestDataLeakage>>>")
	testWG.Done()
}

func TestDisseminateAll2All(t *testing.T) {
	// Scenario: spawn some nodes, have each node
	// disseminate a block to all nodes.
	// Ensure all blocks are received

	t.Parallel()
	portPrefix := 6610
	stopped := int32(0)
	go waitForTestCompletion(&stopped, t)

	totalPeers := []int{0, 1, 2, 3, 4, 5, 6}
	n := len(totalPeers)
	peers := make([]Gossip, n)
	wg := sync.WaitGroup{}

	for i := 0; i < n; i++ {
		wg.Add(1)
		go func(i int) {
			totPeers := append([]int(nil), totalPeers[:i]...)
			bootPeers := append(totPeers, totalPeers[i+1:]...)
			pI := newGossipInstance(portPrefix, i, 100, bootPeers...)
			pI.JoinChan(&joinChanMsg{}, common.ChainID("A"))
			pI.UpdateChannelMetadata([]byte{}, common.ChainID("A"))
			peers[i] = pI
			wg.Done()
		}(i)
	}
	wg.Wait()
	waitUntilOrFail(t, checkPeersMembership(t, peers, n-1))

	bMutex := sync.WaitGroup{}
	bMutex.Add(10 * n * (n - 1))

	wg = sync.WaitGroup{}
	wg.Add(n)

	reader := func(msgChan <-chan *proto.GossipMessage, i int) {
		wg.Done()
		for range msgChan {
			bMutex.Done()
		}
	}

	for i := 0; i < n; i++ {
		msgChan, _ := peers[i].Accept(acceptData, false)
		go reader(msgChan, i)
	}

	wg.Wait()

	for i := 0; i < n; i++ {
		go func(i int) {
			blockStartIndex := i * 10
			for j := 0; j < 10; j++ {
				blockSeq := uint64(j + blockStartIndex)
				peers[i].Gossip(createDataMsg(blockSeq, []byte{}, "", common.ChainID("A")))
			}
		}(i)
	}
	waitUntilOrFailBlocking(t, bMutex.Wait)

	stop := func() {
		stopPeers(peers)
	}
	waitUntilOrFailBlocking(t, stop)
	atomic.StoreInt32(&stopped, int32(1))
	fmt.Println("<<<TestDisseminateAll2All>>>")
	testWG.Done()
}

func TestEndedGoroutines(t *testing.T) {
	t.Parallel()
	testWG.Wait()
	ensureGoroutineExit(t)
}

func createDataMsg(seqnum uint64, data []byte, hash string, channel common.ChainID) *proto.GossipMessage {
	return &proto.GossipMessage{
		Channel: []byte(channel),
		Nonce:   0,
		Tag:     proto.GossipMessage_CHAN_AND_ORG,
		Content: &proto.GossipMessage_DataMsg{
			DataMsg: &proto.DataMessage{
				Payload: &proto.Payload{
					Data:   data,
					Hash:   hash,
					SeqNum: seqnum,
				},
			},
		},
	}
}

func createLeadershipMsg(isDeclaration bool, channel common.ChainID, incTime uint64, seqNum uint64, endpoint string, pkiid []byte) *proto.GossipMessage {

	metadata := []byte{}
	metadata = strconv.AppendBool(metadata, isDeclaration)

	leadershipMsg := &proto.LeadershipMessage{
		IsDeclaration: isDeclaration,
		PkiID:         pkiid,
		Timestamp: &proto.PeerTime{
			IncNumber: incTime,
			SeqNum:    seqNum,
		},
	}

	msg := &proto.GossipMessage{
		Nonce:   0,
		Tag:     proto.GossipMessage_CHAN_AND_ORG,
		Content: &proto.GossipMessage_LeadershipMsg{LeadershipMsg: leadershipMsg},
		Channel: channel,
	}
	return msg
}

type goroutinePredicate func(g goroutine) bool

var connectionLeak = func(g goroutine) bool {
	return searchInStackTrace("comm.(*connection).writeToStream", g.stack)
}

var runTests = func(g goroutine) bool {
	return searchInStackTrace("testing.RunTests", g.stack)
}

var waitForTestCompl = func(g goroutine) bool {
	return searchInStackTrace("waitForTestCompletion", g.stack)
}

var gossipTest = func(g goroutine) bool {
	return searchInStackTrace("gossip_test.go", g.stack)
}

var goExit = func(g goroutine) bool {
	return searchInStackTrace("runtime.goexit", g.stack)
}

var clientConn = func(g goroutine) bool {
	return searchInStackTrace("resetTransport", g.stack)
}

var testingg = func(g goroutine) bool {
	return strings.Index(g.stack[len(g.stack)-1], "testing.go") != -1
}

func anyOfPredicates(predicates ...goroutinePredicate) goroutinePredicate {
	return func(g goroutine) bool {
		for _, pred := range predicates {
			if pred(g) {
				return true
			}
		}
		return false
	}
}

func shouldNotBeRunningAtEnd(gr goroutine) bool {
	return !anyOfPredicates(runTests, goExit, testingg, waitForTestCompl, gossipTest, clientConn, connectionLeak)(gr)
}

func ensureGoroutineExit(t *testing.T) {
	for i := 0; i <= 20; i++ {
		time.Sleep(time.Second)
		allEnded := true
		for _, gr := range getGoRoutines() {
			if shouldNotBeRunningAtEnd(gr) {
				allEnded = false
			}

			if shouldNotBeRunningAtEnd(gr) && i == 20 {
				assert.Fail(t, "Goroutine(s) haven't ended:", fmt.Sprintf("%v", gr.stack))
				util.PrintStackTrace()
				break
			}
		}

		if allEnded {
			return
		}
	}
}

func metadataOfPeer(members []discovery.NetworkMember, endpoint string) []byte {
	for _, member := range members {
		if member.InternalEndpoint.Endpoint == endpoint {
			return member.Metadata
		}
	}
	return nil
}

func waitForTestCompletion(stopFlag *int32, t *testing.T) {
	time.Sleep(timeout)
	if atomic.LoadInt32(stopFlag) == int32(1) {
		return
	}
	util.PrintStackTrace()
	assert.Fail(t, "Didn't stop within a timely manner")
}

func stopPeers(peers []Gossip) {
	stoppingWg := sync.WaitGroup{}
	stoppingWg.Add(len(peers))
	for i, pI := range peers {
		go func(i int, p_i Gossip) {
			defer stoppingWg.Done()
			p_i.Stop()
		}(i, pI)
	}
	stoppingWg.Wait()
	time.Sleep(time.Second * time.Duration(2))
}

func getGoroutineRawText() string {
	buf := make([]byte, 1<<16)
	runtime.Stack(buf, true)
	return string(buf)
}

func getGoRoutines() []goroutine {
	goroutines := []goroutine{}
	s := getGoroutineRawText()
	a := strings.Split(s, "goroutine ")
	for _, s := range a {
		gr := strings.Split(s, "\n")
		idStr := bytes.TrimPrefix([]byte(gr[0]), []byte("goroutine "))
		i := (strings.Index(string(idStr), " "))
		if i == -1 {
			continue
		}
		id, _ := strconv.ParseUint(string(string(idStr[:i])), 10, 64)
		stack := []string{}
		for i := 1; i < len(gr); i++ {
			if len([]byte(gr[i])) != 0 {
				stack = append(stack, gr[i])
			}
		}
		goroutines = append(goroutines, goroutine{id: id, stack: stack})
	}
	return goroutines
}

type goroutine struct {
	id    uint64
	stack []string
}

func waitUntilOrFail(t *testing.T, pred func() bool) {
	start := time.Now()
	limit := start.UnixNano() + timeout.Nanoseconds()
	for time.Now().UnixNano() < limit {
		if pred() {
			return
		}
		time.Sleep(timeout / 60)
	}
	util.PrintStackTrace()
	assert.Fail(t, "Timeout expired!")
}

func waitUntilOrFailBlocking(t *testing.T, f func()) {
	successChan := make(chan struct{}, 1)
	go func() {
		f()
		successChan <- struct{}{}
	}()
	select {
	case <-time.NewTimer(timeout).C:
		break
	case <-successChan:
		return
	}
	util.PrintStackTrace()
	assert.Fail(t, "Timeout expired!")
}

func searchInStackTrace(searchTerm string, stack []string) bool {
	for _, ste := range stack {
		if strings.Index(ste, searchTerm) != -1 {
			return true
		}
	}
	return false
}

func checkPeersMembership(t *testing.T, peers []Gossip, n int) func() bool {
	return func() bool {
		for _, peer := range peers {
			if len(peer.Peers()) != n {
				return false
			}
			for _, p := range peer.Peers() {
				assert.NotNil(t, p.InternalEndpoint)
				assert.NotEmpty(t, p.Endpoint)
			}
		}
		return true
	}
}


We started working with NovaCare Kids when my oldest son, Tyler, was displaying signs of his motor skills being grossly delayed. After an initial evaluation with Angela, she recommended that he be fit with leg braces, which proved to be one of the best early interventions. He continued to work with Angela and the rest of her team for speed therapy until he was about five years old. Because of his hard work and the dedication of his therapists, Tyler was able to start school on time with his peers.
He still requires daily use of his braces, but he enjoys swimming and golfing. I have no doubt that without the care and attention of Tyler's therapists he would not be as independent or confident in himself. One of his favorite things to do is breakdown barriers and prove to people, including his doctors, that cerebral palsy doesn't define him!
Our story continues with our second child, Brayden. When he was 18 months old, I expressed to my doctor that I was worried about his limited vocabulary. We were able to start speech therapy three times a week and, again, thanks to his hard work and the NovaCare Kids therapists, Brayden was able to improve to where he just has a speech delay. During his speech therapy, we also noticed some early motor delays and started physical and occupational therapy. He has made so many quick and big improvements, and currently enjoys playing soccer with his friends and just started gymnastics.
I never thought I'd have one child in full-time therapy, let alone two. However, Tyler and Brayden have taught me two very important lessons. First, kids will get there. They'll reach milestones and improve; it might just not look the same as other kids. Second, therapy early and often works! Thanks to Angela and her team, I have two incredibly brilliant, sweet and strong little men who continue to impress me.


text:
WILL KODAK SUCCESSFULLY PIVOT INTO PHARMACEUTICALS? I DOUBT IT.
by Adam Hartung | Jul 30, 2020 | In the Whirlpool, Innovation, Leadership, Marketing, Strategy, Trends
Yesterday (7/28/20), President Trump surprised a LOT of people announcing that via the Defense Production Act (DPA) the US government is going to give Kodak $765 million to make pharmaceuticals. The tie to current COVID-19 pandemic issues, for which the Act was invoked, is at best tenuous. Somehow the announcement seems to be more about moving pharma production back to the USA. Which is why it left me, and a lot of others, asking "why would you pick Kodak?"
Everyone knows the Kodak story. Great innovator, makes the Brownie and creates an entirely new market called "amateur photography." From an era when almost nobody had a picture of themselves, Kodak made pictures commonplace. And the company was a wild success. The US Department of Defense asked Kodak to help them develop a way to send photos digitally from satellites to earth, and after spending a lot of taxpayer money Kodak invents digital photography. A very happy DOD allowed Kodak to keep the civilian rights to digital photography. Locked into the profits from film sales, Kodak never develops the products or market and licensed away the technology. Which doomed Kodak to the world of business history books as one of the classic business screw-ups of all time, riding film sales to death and missing the next big market wave.
Over the last 20 years there's been nothing new to excite anyone about Kodak. They tried launching a blockchain technology-based business for photographers to manage picture rights. Way too late and poorly conceived, and lacking any demand, that went nowhere. Lacking any new ideas leadership grabbed the lightest "shiny new thing" and launched Kodak's own cryptocurrency "KodakCoin." Missed it? So did everyone else. In a word, Kodak was going nowhere.
I always recommend watching trends, and then pivoting your strategy to be on trend. So why didn't the blockchain and cryptocurrency "pivots" work? Simply, Kodak brought nothing to the marketplace. They didn't identify an un-met or under-met need and try to fill it with a better solution. Kotak just tried to jump into some shiny technology and throw it onto the marketplace hoping someone would think they needed it. They didn't. So those pivots failed.
Big companies can pivot. IBM pivoted from a mainframe hardware company into a software and services company. And that worked because IBM understood customers had un-met and under-met needs for enterprise applications and Software-As-A-Service (SAAS) use. IBM moved from making expensive, over-developed hardware to meeting a very real customer need, and the pivot revitalized a nearly obsolete company.
Even before IBM, Singer was once a manufacturer of sewing machines. As the 1960s ended home sewing was in a tailspin, and commercial sewing was all going to Asia. Singer had nothing new to offer, while it's primary competition (Brother) was innovating gobs of new features to make sewing better, faster, easier and cheaper. So Singer sold (all its products, manufacturing, brand name, etc) to Brother. Leadership studied the marketplace and identified a very big, growing and under-met need for defense electronics suppliers. Leadership carefully acquired leading companies with new technologies in forward looking infrared, heads up displays and others to build a leading-edge defense contractor. Note, they first identified an under-met need. Second, (via acquisition) they brought to market a lot of product innovation to improve customer performance in ways not previously utilized. The pivot was built on under-met needs and innovation.
So what is the plan for Kodak? Kodak knows nothing about pharmaceuticals, and their understanding of "chemistry" (to the extent it still exists) has NO application in pharma. (Ever heard of a joint venture called DuPont/Merck designed to apply DuPont chemistry expertise to pharma? I didn't think so. It didn't survive.) The plan is to build a company to make the most generic "pharmaceutical ingredients." Not blockbuster pharmaceuticals. Literally, the very most generic ingredients. Not better ingredients. Not cheaper ingredients. Just make what already exists – and almost assuredly at a higher cost.
These Kodak ingredients are not innovative. Making them is not innovative. The reason "big pharma" doesn't make these is because they are GENERIC products of low value, and production has moved to China and India where costs are lower. There is no innovation in these products. And Kodak has NO PLAN to add any innovation. None. Not in products, not in manufacturing process, not in markets served or customer service. Nope. Kodak plans to take 3 to 4 YEARS (any idea how fast markets move these days) to develop a plant to make a generic product that is sold on the basis of cost.
The only way this works, at all, is if the government forces, by regulation, U.S. pharma companies to buy from Kodak (in 3 to 4 years when they supposedly can make the stuff.) Otherwise, why pay the higher price? Today, American politicians constantly decry the high U.S. drug prices. So we are to expect that $765 million of taxpayer money will be spent on a plant, to make a generic compound, readily available in the world today, at a higher price, that will then be forced into American pharma products making them EVEN MORE EXPENSIVE! This is exactly how America ended up with the Bath Iron Works to make Navy ships which are the MOST expensive in the world – and thus wholly non-competitive in commercial ship production.
Does this not sound …… problematic? If we need U.S. based manufacturing for these products every single pharma company in the USA could open a plant faster, manufacturing at lower cost than Kodak, and with no quality or other regulatory concerns. There literally is no need for Kodak to become a supplier in this supply chain. And – absolutely no reason the U.S. taxpayer should be expected to teach Kodak how to "pivot" into becoming a new company. If the White House wants to use the D.P.A. to make more generic pharma compounds then it can push [insert any pharma company name you like here] to do it like they pushed G.M. to make ventilators!
Net/net – this is a pivot, and Kodak desperately needs to pivot. But this will not be a successful pivot. Because it is not targeting an unmet or under-met need. It is not utilizing innovation to create a better solution for meeting customer needs. This is making a generic product, that is readily available, at a higher cost than it is available today. Who wants this?
I'm sure Kodak shareholders are happy. Today. But this is a train wreck. Don't expect this plant to ever make it to fruition, as the pharma companies will unwind this deal long before Kodak makes anything. And if we're lucky, taxpayers will get some of their money back. But who knows, because this is a really stupid idea.
TRENDS MATTER. If you align with trends your business can do GREAT! Are you aligned with trends? What are the threats and opportunities in your strategy and markets? Do you need an outsider to assess what you don't know you don't know? You'll be surprised how valuable an inexpensive assessment can be for your future business (https://adamhartung.com/assessments/)
Give us a call or send an email. Adam@sparkpartners.com
Medical Tourism TREND – or How to Make a Fortune From US Health Care Stupidity
by Adam Hartung | Jul 19, 2018 | Entrepreneurship, In the Rapids, Innovation, Marketing, Medical
USA health care is ridiculously expensive. It's good, but no statistics show that US healthcare is better than any other developed country. Nor any better than accredited facilities in large, developing countries. Look at these comparisons according to Medicaltourism.com:
Procedure USA cost India cost in accredited facility
Heart Bypass $123,000 $7,900
Heart Valve Replacement $170,000 $10,450
Hip Replacement $40,364 $7,200
Knee Replacement $35,000 $6,600
Spinal Fusion $110,000 $10,300
Hysterectomy $15,400 $3,200
Cornea Replacement $17,500 $2,800
Over 1/3 of Americans live with the myth that if they need medical care, somehow it will magically happen at no cost. The Affordable Care Act tried to fix that myth by making everyone buy health insurance. But Congress removed that government mandate. So most Americans that don't have company-sponsored health insurance don't buy insurance. Their primary source of health insurance is hope. When illness or accident happens these folks end up with extra-ordinary debt. And they can't eliminate this debt because health care debt doesn't go away in bankruptcy. So every year more and more people learn that an unexpected health incident means they will spend the rest of their lives paying for medical services that were 10x or 100x what they expected.
This is a trend that will not end soon. Costs keep going up. The political sides are too divided on what to do. And health insurance companies spend literally billions annually to make sure insurance for all (referred to as Medicare for all) never becomes reality.
This trend means there is opportunity. And that has become medical tourism. Literally, flying to foreign countries for medical procedures.
You may say "not me." But if you have no money in the bank, and you let your health insurance lapse when you lost your last corporate job ended and you entered the gig economy, you could face a very tough situation. The same one almost all farmers face, and most small business owners, since their insurance is unaffordable. And most 1099 contract employees. When you have an unexpected heart attack at age 41 you wake up to hear a hospital admin say "you are alive, but you need surgery. If you want to live, we can do a heart bypass. Just sign this document and you'll wake up somewhere north of $123,000 in debt." Which means you'll lose your house, for sure. Your kids won't go to college. And you'll never again buy a new car.
Or you blow out a hip, or knee,playing that Sunday basketball pick-up game – or golf. You're 50-55, so too young for Medicare. But you lost health insurance years ago. Or you have a minimalistic plan which will cover a fraction of the cost. Finding the cost is $35,000 to $40,000 (or more likely $60,000 at a for-profit US hospital) are you really able to afford this? Or will you spend your life using crutches, or in a wheelchair? Or start an on–line begging campaign from your friends to cover the cost?
Suddenly, being a medical tourist doesn't sound so unlikely. Saving $30,000 to $100,000 could determine your financial future. This trend was pretty clear back in 2010 when I pointed out that US medical tourists grew from 700,000 in 2007 to 1.2 million in just 3 years. The trend was actually obvious in 2005, when most people laughed at the idea of medical tourism – because they refused to look at the demographic and cost trends.
That's why medical tourism is already a $20B business. And growing at 18% annually. Some analysts estimate the global market at almost $80B. Demographics are all in favor of future growth. The developed world population is aging. Health care costs are going up. Government ability to pay is going down. Insurers are charging outrageous rates. Fewer people are buying health care, and even fewer are buying "gold plated plans" that match the average plan in 1990. And American health care policies, in particular, keep driving up costs. It is EASY to see that as people can't afford care at home, so they WILL be making more trips overseas.
There are already companies making the plunge. Some are matching services between patients and medical facilities. Some are building certified medical facilities in places like India, Singapore, Brazil, Malaysia, Thailand, Costa Rica and Mexico. The opportunities are as big as the health industry.
And this trend affects every business. Are you still stuck in the status quo thinking of extremely expensive insurance for employees, or none? Medical tourism offers a plethora of other opportunities. You can offer a bare-bones domestic plan, with augmented insurance to be a medical tourist. Or even a company sponsored plan, with the opportunity for employees to build a health-care bank, and a relationship with a medical tourism company to help employees find providers offshore. And gig-economy employees can drop the idea of domestic coverage (other than bare bones) for a mixed program including offshore insurance.
Fighting the health cost trend in the USA is foolish. Doing nothing hurts your competitiveness. Given the opportunities in medical tourism, are you thinking about how to build on this trend as a new business? Or a way to offer more to full time and 1099 contractors?
Apple's Installed Base – Foundation for Success, or Curse?
by Adam Hartung | Jul 11, 2018 | Computing, Growth Stall, Innovation, Investing, Software
The last few quarters sales growth has not been as good for Apple as it once was. The iPhone X didn't sell as fast as they hoped, and while the Apple Watch outsells the entire Swiss watch industry it does not generate the volumes of an iPhone. And other new products like Apple Pay and iBeacon just have not taken off.
Amidst this slowness, the big winner has been "Apple Services" revenue. This is largely sales of music, videos and apps from iTunes and the App store. In Q2, 2018 revenues reached $9.2B, 15% of total revenues and second only to iPhone sales. Although Apple does not have a majority of smartphone users, the user base it has spends a lot of money on things for Apple devices. A lot of money.
In a bit of "get them the razor so they will buy the razor blades" CEO Tim Cook's Apple is increasingly relying upon farming the "installed base" of users to drive additional revenues. Leveraging the "installed base" of users is now THE primary theme for growing Apple sales. And even old-tech guys like Warren Buffett at Berkshire Hathaway love it, as they gobble up Apple shares. As do many analysts, and investors. Apple has paid out over $100B to developers for its services, and generated over $40B in revenues for itself – and with such a large base willing to buy things developers are likely to keep providing more products and working to grow sales.
But the risks here should not be taken lightly. At one time Apple's Macintosh was the #1 selling PC. But it was "closed" and required users buy their applications from Apple. Microsoft offered its "open architecture" and suddenly lots of new applications were available for PCs, which were also cheaper than Macs. Over a few years that "installed base" strategy backfired for Apple as PC sales exploded and Mac sales shrank until it became a niche product with under 10% market share.
Today, Android phones are the #1 smartphone market share platform, and Android devices (like the PC) are much cheaper. Even cheaper are Chinese made products. Although there are problems, the risk exists that someday apps, etc for Android and/or other platforms could become more standard and the larger Android base could "flip" the market.
The history of companies relying on an installed base to grow their company has not gone well. Going back 30 years, AM Multigraphics an ABDick sold small printing presses to schools, government agencies and businesses. After the equipment sale these companies made most of their growth on the printing supplies these presses used. But competitors whacked away at those sales, and eventually new technologies displaced the small presses. The installed base shrank, and both companies disappeared.
Xerox would literally give companies a copier if they would just pay a "per click" charge for service on the machine, and use Xerox toner. Xerox grew like the proverbial weed. Their service and toner revenue built the company. But then people started using much cheaper copiers they could buy, and supply with cheaper consumables. And desktop publishing solutions caused copier use to decline. So much for Xerox growth – and the company rapidly lost relevance. Now Xerox is on the verge of disappearing into Fuji.
HP loved to sell customers cheap ink-jet printer so they bought the ink. But now images are mostly transferred as .jpg, .png or .pdf files and not printed at all. The installed base of HP printers drove growth, until the need for any printing started disappearing.
The point? It is very risky to rely on your installed platform base for your growth. Why? Because competitors with cheaper platforms can come along and offer cheaper consumables, making your expensive platform hard to keep forefront in the market. That's the classic "innovator's dilemma" – someone comes along with a less-good solution but it's cheaper and people say "that's good enough" thus switching to the cheaper platform. This leaves the innovator stuck trying to defend their expensive platform and aftermarket sales as the market switches to ever better, cheaper solutions.
It's great that Apple is milking its installed base. That's smart. But it is not a viable long-term strategy. That base will, someday, be overtaken by a competitor or a new technology. Like, maybe, smart speakers. They are becoming ubiquitous. Yes, today Siri is the #1 voice assistant. But as Echo and Google speaker sales proliferate, can they do to smartphones what smartphones did to PCs? What if one of these companies cooperates with Microsoft to incorporate Cortana, and link everything on the network into the Windows infrastructure? If these scenarios prevail, Apple could/will be in big trouble.
I pointed out in October, 2016 that Apple hit a Growth Stall. When that happens, maintaining 2% growth long-term happens only 7% of the time. I warned investors to be wary of Apple. Why? Because a Growth Stall is an early indicator of an innovation gap developing between the company's products and emerging products. In this case, it could be a gap between ever enhanced (beyond user needs) mobile devices and really cheap voice activated assistant devices in homes, cars, offices, everywhere. Apple can milk that installed base for a goodly while, but eventually it needs "the next big thing" if it is going to continue being a long-term winner.
Harley Davidson Has Far Worse Problems Than President Trump or EU Tariffs
by Adam Hartung | Jun 27, 2018 | In the Swamp, Innovation, Investing, Manufacturing, Transportation, Trends
On Monday, Harley Davidson, America's leading manufacturer of motorcycles, announced it was going to open a plant in Europe.
Ostensibly this is to counter tariffs the EU will be imposing on its products if imported from the USA. President Trump reacted vociferously on Tuesday, threatening much bigger taxes on Harley if it brings to the USA any parts or motorcycles from its offshore plants in Brazil, Australia, India or Thailand. He also intimated that Harley Davidson was likely to collapse.
Lots of heat, not much light. The issues for Harley Davidson are far worse than an EU tariff.
Harley Davidson has about 1/3 of the US motorcycle market. But in "heavy motorcycles," those big bikes that are heavier and generally considered for longer riding, Harley has half the market. Which sounds great, until you realize that until the 1970s, Harley had 100% of that market. Ever since then, Harley has been losing share – to imports and to its domestic competitor Polaris.
It was 2006 when I first wrote about Harley Davidson's big demographic problem. Basically, its customers were all aging. Younger people were buying other motorcycles, so the "core" Harley customer was getting older every year. From mid-30s in the 1980s, by the year 2000 the average buyer was well into their mid-40s. In 2007, I pointed out that Harley had made a stab at changing this dynamic by introducing a new motorcycle with an engine made by Porsche, and a far more modern design (the V-Rod.) But Harley wasn't committed to building a new customer base, so when dealers complained that the V-Rod "wasn't really a Harley" the company backed off the marketing and went back to all its old ways of doing business.
Simultaneously, Harley Davidson motorcycle prices were rising faster than inflation, while Japanese manufacturers were not. Thus, as I also pointed out in 2007, it was struggling to maintain market share. Slower sales caused a lay-off that year, and despite the brand driving huge sales of after-market products like jackets and T-shirts, which had grown as big as bike sales, it was unclear how Harley would slow the aging of its customer base and find new, younger buyers. Harley simply eschewed the trend toward selling smaller, lighter, cheaper bikes that had more appeal to more people – and in more markets.
Globally, the situation is far more bleak than the USA. America has one of the lowest motorcycle ridership percentages on the globe. Americans love cars. But in more congested countries like across Europe or Japan and China, and in much poorer countries like India, Korea, and across South America motorcycles are more popular than automobiles. And in those countries Harley has done poorly. Because Harley doesn't even have the smaller 100cc,200cc, 400cc and 600cc bikes that dominate the market. For example, in 2006 (I know, old, but best data I could find) Harley Davidson sold 349,200 bikes globally. Honda sold 10.3 million. Yamaha sold 4.4 million. Even Suzuki sold 3.1 million – or 10 times Harley's production.
But, being as fair as possible, let's focus on Europe – where the new Harley plant is to be built. And let's look exclusively at "heavy motorcycles" (thus excluding the huge market in which Harley has no products.) In 2006, Harley was 6th in market share. BMW 16%, Honda 15%, Yamaha 15%, Suzuki 15%, Kawasaki 11% and Harley Davidson 9%. Wow, that is simply terrible.
Clearly, Harley has already become marginalized globally. Outside the USA, Harley isn't even relevant. The Japanese and Germans have been much more successful everywhere outside the USA, and every one of those other markets is bigger than the USA. Harley was simply relying on its core product (big bikes) in its core market (USA) and seriously failing everywhere else.
Oh, but even that story isn't as good as it sounds. Because in the USA sales of Harley motorcycles has been declining for a decade! Experts estimate that every year which passes, Harley's customer base ages by 6 months. The average rider age is now well into their 50s. Since Q3, 2014 Harley's sales growth has been negative! In Q2 and Q3 2017 sales declines were almost 10%/quarter!
As its customer demographic keeps working against it, new customers for big bikes are buying BMWs from Germany – and Victory and Indian motorcycles made by Polaris, out of Minnesota (Polaris discontinued the Victory brand end of 2017.) BMW sales have increased for 7 straight quarters, and their European sales are growing stronger than ever – directly in opposition to Harley's sales problems. Every quarter Indian is growing at 16-20%, taking all of its sales out of Harley Davidson USA share.
Going back to my 2016 column, when I predicted Harley was in for a hard time. Shares hit an all-time high in 2006 of $75. They have never regained that valuation. They plummeted during the Great Recession, but bailout funds from Berkshire Hathaway and the US government saved Harley from bankruptcy. Shares made it back to $70 by 2014, but fell back to $40 by 2016. Now they are trading around $40. Simply put, as much as people love to talk about the Harley brand, the company is rapidly becoming irrelevant. It is losing share in all markets, and struggling to find new customers for a product that is out-of-date, and sells almost exclusively in one market. Its move to manufacture in Europe is primarily a Hail-Mary pass to find new sales, paid for by corporate tax cuts in the USA and tariff tax avoidance in Europe.
But it won't likely matter. Like I said in 2006, Harley Davidson is a no-growth story, and that's not a story where anyone should invest.
GE Kicked Off the Dow (DJIA) – The Worst Board of Directors in America
by Adam Hartung | Jun 20, 2018 | Boards of Directors, Growth Stall, Investing, Manufacturing, Strategy
An American epoch has ended. General Electric was part of the first ever Dow Jones Index in 1896. When the Dow Jones Industrial Average was formed in 1907 GE was a participant. GE has been the only company to remain on the index. All other original companies long ago completely disappeared.
GE did so well because its leadership had been able to constantly change the company to keep it relevant, and growing. During the century prior to hiring Jeff Immelt as CEO GE went from light bulbs to generating electricity and making all kinds of electrical infrastructure equipment, electric locomotives, mainframe computers, medical equipment, computer services, financial services, entertainment…. The list is very long.
Although not all GE CEOs were great, the Board was able to place CEOs in office who could sense market shifts and make good decisions. GE leadership thoughtfully analyzed markets, and made investment decisions to sell businesses that were not growing. And they made investment decisions to invest in trends which created growth. One of the best of these was Jack Welch, who developed the nickname "Neutron Jack" for his willingness to jettison businesses that were not growing and leading their industry, while willingly investing in entirely new growth markets where trends showed high rates of return like financial services and entertainment – wildly "non-industrial" markets.
But CEO Immelt was completely tone-deaf to the outside world. He was wholly unable to understand how to lead a team that could make good investments. Instead under Immelt's leadership GE over-invested in historical products where they were losing advantage but trying to "keep up." Selling businesses that were growing but faced stiff competition, rather than investing in growth. And refusing to invest in new external growth opportunities that could keep revenues increasing – and drive a higher GE market capitalization.
All the way back in 2009 I pointed out that GE was in a Growth Stall, and had only a 7% chance of consistently growing at 2%. I warned investors. At the time I said GE had to go all-out on a growth strategy, or things would turn ugly. But a lot of investors, employees – and apparently the Board of Directors – were ready to blame the Growth Stall on the economy. And blame it on Welch, who had been gone for 8 years. And say GE was lucky Immelt saved the company from bankruptcy with a loan from Warren Buffett's Berkshire Hathaway.
Say what? Saved the company? Why did Immelt, and the Board, let GE get into such terrible shape? It was time to replace the CEO, not double down on his failed strategy.
Six years ago, May, 2012, I published in Forbes "5 CEOs that Should Have Already Been Fired." At the time I said Immelt was the 4th worst CEO in America. I cited the 2009 column, and pointed out things really weren't any better in 2012 than in 2009. That column had well over 1million reads. There was no way GE's board was not aware of the column, and the realization that Immelt was a horrible CEO.
The Board of #3 (Walmart) fired Mike Duke. And the Board of #1 (Microsoft) fired Steve Ballmer. There is no real board at #2 Sears (which will file bankruptcy soon enough) because the CEO is also the largest shareholder (via his hedge fund) and he controls all board decisions. He should have fired himself, but had too much ego. But the board of GE – well it did nothing. Even though GE almost went bankrupt under Immelt, and its value was being destroyed quarter after quarter it left him in place.
I revisited the performance of these five CEOs and their companies in August, 2014 and reminded hundreds of thousands of readers – which I'm sure included GE's Board of Directors – that company revenues had declined every year since 2009. And this string of failures had caused the company's value to decline by 2/3. Yet, the board did nothing to replace this horrific CEO.
By March, 2017 I was so exasperated I finally titled my column "GE Needs a New Strategy and a New CEO." Again, I detailed all the things that went wrong. It took 7 more months before the Board pushed Immelt out. But the new CEO failed to offer a better strategy, continuing to promote the notion of selling businesses to raise cash to "fix" the broken businesses – without identifying any growth strategy at all.
The only thing that can "fix" GE – save it from being dismembered and sold off – is a growth strategy. I offered how the new CEO could undertake this effort in October, 2017. But the Board, still wholly incompetent, still isn't listening. Nobody should be surprised that GE is now removed from the Dow, and the new CEO is clearly without a clue how to find a path back to relevancy.
Too bad for investors, employees, suppliers, customers and the communities where the GE businesses reside. This didn't have to happen. But due to an incompetent Board of Directors, which did nothing to properly govern an incompetent CEO, it did. And there's little doubt it won't be long before GE meets the same end as DuPont.
Amazon Alexa Is Becoming Ubiquitous – And That's an Apple Threat
by Adam Hartung | May 8, 2018 | Computing, Growth Stall, Innovation, Investing, Software, Trends, Web/Tech
One in five American homes with wifi now has an Amazon Alexa. And the acceptance rate is growing. To me that seems remarkable. I remember when we feared Google keeping all those searches we did. Then the fears people seemed to have about Facebook knowing our friends, families and what we talked about. Now it appears that people have no fear of "big brother" as they rapidly adopt a technology into their homes which can hear pretty near everything that is said, or that happens.
It goes to show that for most people, convenience is still incredibly important. Give us mobile phones and we let land-lines go, because mobile is so convenient – even if more expensive and lower quality. Give us laptops we let go of the traditional office, taking our work everywhere, even at a loss of work-life balance. Give us e-commerce and we start letting retailers keep our credit card information, even if it threatens our credit security. Give us digital documents via Kindle, or a smart device on the web grabbing short articles and pdf files, and we get rid of paper books and magazines. Give us streaming and we let go of physical entertainment platforms, choosing to download movies for one-time use, even though we once thought "owning" our entertainment was important.
With each new technology we make the trade-off between convenience and something we formerly thought was important. Such as quality, price, face-to-face communications, shopping in a store, owning a book or our entertainment – and even security and privacy. For all the hubbub that regulators, politicians and the "old guard" throws up about how important these things were, it did not take long for these factors to not matter as convenience outweighed what we used to think we wanted.
Now, voice activation is becoming radically important. With Google Assistant and Alexa we no longer have to bother with a keyboard interface (who wants to type?) or even a small keypad – we can just talk to our smart device. There is no doubt that is convenient. Especially when that device learns from what we say (using augmented intelligence) so it increasingly is able to accurately respond to our needs with minimal commands. Yes, this device is invading our homes, our workplaces and our lives – but it is increasingly clear that for the convenience offered we will make that trade-off. And thus what Alexa can do (measured in number of skills) has grown from zero to over 45,000 in just under 3 years.
And now, Amazon is going to explode the things Alexa can do for us. Historically Amazon controlled Alexa's Skills market, allowing very few companies to make money off Alexa transactions. But going forward Amazon is monetizing Alexa, and developers can keep 70% of the in-skill purchase revenues customers make. Buy a product or service via Alexa and developers can now make a lot of money. And, simultaneously, Amazon is offering a "code-free" skills developer, expanding the group of people who can write skills in just minutes. In other words, Amazon is setting off a gold rush for Alexa skills development, while simultaneously making the products remarkably cheap to own.
This is horrible news for Apple. Apple's revenue stagnated in 2016, declining year over year for 3 consecutive quarters. I warned folks then that this was a Growth Stall, which often implies a gap is developing between the company and the market. While Apple revenues have recovered, we can now see that gap. Apple still relies on iPhone and iPad sales, coupled with the stuff people buy from iTunes, for most of its revenue and growth. But many analysts think smartphone sales may have peaked. And while focusing on that core, Apple has NOT invested heavily in Siri, its voice platform. Today, Siri lags all other voice platforms in quality of recognition, quality of understanding, and number of services. And Apple's smart speaker sales are a drop in the ocean of Amazon Echo and Echo Dot sales.
By all indications the market for a lot of what we use our mobile devices for is shifting to voice interactivity. And Apple is far behind the leader Amazon, and the strong #2 Google. Even Microsoft's Cortana quality is considered significantly better than Siri. If this market moves as fast as the smartphone market grew it will rob sales of smartphones and iTunes, and Apple could be in a lot of trouble faster than most people think. Relevancy is a currency quickly lost in the competitive personal technology business.
The Decline of CDs and PCs – Trends Affect Us All More Than We Think
by Adam Hartung | Apr 3, 2018 | Computing, Growth Stall, Innovation, Investing, Mobile, Music
Do you still have a pile of compact discs? If so, why? When was the last time you listened to one? Like almost everyone else, you probably stream your music today. If you are just outdated, you listen to music you bought from iTunes or GooglePlay and store on your mobile device. But it would be considered prehistoric to tell people you carry around CDs for listening in your car – because you surely don't own a portable CD player.
As the chart shows, CD sales exploded from nothing in 1983 to nearly 1B units in 2000. Now sales are less than 1/10th that number, due to the market shift expanded bandwidth allowed.
Do you still carry a laptop? If so, you are a dying minority. As PCs became more portable they became indispensable. Nobody left the office, or attended a meeting, without their laptop. That trend exploded until 2011, when PC sales peaked at 365M units. As the chart shows, in the 6 years since, PC sales have dropped by over 100M units, a 30% decline. The advent of mobile devices (smartphones and tablets) coupled with expanded connectivity and growing cloud services allowed mobility to reach entirely new levels – and people stopped carrying their PCs. And just like CDs are disappearing, so will PCs.
These charts dramatically show how quickly a new technology, or package of technologies, can change the way we behave. Simultaneously, they change the competitive landscape. Sony dominated the music industry, as a producer and supplier of hardware, when CDs dominated. But, as I wrote in 2012, the shift to more portable music caused Sony to fall into a rapid decline, and the company suffered 6 consecutive years (24 quarters) of falling sales and losses. The one-time giant was crippled by a technology shift they did not adopt. And they weren't alone, as big box retailers such as Best Buy and Circuit City also faltered when these sales disappeared.
Once, Microsoft was synonymous with personal technology. Nobody maximized the value in PC growth more than Microsoft. But changing technology altered the competitive landscape, with Apple, Google, Samsung and Amazon emerging as the leaders. Microsoft, as the almost unnoticed launch of Windows 10 demonstrated, is struggling to maintain relevancy.
Too often we discount trends. Like Sony and Microsoft we think historical growth will continue, unabated. We find ways to discount market shifts, saying the products are "niche" and denigrating their quality. We will express our view that the market has "hiccuped" and will return to growth again. By the time we admit the shift is permanent new competitors have overtaken the lead, and we risk becoming totally obsolete. Like Toys-R-Us, Radio Shack, Sears and Motorola.
Aircraft stalls when not enough power to climb
The time for action is when the very first signs of shift happened. I've written a lot about "Growth Stalls" and they occur in just 2 quarters. 93% of the time a stalled company never again grows at a mere 2%/year. Look at how fast GE went from the best company in America to the worst. It is incredibly important that leadership react FAST when trends push customers toward new solutions, because it often takes very little time for the trend to make dying markets completely untenable.
Warren Buffett Is Loading Up On Apple – Why You Should be Wary
by Adam Hartung | Mar 13, 2018 | Defend & Extend, Growth Stall, Innovation, Investing, Software
In February, Berkshire Hathaway revealed it had dumped its IBM position. Good riddance to a stock that has gone down for 5 years while the S&P went up! What did Buffett do with the money? He loaded up on Apple – making that high-flyer Berkshire's #1 holding. So, isn't the smart thing now to buy Apple?
First, don't confuse your investing goals with Berkshire Hathaway's. It may seem that everyone has the same objective, to buy stocks that go up. But Berkshire is a very special case. As I pointed out in 2014, we mere mortals can't invest like Buffett, and shouldn't try. Berkshire Hathaway has the opportunity to make investments in special situations with tremendous return potential that we don't have. Berkshire's investment strategy is to invest where it can create cash to prepare for special situations, or to park money where it can make a decent return, and hopefully generate cash while it waits.
Apple is the #1 most cash-rich company on the planet, and with the new tax laws it can repatriate that cash. This is an opportunity for a "special dividend" to investors, and that is the kind of thing that Buffett loves. He isn't a venture capitalist looking for a 10x price appreciation. He wants a decent 5% rate of return, and hopefully dividends, so he can grow cash for his special situation opportunities. Apple, the most valuable company on any exchange, is exactly the kind of company where he can place a few billion dollars without driving up the price and let it sit making a solid 5-6%, collect dividends and maybe get a few kickers from things like the cash repatriation.
Second, let's not forget that Buffett's IBM buying spree lost money. If he was a great tech investor, he never would have bought IBM. He bought it for the same reason he's buying Apple, only he was wrong about what was going to happen to IBM as it continued to lose relevancy.
I pointed out in May, 2016 that Apple was showing us all a lot of sustaining innovations, with new rev levels of existing products, but almost no new disruptive innovations. The company that once gave us iPods, iTunes, iPhones and iPads was increasingly relying on the next version of everything to drive sales. Lots of incremental improvement. But little discussion about any breakthrough products, like iBeacon, ApplePay or even the Apple Watch. In a real way, Apple was looking a lot more like the old Microsoft with its Windows and Office fascination than the old Apple.
By October, 2016 Apple hit a Growth Stall. While this may have seemed like "no big deal," recall that only 7% of the time do companies maintain a 2% growth rate after stalling. Is Apple going to be in that 7%? With the launch of the less-than-overwhelming iPhone X, and the actual drop in iPhone sales in Q4, 2017 it looks increasingly like Apple is on the same road as all other stalled companies.
In the short term Apple has said it is milking its installed base. By constantly bringing out new apps it has raised iTunes sales to over $30B/quarter. And it has a dedicated cadre of developers making over $25B/year creating new apps. So Apple is doing its best to get as much revenue out of that installed base of iPhones as it can, even if device sales slow (or decline.) For Buffett, this is no big deal. After all, he's parking cash and hoping to get dividends. Milking the base is a cash generation strategy he would love – like a railroad, or Coca-Cola.
But if you're interested in maintaining high returns in your portfolio, be aware of what's happening. Apple is changing. It's not going to falter and fail any time soon. But don't be lulled by Berkshire's big purchases into thinking Apple in 2018 is anything like it was in 2012 – or through 2014. Instead, keep your eyes on game changers like Netflix, Tesla and Amazon.
Walmart's Surprising Tumble – Analysts Never Learn, Will You?
by Adam Hartung | Mar 6, 2018 | In the Swamp, Investing, Retail, Scenario Planning
On February 20, 2018 Walmart's stock had its biggest price drop ever. And the second biggest percentage decline ever. Even though same store sales improved, investors sold off the stock in droves. And after a pretty healthy recent valuation run-up.
What happened? Simply put, Walmart said its on-line sales slowed and its cost of operations rose, slowing growth and cramping margins. In other words, even though it bought Jet.com Walmart is still a long, long way from coming close to matching the customer relationship and growth of Amazon.com. And (surprise, surprise) margins in on-line aren't an easy thing — as Amazon's thin margins for 15 years have demonstrated.
In other words, this was completely to be expected. Walmart is a behemoth with no adaptability. For decades the company has been focused on how to operate its warehouses and stores, and beat up its suppliers. Management had to be drug, kicking and screaming, into e-commerce. And failing regularly it finally made an acquisition. But to think that Jet.com was going to change WalMart's business model into a growing, high profit operation any time soon was foolish. Management still wants people in the store, first and foremost, and really doesn't understand how to do anything else.
All the way back in 2005, I wrote that Walmart was too big to learn, and was unwilling to create white space teams to really explore growing e-commerce (hence the belated Jet-com acquisition.) In 2007, I wrote that calling Walmart a "mature" competitor with huge advantages was the wrong way to view the company already under attack by all the e-commerce players. In July, 2015 Amazon's market cap exceeded Walmart's, showing the importance of retail transformation on investor expectations. By February, 2016 there were 10 telltale signs Walmart was in big trouble by a changing retail market. And by October, 2017 it was clear the Waltons were cashing out of Walmart, questioning why any investor should remain holding the stock.
It really is possible to watch trends and predict future markets. And that can lead to good predictions about the fates of companies. The signs were all there that Walmart shouldn't be going up in value. Hope had too many investors thinking that Walmart was too big to stumble – or fail. But hope is not how you should invest. Not for your portfolio, and not for your business. Walmart should have dedicated huge sums to e-commerce 15 years ago, now it is playing catch up with Amazon.com, and that's a race it simply won't win. Are you making the right investment decisions for your business early enough? Or will you stumble like Walmart?
Warren Buffett's Painful IBM Lesson – Have You Learned It?
by Adam Hartung | Feb 27, 2018 | Growth Stall, Innovation, Investing, Software
This February, Warren Buffett admitted he had no faith in IBM. After accumulating a huge position, by 4th quarter of 2017 he sold out almost the entire Berkshire Hathaway position. He lost faith in the IBM CEO Virginia (Ginni) Rometty, who talked big about a turnaround, but it never happened.
Mr. Buffett would have been wise to stopped having "faith" long ago. All the way back in May, 2014 I wrote that IBM was not going to be a turnaround. CEO Rommetty was spending ALL its money on share buybacks, rather than growing its business. The Washington Post made IBM the "poster child" for stupid share buybacks, pointing out that spending over $8B on repurchases had maintained earnings-per-share, and propped up the stock price, but giving IBM the largest debt-to-equity ratio of comparable companies.
IBM was already in a Growth Stall, something about which I've written often. Once a company stalls, its odds of growing at 2%/year fall to a mere 7%. But it was clear then that the CEO was more interested in financial machinations, borrowing money to repurchase shares and prop up the stock, rather than actually investing in growing the company. The once great IBM was out of step with the tech market, and had no programs in place to make it an industry leader in the future.
By April, 2017 it was clear IBM was a disaster. By then we had 20 consecutive quarters of declining revenue. Amazing. How Rometty kept her job was completely unclear. Five years of shrinkage, while all investments were in buying the stock of its shrinking enterprise – intended to hide the shrink! CEO Rometty continued promising a turnaround, with vague references to the "wonderful" Watson program. But it was clear, Buffett (and everyone else) needed to get out in 2014. So Berkshire ate its losses, took the money and ran.
Have you learned your lesson? As an investor are you holding onto stocks long after leadership has shown they have no idea how to grow revenues? If so, why? Hope is not a strategy.
As a leader, are you still forecasting hockey stick turnarounds, while continuing to invest in outdated products and businesses? Are you hoping your past will somehow create your future, even though competitors and markets have moved on? Are you leading like Rometty, hoping you can hide your failures with financial machinations and Powerpoint presentations about how things will turn your way in the future – even though those assumptions are made out of hole cloth?
It's time to get real about your investments, and your business. When revenues are challenged, something bad is happening. It's time to do something. Fast. Before a bad quarter becomes 20, and everyone is giving up.
Get notified via email on new Posts!
The Decline and Fall of Chicago and Illinois
Trends Drive Value- Apple, Amazon, Google, Facebook, Microsoft
Why Uber's Autonomous Car Project Flopped
Disney and Uber – Using Trends To Great Success
Adam@sparkpartners.com

redpajama_set_name: RedPajamaCommonCrawl


text:
Reconsidering a "Neglected Classic" and Widening the Canon of World War I Poetry: The Song of Tiadatha
Andrew Scragg
Reconsidering a "Neglected Classic" and Widening the Canon of World War I Poetry:The Song of Tiadatha
If Wilfred Owen's dictum was "my subject is war and the pity of war,"'1 Owen Rutter's might well have been "my subject is war and the boredom of war." His mock-epic poem The Song of Tiadatha deals with the experiences of Tiadatha (a pun on "Tired Arthur"—a young "nut"2 or man about town) who volunteers for the Dudshire Regiment at the start of the Great War, seeing (some) action in France and Salonica before being wounded and returning home to London on leave. It is not well known now; there are no critical editions3 and it has received virtually no critical attention; only one collection of war poetry, Never Such Innocence, anthologises passages from it, describing it as "one of the great neglected classics of the war … its gentle mockery, its warmth and sympathy, and its ability to produce flashes of sharp savagery make it unique."4 This article explores Rutter's poem to put it into the context of his war experiences, to investigate possible reasons why it has been sidelined in the discourse of Great War poetry, and to identify why it should now be reconsidered as a meaningful poetic commentary on the realities of the Great War.
The poem was written by Edward Owen Rutter (1889–1944), at the time a captain in the 7th Battalion, Wiltshire Regiment. Rutter was the son of a Royal Navy reserve engineer, educated at St. Paul's School, London, and had worked in the North Borneo civil service as a magistrate and district officer between 1910 and 1915, when he returned to London to enlist in the army. He was commissioned 2nd Lieutenant on 21 June 1915. In September 1915 the battalion left its camp at Sutton Veny for France and in November 1915 they moved south to Marseilles and then on to Salonica (modern day Thessalonika) as part of the 26th [End Page 463] Division. While in Salonica the Wiltshires took part in the Battle of Horseshoe Hill (10–18 August 1916) and the Battles of Doiran (24–25 April and 8–9 May 1917).
In June 1918 the 7th Battalion returned to France, arriving at Serqueux on 1 July 1918, where it was attached to the 50th (Northumbrian) Division. In France they saw action at the Battles of the Hindenburg Line (12 September–12 October 1918) as well as the Battle of Selle (17–25 October 1918) and the Battle of Valenciennes (1 November 1918). Demobilisation started in December 1918 and was completed in March 1919 when the final Wiltshires left France.5
The poem was written while Rutter was in Salonica and submitted in sections to the Balkan News, a daily troop newspaper edited by Collinson Owen which ran from 1915 to 1919. Rutter, like many soldier poets, was not a trained writer and used the pseudonym "Klip-Klip" for the initial publication. The series of poems were popular with the troops and Owen reprinted them in book form in Salonica in 1919; it was republished in London by T. Fisher Unwin in 1920. The book was reviewed in the Saturday Review: "Written in high spirits it achieves success of real value"6 and in the Athenaeum: "It is indeed an epic, if one can allow such a dignified title for a composition which is all gentle satire and kindly poking of fun at an essentially English type and yet without any literary merit whatsoever. This latter fact, however, has not prevented, nor will it prevent a large number of the Salonican army from treasuring Capt. Rutter's verses."7
Click for larger view
View full resolution
Unwin issued a second edition in 1922, which went through six printings by 1928 and it was republished by Philip Allan and Co., bound with a sequel, The Travels of Tiadatha, in 1935, by which time Rutter himself described it as "a period piece."8 Rutter became a successful writer and traveller after the war; he was a Fellow of the Royal Geographical and Royal Anthropological Societies and editor of the...


What Factors Affect The Cost of Salon Services?
One of the factors that can affect the cost of a service is the time it will take to render it. For example, the cost of a haircut will depend on the time it will take for a barber or stylist to finish a haircut. Once they are done with one customer, they will need to have a break first.
Another factor that will affect the cost of a salon service is the product. You will need to buy different salon products to be able to provide different services to different customers. Again, this will be added to the cost of the service.
The experience of the stylist will also come into play when pricing services. The experience of the stylist will have an impact on the quality of work they will render.
The cost of the competition can also affect the pricing of salon services. Of course, the salon will need to make themselves as the more appealing choice than their competition in order to attract more customers. You do not want the price to be too low or too expensive as well.
Promotions in the salon can be a great way to attract new customers. Again this will affect the pricing of the service you will put into promotion. You may offer a haircut at half the price or a discounted spa.
These are just some of the factors that can affect the cost of a salon service.
In 2012, the number of motorcycle accidents, as reported to the U.S. National Highway Traffic Safety Administration (NHTSA) was 112,000. The number of accidents that caused injuries was 93,000, while fatal accidents totaled to 4,957. Over the coming years, the possibility of motorcycle accidents increasing is more likely as the number of Americans acquiring and riding motorcycles also continues to multiply.
Analysis of motorcycle crash data show that those who usually get involved in motorcycle accidents are riders who never received formal riding education. These are people who believed that learning from friends or kin how to ride, rather than enrolling in a riding school, was enough. Thus, they never realize that motorcycling is not just learning how to maneuver a motorbike through traffic, it is rather learning how to ride safely.
Motorcycle accidents are classified under two categories: single vehicle motorcycle accidents and multiple-vehicle accidents. Single vehicle motorcycle accidents are more common motorcycle crashes than multiple-vehicle accidents. Its causes include riding with a high blood alcohol level, riding too fast even during poor weather conditions, and failure to brake and maneuver properly, especially when approaching a bend. A rider committing any of these mistakes often result to him/her losing his/her balance and crashing into road fixtures or getting thrown off from his/her bike.
Though less common, multiple-vehicle crashes are, however, causes of more serious injuries. This type of accident involves another vehicle. One example of multiple-vehicle crash, which also happens to be the most dangerous and the most injurious, is head-on collision, which often occurs in undivided rural highways or when one vehicle, either the motorbike or the other vehicle, travels on the wrong side of the road.
Majority of multiple-vehicle crashes occur due to drivers failing to notice approaching motorcyclists or drivers denying motorcyclists their right of way (in these situations drivers are the ones at fault during accidents, though fault is not too easy to prove). Often, rather than accepting fault, most drivers rather blame on motorcycle riders, saying that riders careen in and out of traffic, putting their own and other lives at risk.
The National Highway Traffic Safety Administration (NHTSA) says that motorcycle riders, especially in multiple-vehicle crashes, are more susceptible to injuries or death due to the absence of whatever may protect them from the force of impact created during collision.
Wausau personal injury lawyers, likewise, says that motorcycle accidents account for a significant number of serious or fatal automotive accidents in the United States each year. Because motorcyclists are not able to rely on the types of protections that other motorists enjoy, even a relatively minor accident can have devastating consequences for the victims, potentially resulting in costly medical treatment and lost income that victims and their families may not be able to afford. As such, a victim of a motorcycle accident that is due to someone else's reckless or irresponsible behavior may require significant compensation in order to deal with the challenges ahead. Fortunately, it is often possible for an injury victim to file a motorcycle accident claim to pursue this compensation.
The kind of driving history you have will affect greatly your compliance with the Financial Responsibility law, the law that requires you to prove your capability to pay for damages due to accidents wherein you are at fault. Proving this capability may be done by showing that you are covered, that is, you have auto liability insurance, or by meeting any state-approved alternative to the mandated auto insurance coverage. While a clean driving record may save you from paying too costly car insurance premiums, a record that is marred with traffic violations, such as a DUI and/or, reckless driving, or worse, an accident wherein you are at fault, can very likely result to more expensive policies because insurance providers will see and tag you as a "high-risk" driver.
Getting identified as a "high-risk" driver, however, is not only based on past traffic violations. A car insurance provider can also identify you as such if you are below 25 years old (more so if you are below 20 since records from the National Highway Traffic Safety Association consistently show that teenagers are the ones most prone to accidents), driving a high-powered sports car or any type of car that is more prone to accidents, have high annual mileage, or have been required by the court to file an SR-22.
Except in the state of New Hampshire, where drivers are not mandated to carry car insurance coverage, or in Virginia, where drivers can have their vehicle registered as uninsured by paying the required uninsured motor vehicle fee to the state's Department of Motor Vehicles (DMV), all other 48 states require drivers to carry auto liability insurance. Possible consequences of non-compliance with this requirement are suspension of your driving privileges, fines, higher cost of insurance premiums and a court order that will require you to file an SR-22. If you do cause an accident, then you may also face a civil lawsuit wherein you would be legally required to compensate your victim for all the economic and non-economic damages he or she will suffer from.
To help you and all other drivers and car owners find the best and cheapest car insurance coverage that will include all your insurance needs are independent car insurance companies which offer free online quotes from different providers. According to the website of Hankey Law Office, P.C., some will provide as many as a dozen quotes to help compare actual prices of policies and the types of coverages included in each policy. Besides car insurance, you can also ask for SR-22 quotes plus require them to make the filing and purchase on your behalf.
As said on the website of Habush Habush & Rottier SC, dog-bite cases are increasing in the United States. Every year, a large number of people are sent to the ER with severe injuries after being attacked by a stranger's canine. A number of these individuals are left with awful injuries that could possess a life-long impact on their quality of life, and way too many sufferers are kids.
The monetary expense of dog bites in America is tremendous. Nevertheless, that might not even be close to the real medical costs for insurance agencies, because according to the American Medical Association, State Farm paid compensation on 11,000 dog bite cases for an overall total of 2 million in 1995. In line with the Centers for Disease Control and Prevention, dog bites account for about 164.9 million in direct medical costs every year. Commonly the dog-bite settlement is paid for from the dog owner's homeowner's insurance. Cost may be considerably lower the less serious the injury is. For personal victims of dog attacks, the average complete cost in doctor's bills is around $26,166.
For victims of dog attacks, the toll could be more than monetary. When they suffer severe dog bites a lot of people must undergo rehabilitation and surgery. The mental cost of these trying encounters is often the hardest aspect of a dog-bite injury, and victims ought to have the right to compensation not just for their medical bills, but also for the additional pain and suffering due to their injuries.


"Ordered a cake here for my child's birthday and everyone is surprised with how fantastic the cake looks. More importantly, the taste is amazing."
"This shop helps me save time and energy. The service they offer is amazingly great. I wouldn't go anywhere else."
"My wife and I love Colorado Seasons. They have the best unique holiday gifts (we went during Christmas when it's a Christmas store but haven't been there other ..."
"Spa brokers is a great place to get all your hot tub needs met. We purchase our supplies from here and also use their bulk warehouse to purchase ..."
"Super target has a great place to shop for all various items. Food is fresh and is watered such as the vegetables. The workers always are helpful and ..."
"Got a haircut today.. Love it. Terri did a great job after fixing someone mistake that I had for 3 month.. Love it going to come back..."
"Did a good job and very through. Found the problem where other brake places didn't. Showed me everything in person and showed me things that were working fine ..."


text:
Hagridden by Samuel Snoek-Brown
Hagridden
Author: Samuel Snoek-Brown
Publisher: Columbus Press
Release Date: August 19th, 2014
Source: Received from the author in exchange for an honest review.
As the Civil War winds violently down, fears of the South's uncertain future fuse with its unraveling traditions. Against the backdrop of this post-apocalyptic landscape, so littered with corpses and mythology and desperation, two women, stranded and alone in the Louisiana bayou, fight to survive.
To be honest with you, I had a lot of trouble getting through this book. While the writer does have some talent and there is some good writing in the bones of this novel, there were just some stylistic things that bugged me at a point it made it difficult to even read. Between the confusing opening, to the odd writing style, I admit, I just couldn't get over some of my first impressions.
The one thing that I severely disliked was the lack of quotation marks in Hagridden. Dialog was just throw in here and there and there was nothing to separate it from the rest of the text. I know, Samuel Snoek-Brown isn't the first author to use this technique, but it personally just distracting for me and interrupts the flow of a book. The speech itself did give a nice impression of the area and the people in the story, but I felt like I was often backtracking when I hit conversation to read it as intended.
The other thing that gets annoying is the two women in this story never get names. Everyone around them is given an identity, but the main characters remain faceless. Again, this is a stylistic thing, but it made it hard for me to relate to them. When you start the story with so much distance, it makes it hard to immerse yourself.
The story itself was interesting. The pacing to start was awkward (I don't even know how to explain it, but it was just difficult to read. The perspectives felt off, and it sort of had that 'shaky camera angle' type feel to it), but after the first chapter it was easier to read. The story had a strong sense of place, and you could feel like you were in the correct point in history, which is always a plus with historical novels. Characters felt dynamic, and there was a good mix of events happening (romance, drama, suspense). It's a bit hard for me to talk about the story since the main characters don't have names, but the relationship dynamics were unique to see. I liked the mother-in-law/daughter-in-law relationship because it's a bit unusual. I also liked seeing their struggles with the husband of the girl out of the picture.
Overall, the bones were really nice. Just some overlying problems that really kept me from getting into things. Other readers may really like this, but it wasn't the book for me in the end.
Labels: Historical Fiction
Taken With a Grain of Salt by Aaron Galvin
Taken With a Grain of Salt
Author: Aaron Galvin
Publisher: Self-Published
Anyone can be taken - a simple truth for Salt folk, a stark reality for innocent teens Garrett Weaver and Kellen Winstel. Kidnapped by Selkie slave catchers and dragged into the realm beneath the waves, both teens must adapt to their new surroundings if they hope to see the shore again. Yet even if they escape their captors, both will need to embrace their fears. For darker things than Selkies lurk in the Salted depths and not all of them have pure intentions for the innocent ones brought down into their watery world.
To read my review of the first book in this series, Salted, click here.
Taken With a Grain of Salt is the sequel to the novel Salted, a book I reviewed back in March. Featuring the same great cast of characters, this novel further explores the world of the selkies, Salt Children, and their ocean bound world. While Salted started with a story line that largely centered in our world, this sequel explores the larger reaches of the salt, and the creatures that live there.
What I like most about these novels is the characters continue to grow and develop as time wears on. While we follow multiple story lines, no character feels neglected and still feels important to the central story. I liked seeing the slow progression of Kellen and Garrett's relationship with the people around them, and I also enjoyed learning more of the lore that created the selkies and the system surrounding them. I will admit, it's hard to speak too much about any one element without throwing out spoilers, but I will say this book was great to fill in information that maybe wasn't in the first novel.
I liked that this book brushed a little more against some of the mermaid elements, while still keeping it's own twists. The concept of selkies and the transformations involved is really cool, but it's also nice to see maybe some elements of what are more typical ocean tails. (tales, tails, yeah, really couldn't avoid that little moment there). I also enjoyed seeing that even those that were the authorities in the Salted world have larger forces they have to be on guard for. Always a bigger fish in the ocean right? Or something like that.
Overall, I love the Salt series because it maintains an element of freshness. The elements the author uses just have a new feeling to them, and the characters are diverse, dynamic and really well rounded. As they adapt to new situations, powers, and relationships, each character in this story is truly tested by the ocean, fighting for survival in both an internal and external way. I enjoyed this book even more than the first one, so hopefully the rest of the series will continue to live up to my now high expectations for this author. No pressure right?
Labels: Fantasy
Blog Update!
In case you haven't noticed, there has been a sort of reading list train-wreck that occurred this month. My daytime job sort of took over my life for a few weeks, and I'm trying my best to catch up, so I appreciate your patience with me. My reading list calendar is going to require some reshuffling, so I apologize for any confusion that may cause.
If you were NaNoWriMoing, I bet you were pretty busy too! I have enjoyed meeting new authors this month, and have been excited to see new writers embracing the November chaos. I've also had a handful of new beta reading projects come through, so it's been great to see some works-in-progress on their way towards publication.
While I have one new review about to be release, here's a little teaser present for the holidays in honor of that review. Aaron Galvin has recently released one of my favorite books from this year, Salted, as a free ebook. So get out there and grab that freebee! http://www.amazon.com/Salted-Aaron-Galvin-ebook/dp/B00JSKYEB4/ref=sr_1_2?ie=UTF8&qid=1417460352&sr=8-2&keywords=salted
Meanwhile, stay tuned for my upcoming review of the sequel: With a Grain of Salt.
Happy Holidays and let's get December off to a good start!


Anything in the color black from Thom Browne is rare, it's said that if you want a piece in black, you'd have to have it custom ordered. And this season's collection used a whole lot of black which is not likely to happen again for awhile so GET IT WHILE YOU CAN! Classic Thom Browne cardigan made of TB's lightweight wool fabric with the signified grosgrain now in black. Like most TB's pieces, this one features a grosgrain placket at the side seams and front. Finished off with ribbed trim at the hem, cuffs, and pockets.